Add repo-family metadata to Fabric registry
This commit is contained in:
parent
3240b78185
commit
0ea99a73d8
15 changed files with 189 additions and 32 deletions
2
SCOPE.md
2
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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 "",
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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"})
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue