M0 e M1 revisados e fixes implementados

This commit is contained in:
Marcos Lohmann
2026-09-14 08:18:53 -03:00
parent 439f60598e
commit f3efcc3414
72 changed files with 4831 additions and 128 deletions

View File

@@ -0,0 +1,107 @@
"""Global Capability Registry storage (EP-02-T03, README.md §6.6, ADR-010).
A control-plane asset, deliberately separate from the per-client catalog
view the Gateway serves (README.md §6.6: "O Global Registry é um
control-plane asset. Ele não deve ser confundido com a visão de catálogo
entregue a cada client."). Consumers (Gateway, Fabric, Offering Filter) only
ever read through `list_active`/`get` -- nothing outside the publishing
pipeline (EP-02-T02) writes here, and the pipeline never writes directly:
it only transitions status through `lifecycle.py`.
SQLite, used synchronously: this store is not on the Gateway's request
hot path in this milestone (routing is still static, EP-06 wires it in
later), and SQLite operations here are single-digit milliseconds -- an
`asyncio.to_thread` wrapper is a straightforward addition if/when a caller
needs this off the event loop.
"""
from __future__ import annotations
import sqlite3
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from emcp_bus.registry.models import CapabilityManifest, LifecycleStatus
class CapabilityNotFoundError(Exception):
pass
class RegistryStore:
def __init__(self, db_path: str | Path) -> None:
self._db_path = str(db_path)
with self._connect() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS capabilities (
name TEXT PRIMARY KEY,
domain TEXT NOT NULL,
manifest_json TEXT NOT NULL,
lifecycle_status TEXT NOT NULL
)
"""
)
@contextmanager
def _connect(self) -> Iterator[sqlite3.Connection]:
conn = sqlite3.connect(self._db_path)
try:
yield conn
conn.commit()
finally:
conn.close()
def upsert(self, manifest: CapabilityManifest) -> None:
with self._connect() as conn:
conn.execute(
"""
INSERT INTO capabilities (name, domain, manifest_json, lifecycle_status)
VALUES (?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
domain = excluded.domain,
manifest_json = excluded.manifest_json,
lifecycle_status = excluded.lifecycle_status
""",
(
manifest.name,
manifest.domain,
manifest.model_dump_json(),
manifest.lifecycle.status.value,
),
)
def get(self, name: str) -> CapabilityManifest:
with self._connect() as conn:
row = conn.execute(
"SELECT manifest_json FROM capabilities WHERE name = ?", (name,)
).fetchone()
if row is None:
raise CapabilityNotFoundError(name)
return CapabilityManifest.model_validate_json(row[0])
def list_by_domain(self, domain: str) -> list[CapabilityManifest]:
with self._connect() as conn:
rows = conn.execute(
"SELECT manifest_json FROM capabilities WHERE domain = ? ORDER BY name", (domain,)
).fetchall()
return [CapabilityManifest.model_validate_json(row[0]) for row in rows]
def list_active(self) -> list[CapabilityManifest]:
"""What Gateway/Fabric/Offering Filter are allowed to read (README.md §6.6) --
anything still in nominate/review/register, or already deprecated/retired,
is a control-plane-only concern."""
with self._connect() as conn:
rows = conn.execute(
"SELECT manifest_json FROM capabilities WHERE lifecycle_status = ? ORDER BY name",
(LifecycleStatus.ACTIVE.value,),
).fetchall()
return [CapabilityManifest.model_validate_json(row[0]) for row in rows]
def set_lifecycle_status(self, name: str, status: LifecycleStatus) -> None:
manifest = self.get(name)
updated = manifest.model_copy(
update={"lifecycle": manifest.lifecycle.model_copy(update={"status": status})}
)
self.upsert(updated)