From 0ea99a73d8c081c56c2b4d165453f44112a3cae3 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 25 Jul 2026 18:08:57 +0200 Subject: [PATCH] Add repo-family metadata to Fabric registry --- SCOPE.md | 2 +- WORK-RECORDS.md | 4 +- docs/rail-rapp-reef-adaptation.md | 23 ++++- docs/registry-onboarding.md | 12 +++ railiance_fabric/accountability_roots.py | 5 + railiance_fabric/cli.py | 41 ++++++--- railiance_fabric/connectors.py | 5 + railiance_fabric/graph_explorer.py | 5 + railiance_fabric/registry.py | 92 +++++++++++++++++-- registry/railiance-repos.yaml | 8 ++ tests/test_accountability_root_adapters.py | 2 + tests/test_connectors.py | 4 +- tests/test_graph_explorer.py | 3 + tests/test_registry.py | 4 + ...P-0025-rail-rapp-reef-fabric-adaptation.md | 11 ++- 15 files changed, 189 insertions(+), 32 deletions(-) diff --git a/SCOPE.md b/SCOPE.md index 37b3e25..223e364 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -68,7 +68,7 @@ read model. ## Current State -- Status: active implementation +- Status: operational / evolving - Implementation: registry service, graph validation and query tooling, graph explorer, State Hub export contract, financial-fabric reset flow, and local onboarding manifests are all present diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index e3e1b43..af3643d 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,8 +8,8 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | -| workplan | RAIL-FAB-WP-0025 | active | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | +| workplan | RAIL-FAB-WP-0025 | finished | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | | task | RAIL-FAB-WP-0025-T01 | done | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | | task | RAIL-FAB-WP-0025-T02 | done | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | | task | RAIL-FAB-WP-0025-T03 | done | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | -| task | RAIL-FAB-WP-0025-T04 | wait | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | +| task | RAIL-FAB-WP-0025-T04 | done | — | workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md | diff --git a/docs/rail-rapp-reef-adaptation.md b/docs/rail-rapp-reef-adaptation.md index 64a129f..e1369f8 100644 --- a/docs/rail-rapp-reef-adaptation.md +++ b/docs/rail-rapp-reef-adaptation.md @@ -19,6 +19,7 @@ This document is intentionally narrower than the framework architecture in The first adaptation wave is deliberately small: - onboard the missing current Railiance repos in the seed manifest +- persist repo-family metadata in the registry and repository projections - add the first rail-specific capability type - define canonical `service_type` values for future declarations - avoid inventing full repo-family metadata or new export relations before the @@ -63,6 +64,21 @@ Initial Fabric shape: The first declarations should make the substrate boundary visible before Fabric tries to model every machine or placement relation in detail. +## Registry Metadata Path + +The registry now carries the first repo-family-aware metadata fields on +repository records: + +- `repo_family` +- `ownership_repo` +- `primary_rail` +- `supported_rails` +- `substrate_kind` + +This metadata is available through repository registration, manifest sync, and +repository projections such as the graph explorer's synthesized repository +nodes. + ## First New Capability Type Fabric now reserves: @@ -83,12 +99,13 @@ service-level visibility before they need their own dedicated capability types. The following are not done by this adaptation slice: -- repo-level `repo_family` metadata in the registry -- `ownership_repo`, `supported_rails`, `primary_rail`, or `substrate_kind` - registry metadata - export relations such as `supports_rail`, `hosts_rail`, `binds_rapp`, or `governed_by` - validator rules for repo-family-specific declaration completeness +- graph queries that resolve placement or governance directly from the new + repo-family metadata +- State Hub read-model fields that surface repo family directly in generated + repo briefs or inventory views Those require a second wave after the first real `rail-*`, `rapp-*`, and `reef-*` repos exist. diff --git a/docs/registry-onboarding.md b/docs/registry-onboarding.md index 35c51ed..3326434 100644 --- a/docs/registry-onboarding.md +++ b/docs/registry-onboarding.md @@ -45,6 +45,7 @@ repositories: path: .. default_branch: main state_hub_repo_id: 2c0de614-e468-4eb6-8157-470649ac8c05 + repo_family: ownership declaration_paths: - .. sbom: bom.json @@ -58,6 +59,17 @@ repositories: link back to State Hub repo views, but do not block onboarding when a repo is registered in Fabric before that ID is wired in here. +The following repo-family metadata fields are also optional: + +- `repo_family`: `ownership`, `rail`, `rapp`, or `reef` +- `ownership_repo`: canonical owning `railiance-*` repo when the registered repo + is not itself an ownership repo +- `primary_rail`: default rail for a reef or package when one is explicitly + declared +- `supported_rails`: array of supported rail ids for `rapp-*` repos +- `substrate_kind`: grouped substrate category such as `server`, + `workstation-group`, or `cluster` + `sbom` or `sboms` may point to CycloneDX JSON/YAML files. When present, the command ingests them as queryable library inventory after repository registration. diff --git a/railiance_fabric/accountability_roots.py b/railiance_fabric/accountability_roots.py index 5b03c72..70f8676 100644 --- a/railiance_fabric/accountability_roots.py +++ b/railiance_fabric/accountability_roots.py @@ -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 "", diff --git a/railiance_fabric/cli.py b/railiance_fabric/cli.py index ca172a4..f14d4ae 100644 --- a/railiance_fabric/cli.py +++ b/railiance_fabric/cli.py @@ -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 [] diff --git a/railiance_fabric/connectors.py b/railiance_fabric/connectors.py index e11c7dc..fa3a29c 100644 --- a/railiance_fabric/connectors.py +++ b/railiance_fabric/connectors.py @@ -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", diff --git a/railiance_fabric/graph_explorer.py b/railiance_fabric/graph_explorer.py index 22763d4..e702000 100644 --- a/railiance_fabric/graph_explorer.py +++ b/railiance_fabric/graph_explorer.py @@ -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", diff --git a/railiance_fabric/registry.py b/railiance_fabric/registry.py index 2356f26..ec8d343 100644 --- a/railiance_fabric/registry.py +++ b/railiance_fabric/registry.py @@ -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") diff --git a/registry/railiance-repos.yaml b/registry/railiance-repos.yaml index bdc8a38..edb463f 100644 --- a/registry/railiance-repos.yaml +++ b/registry/railiance-repos.yaml @@ -7,6 +7,7 @@ repositories: path: .. default_branch: main state_hub_repo_id: 2c0de614-e468-4eb6-8157-470649ac8c05 + repo_family: ownership declaration_paths: - .. @@ -15,12 +16,14 @@ repositories: path: /home/worsch/railiance-master remote_url: forgejo-remote:coulomb/railiance-master.git default_branch: main + repo_family: ownership - slug: railiance-forge name: railiance-forge path: /home/worsch/railiance-forge remote_url: forgejo-remote:coulomb/railiance-forge.git default_branch: main + repo_family: ownership - slug: railiance-infra name: railiance-infra @@ -28,6 +31,7 @@ repositories: remote_url: http://92.205.130.254:32166/coulomb/railiance-infra.git default_branch: main state_hub_repo_id: 485187c0-4fad-42f7-984c-5e317a66c5de + repo_family: ownership - slug: railiance-cluster name: railiance-cluster @@ -35,6 +39,7 @@ repositories: remote_url: http://92.205.130.254:32166/coulomb/railiance-cluster.git default_branch: main state_hub_repo_id: c3fd0dd2-a0de-415c-8e3a-e37406f4b8f8 + repo_family: ownership - slug: railiance-platform name: railiance-platform @@ -42,6 +47,7 @@ repositories: remote_url: http://92.205.130.254:32166/coulomb/railiance-platform.git default_branch: main state_hub_repo_id: 5115e0c5-009f-4168-b155-9943fe2ab9a7 + repo_family: ownership - slug: railiance-apps name: railiance-apps @@ -49,6 +55,7 @@ repositories: remote_url: http://92.205.130.254:32166/coulomb/railiance-apps.git default_branch: main state_hub_repo_id: cb06310e-4381-428b-b8f3-d3ea8ac2f70d + repo_family: ownership - slug: railiance-enablement name: railiance-enablement @@ -56,6 +63,7 @@ repositories: remote_url: http://92.205.130.254:32166/coulomb/railiance-enablement.git default_branch: main state_hub_repo_id: bc978be9-ca72-42bb-a451-d4737b779c5b + repo_family: ownership - slug: flex-auth name: flex-auth diff --git a/tests/test_accountability_root_adapters.py b/tests/test_accountability_root_adapters.py index e071ecf..069bb3c 100644 --- a/tests/test_accountability_root_adapters.py +++ b/tests/test_accountability_root_adapters.py @@ -34,6 +34,7 @@ def test_collect_accountability_root_evidence_from_manifest(tmp_path: Path) -> N registered_repo = next(item for item in evidence if item["evidence_type"] == "registered_repository") assert registered_repo["attributes"]["state_hub_repo_id"] == "fixture-state-hub-id" + assert registered_repo["attributes"]["repo_family"] == "ownership" secret_root = next(item for item in evidence if item["evidence_type"] == "secret_root") assert "secret-value" not in json.dumps(secret_root) @@ -420,6 +421,7 @@ repositories: default_branch: main state_hub_repo_id: fixture-state-hub-id remote_url: gitea-remote:coulomb/fixture-repo.git + repo_family: ownership """.format(repo=repo), encoding="utf-8", ) diff --git a/tests/test_connectors.py b/tests/test_connectors.py index 59a8ca6..3a91c33 100644 --- a/tests/test_connectors.py +++ b/tests/test_connectors.py @@ -42,7 +42,7 @@ def test_local_fabric_registry_connector_adds_separate_registry_evidence(tmp_pat _validate_schema("discovery-snapshot.schema.yaml", snapshot) assert snapshot["connector_runs"][0]["status"] == "success" - assert snapshot["connector_runs"][0]["candidate_counts"] == {"nodes": 1, "edges": 1, "attributes": 8} + assert snapshot["connector_runs"][0]["candidate_counts"] == {"nodes": 1, "edges": 1, "attributes": 9} assert any(scope["source_kind"] == "fabric_registry" for scope in snapshot["replacement_scopes"]) registry_node = next(node for node in snapshot["candidates"]["nodes"] if node["kind"] == "FabricRegistryEntry") @@ -50,6 +50,7 @@ def test_local_fabric_registry_connector_adds_separate_registry_evidence(tmp_pat assert registry_node["review_state"] == "candidate" assert registry_node["replacement_scope"].startswith("scope:fixture-repo:local-fabric-registry:fabric_registry") assert registry_node["attributes"]["state_hub_repo_id"] == "state-hub-id" + assert registry_node["attributes"]["repo_family"] == "ownership" assert any(edge["edge_type"] == "cataloged_as" for edge in snapshot["candidates"]["edges"]) registry_attributes = { attribute["name"]: attribute @@ -136,6 +137,7 @@ repositories: default_branch: main state_hub_repo_id: state-hub-id remote_url: gitea-remote:coulomb/fixture-repo.git + repo_family: ownership declaration_paths: - /tmp/fixture-repo """.lstrip(), diff --git a/tests/test_graph_explorer.py b/tests/test_graph_explorer.py index ec5f192..b227ad0 100644 --- a/tests/test_graph_explorer.py +++ b/tests/test_graph_explorer.py @@ -27,6 +27,7 @@ def test_graph_explorer_manifest_and_payload_validate() -> None: "slug": "railiance-fabric", "name": "Railiance Fabric", "state_hub_repo_id": "2c0de614-e468-4eb6-8157-470649ac8c05", + "repo_family": "ownership", }, { "slug": "registered-only", @@ -73,6 +74,7 @@ def test_graph_explorer_manifest_and_payload_validate() -> None: assert registered_only["data"]["reviewState"] == "candidate" assert registered_only["data"]["unresolved"] is True + assert nodes_by_id["repo:railiance-fabric"]["data"]["repoFamily"] == "ownership" assert deployment["data"]["layer"] == "deployment" assert server["data"]["layer"] == "server" assert ":" not in server["data"]["label"] @@ -358,6 +360,7 @@ def test_registry_serves_graph_explorer_exports(tmp_path: Path) -> None: "slug": "railiance-fabric", "name": "Railiance Fabric", "state_hub_repo_id": "2c0de614-e468-4eb6-8157-470649ac8c05", + "repo_family": "ownership", } ) store.upsert_repository({"slug": "registered-only", "name": "Registered Only"}) diff --git a/tests/test_registry.py b/tests/test_registry.py index 6344790..811b7c4 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -35,6 +35,7 @@ def test_registry_accepts_snapshot_and_queries_graph(tmp_path: Path) -> None: "slug": "railiance-fabric", "name": "Railiance Fabric", "remote_url": "https://example.invalid/railiance-fabric.git", + "repo_family": "ownership", } ) @@ -78,6 +79,7 @@ def test_registry_accepts_snapshot_and_queries_graph(tmp_path: Path) -> None: libraries = store.ingest_cyclonedx("railiance-fabric", _cyclonedx_bom()) assert snapshot["repo_slug"] == "railiance-fabric" + assert store.get_repository("railiance-fabric")["repo_family"] == "ownership" assert changed_snapshot["commit"] == "test-commit-2" assert artifact["artifact_type"] == "openapi" assert libraries["component_count"] == 2 @@ -469,6 +471,7 @@ def test_registry_sync_manifest_registers_multiple_repos(tmp_path: Path) -> None " name: Railiance Fabric", f" path: {Path('.').resolve()}", " commit: manifest-commit", + " repo_family: ownership", f" sbom: {sbom_path.name}", " - slug: missing-repo", " name: Missing Repo", @@ -491,6 +494,7 @@ def test_registry_sync_manifest_registers_multiple_repos(tmp_path: Path) -> None repositories = {repo["slug"]: repo for repo in store.list_repositories()} assert set(repositories) == {"missing-repo", "railiance-fabric"} + assert repositories["railiance-fabric"]["repo_family"] == "ownership" assert store.latest_snapshot("railiance-fabric")["commit"] == "manifest-commit" assert store.list_snapshots("missing-repo") == [] assert store.list_libraries(repo_slug="railiance-fabric")[0]["name"] == "jsonschema" diff --git a/workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md b/workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md index ad99956..ad75f70 100644 --- a/workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md +++ b/workplans/RAIL-FAB-WP-0025-rail-rapp-reef-fabric-adaptation.md @@ -4,7 +4,7 @@ type: workplan title: "Rail Rapp Reef Fabric Adaptation" domain: financials repo: railiance-fabric -status: active +status: finished owner: codex topic_slug: railiance created: "2026-07-25" @@ -107,13 +107,11 @@ documented the initial declaration guidance in ```task id: RAIL-FAB-WP-0025-T04 -status: wait +status: done priority: medium state_hub_task_id: "dbfd3000-0204-473e-b1b8-eeb44d3fcd06" ``` -Blocked on T01-T03. - Define the next implementation slice for: - repo-family-aware registry metadata @@ -122,3 +120,8 @@ Define the next implementation slice for: Done when the next repo-local implementation wave can start without reopening the compatibility-first vocabulary decisions above. + +2026-07-25: Implemented the first repo-family-aware registry metadata path in +the registry store, manifest sync flow, and graph-explorer repository +projection. Remaining relation and State Hub projection gaps are now recorded +explicitly in `docs/rail-rapp-reef-adaptation.md`.