Add repo-family metadata to Fabric registry
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s

This commit is contained in:
codex 2026-07-25 18:08:57 +02:00
parent 3240b78185
commit 0ea99a73d8
15 changed files with 189 additions and 32 deletions

View file

@ -1078,6 +1078,11 @@ def _registry_manifest_evidence(root: dict[str, Any], *, max_items: int) -> list
"domain": repo.get("domain", ""),
"default_branch": repo.get("default_branch", ""),
"state_hub_repo_id": repo.get("state_hub_repo_id", ""),
"repo_family": repo.get("repo_family", ""),
"ownership_repo": repo.get("ownership_repo", ""),
"primary_rail": repo.get("primary_rail", ""),
"supported_rails": repo.get("supported_rails", []) if isinstance(repo.get("supported_rails"), list) else [],
"substrate_kind": repo.get("substrate_kind", ""),
"has_local_path": bool(repo.get("path")),
"has_remote_url": bool(repo.get("remote_url")),
"canonical_slug": identity_slug if identity_slug != repo_slug else "",

View file

@ -155,6 +155,11 @@ def build_parser() -> argparse.ArgumentParser:
sync.add_argument("--remote-url", default=None)
sync.add_argument("--default-branch", default="main")
sync.add_argument("--state-hub-repo-id", default=None)
sync.add_argument("--repo-family", default=None)
sync.add_argument("--ownership-repo", default=None)
sync.add_argument("--primary-rail", default=None)
sync.add_argument("--supported-rail", action="append", default=[], help="Declare a supported rail. May be repeated.")
sync.add_argument("--substrate-kind", default=None)
sync.add_argument("--commit", default=None)
sync.add_argument("--json", action="store_true", help="Print the raw snapshot response.")
@ -448,6 +453,11 @@ def _registry_sync(args: argparse.Namespace) -> int:
"remote_url": args.remote_url or _git_value(repo_path, "config", "--get", "remote.origin.url"),
"default_branch": args.default_branch,
"state_hub_repo_id": args.state_hub_repo_id,
"repo_family": args.repo_family,
"ownership_repo": args.ownership_repo,
"primary_rail": args.primary_rail,
"supported_rails": args.supported_rail,
"substrate_kind": args.substrate_kind,
},
)
snapshot = _registry_post(
@ -824,13 +834,7 @@ def _scan_manifest_repo(
repository = _registry_post_checked(
registry_url,
"/repositories",
{
"slug": slug,
"name": item.get("name") or repo_path.name,
"remote_url": item.get("remote_url") or _git_value(repo_path, "config", "--get", "remote.origin.url"),
"default_branch": item.get("default_branch") or "main",
"state_hub_repo_id": item.get("state_hub_repo_id"),
},
_manifest_repository_payload(item, repo_path, slug),
)
stored = _registry_post_checked(
registry_url,
@ -1230,13 +1234,7 @@ def _sync_manifest_repo(registry_url: str, manifest_dir: Path, item: object) ->
repository = _registry_post_checked(
registry_url,
"/repositories",
{
"slug": slug,
"name": item.get("name") or (repo_path.name if repo_path else slug),
"remote_url": item.get("remote_url") or _git_value(repo_path, "config", "--get", "remote.origin.url"),
"default_branch": item.get("default_branch") or "main",
"state_hub_repo_id": item.get("state_hub_repo_id"),
},
_manifest_repository_payload(item, repo_path, slug),
)
result["repository"] = repository
except RegistryRequestError as exc:
@ -1597,6 +1595,21 @@ def _manifest_optional_path(value: object, manifest_dir: Path) -> Path | None:
return path if path.is_absolute() else (manifest_dir / path).resolve()
def _manifest_repository_payload(item: dict[str, Any], repo_path: Path | None, slug: str) -> dict[str, Any]:
return {
"slug": slug,
"name": item.get("name") or (repo_path.name if repo_path else slug),
"remote_url": item.get("remote_url") or _git_value(repo_path, "config", "--get", "remote.origin.url"),
"default_branch": item.get("default_branch") or "main",
"state_hub_repo_id": item.get("state_hub_repo_id"),
"repo_family": item.get("repo_family"),
"ownership_repo": item.get("ownership_repo"),
"primary_rail": item.get("primary_rail"),
"supported_rails": item.get("supported_rails"),
"substrate_kind": item.get("substrate_kind"),
}
def _manifest_paths(value: object, manifest_dir: Path) -> list[Path]:
if value is None:
return []

View file

@ -209,6 +209,11 @@ class LocalFabricRegistryConnector:
"registry_remote_url": match.get("remote_url") or "",
"registry_default_branch": match.get("default_branch") or "",
"state_hub_repo_id": match.get("state_hub_repo_id") or "",
"repo_family": match.get("repo_family") or "",
"ownership_repo": match.get("ownership_repo") or "",
"primary_rail": match.get("primary_rail") or "",
"supported_rails": match.get("supported_rails") if isinstance(match.get("supported_rails"), list) else [],
"substrate_kind": match.get("substrate_kind") or "",
"declaration_paths": match.get("declaration_paths") if isinstance(match.get("declaration_paths"), list) else [],
},
"origin": "registry",

View file

@ -375,6 +375,11 @@ def fabric_graph_explorer_payload(
"confidence": 1.0 if has_snapshot else 0.3,
"visualSize": 56 if has_snapshot else 42,
"ownership": "registry",
"repoFamily": str(repo.get("repo_family") or ""),
"ownershipRepo": str(repo.get("ownership_repo") or ""),
"primaryRail": str(repo.get("primary_rail") or ""),
"supportedRails": repo.get("supported_rails") if isinstance(repo.get("supported_rails"), list) else [],
"substrateKind": str(repo.get("substrate_kind") or ""),
"displayState": "show",
"visibilitySource": "default",
"visibilityReason": "default",

View file

@ -45,6 +45,11 @@ class RegistryStore:
remote_url text,
default_branch text,
state_hub_repo_id text,
repo_family text,
ownership_repo text,
primary_rail text,
supported_rails_json text not null default '[]',
substrate_kind text,
created_at text not null,
updated_at text not null
);
@ -127,6 +132,7 @@ class RegistryStore:
);
"""
)
_ensure_repository_columns(db)
def upsert_repository(self, payload: dict[str, Any]) -> dict[str, Any]:
slug = _required_text(payload, "slug")
@ -135,22 +141,46 @@ class RegistryStore:
remote_url = _optional_text(payload, "remote_url")
default_branch = str(payload.get("default_branch") or "main")
state_hub_repo_id = _optional_text(payload, "state_hub_repo_id")
repo_family = _optional_text(payload, "repo_family")
ownership_repo = _optional_text(payload, "ownership_repo")
primary_rail = _optional_text(payload, "primary_rail")
supported_rails = _optional_string_list(payload, "supported_rails")
substrate_kind = _optional_text(payload, "substrate_kind")
with self._connect() as db:
db.execute(
"""
insert into repositories (
slug, name, remote_url, default_branch, state_hub_repo_id,
created_at, updated_at
repo_family, ownership_repo, primary_rail, supported_rails_json,
substrate_kind, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict(slug) do update set
name = excluded.name,
remote_url = excluded.remote_url,
default_branch = excluded.default_branch,
state_hub_repo_id = excluded.state_hub_repo_id,
repo_family = excluded.repo_family,
ownership_repo = excluded.ownership_repo,
primary_rail = excluded.primary_rail,
supported_rails_json = excluded.supported_rails_json,
substrate_kind = excluded.substrate_kind,
updated_at = excluded.updated_at
""",
(slug, name, remote_url, default_branch, state_hub_repo_id, now, now),
(
slug,
name,
remote_url,
default_branch,
state_hub_repo_id,
repo_family,
ownership_repo,
primary_rail,
json.dumps(supported_rails, sort_keys=True),
substrate_kind,
now,
now,
),
)
return self.get_repository(slug)
@ -159,19 +189,21 @@ class RegistryStore:
rows = db.execute(
"""
select slug, name, remote_url, default_branch, state_hub_repo_id,
created_at, updated_at
repo_family, ownership_repo, primary_rail,
supported_rails_json, substrate_kind, created_at, updated_at
from repositories
order by slug
"""
).fetchall()
return [_row_dict(row) for row in rows]
return [_repository_dict(row) for row in rows]
def get_repository(self, slug: str) -> dict[str, Any]:
with self._connect() as db:
row = db.execute(
"""
select slug, name, remote_url, default_branch, state_hub_repo_id,
created_at, updated_at
repo_family, ownership_repo, primary_rail,
supported_rails_json, substrate_kind, created_at, updated_at
from repositories
where slug = ?
""",
@ -179,7 +211,7 @@ class RegistryStore:
).fetchone()
if row is None:
raise RegistryError(f"repository not found: {slug}", 404)
return _row_dict(row)
return _repository_dict(row)
def add_snapshot(self, repo_slug: str, payload: dict[str, Any]) -> dict[str, Any]:
self.get_repository(repo_slug)
@ -1503,6 +1535,34 @@ def _row_dict(row: sqlite3.Row) -> dict[str, Any]:
return {key: row[key] for key in row.keys()}
def _repository_dict(row: sqlite3.Row) -> dict[str, Any]:
data = _row_dict(row)
raw_supported_rails = data.pop("supported_rails_json", "[]")
try:
decoded = json.loads(raw_supported_rails or "[]")
except json.JSONDecodeError:
decoded = []
data["supported_rails"] = decoded if isinstance(decoded, list) else []
return data
def _ensure_repository_columns(db: sqlite3.Connection) -> None:
existing = {
str(row[1])
for row in db.execute("pragma table_info(repositories)").fetchall()
}
additions = {
"repo_family": "text",
"ownership_repo": "text",
"primary_rail": "text",
"supported_rails_json": "text not null default '[]'",
"substrate_kind": "text",
}
for name, ddl in additions.items():
if name not in existing:
db.execute(f"alter table repositories add column {name} {ddl}")
def _resettable_counts(db: sqlite3.Connection) -> dict[str, int]:
return {
"snapshots": int(db.execute("select count(*) from snapshots").fetchone()[0]),
@ -1911,6 +1971,24 @@ def _optional_text(payload: dict[str, Any], key: str) -> str | None:
return value
def _optional_string_list(payload: dict[str, Any], key: str) -> list[str]:
value = payload.get(key)
if value is None:
return []
if not isinstance(value, list):
raise RegistryError(f"field '{key}' must be an array of strings")
result: list[str] = []
seen: set[str] = set()
for item in value:
if not isinstance(item, str) or not item.strip():
raise RegistryError(f"field '{key}' must be an array of non-empty strings")
cleaned = item.strip()
if cleaned not in seen:
seen.add(cleaned)
result.append(cleaned)
return result
def _utc_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")