Project repo-family relations from local declarations
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

This commit is contained in:
codex 2026-07-26 08:51:29 +02:00
parent 1d8f59f1de
commit 9af9ee3e72
13 changed files with 671 additions and 28 deletions

View file

@ -16,7 +16,7 @@ from .financial import (
materialize_financial_graph_export,
merge_financial_graph_exports,
)
from .loader import repo_root
from .loader import load_yaml, repo_root
from .schema_validation import draft202012_validator
RESET_CONFIRMATION_TOKEN = "RESET-RAILIANCE-FABRIC-GRAPH-DATA"
@ -42,6 +42,7 @@ class RegistryStore:
create table if not exists repositories (
slug text primary key,
name text not null,
path text,
remote_url text,
default_branch text,
state_hub_repo_id text,
@ -49,6 +50,7 @@ class RegistryStore:
ownership_repo text,
primary_rail text,
supported_rails_json text not null default '[]',
declaration_paths_json text not null default '[]',
substrate_kind text,
created_at text not null,
updated_at text not null
@ -138,6 +140,7 @@ class RegistryStore:
slug = _required_text(payload, "slug")
now = _utc_now()
name = str(payload.get("name") or slug)
path = _optional_text(payload, "path")
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")
@ -145,18 +148,20 @@ class RegistryStore:
ownership_repo = _optional_text(payload, "ownership_repo")
primary_rail = _optional_text(payload, "primary_rail")
supported_rails = _optional_string_list(payload, "supported_rails")
declaration_paths = _optional_string_list(payload, "declaration_paths")
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,
slug, name, path, remote_url, default_branch, state_hub_repo_id,
repo_family, ownership_repo, primary_rail, supported_rails_json,
substrate_kind, created_at, updated_at
declaration_paths_json, substrate_kind, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
on conflict(slug) do update set
name = excluded.name,
path = excluded.path,
remote_url = excluded.remote_url,
default_branch = excluded.default_branch,
state_hub_repo_id = excluded.state_hub_repo_id,
@ -164,12 +169,14 @@ class RegistryStore:
ownership_repo = excluded.ownership_repo,
primary_rail = excluded.primary_rail,
supported_rails_json = excluded.supported_rails_json,
declaration_paths_json = excluded.declaration_paths_json,
substrate_kind = excluded.substrate_kind,
updated_at = excluded.updated_at
""",
(
slug,
name,
path,
remote_url,
default_branch,
state_hub_repo_id,
@ -177,6 +184,7 @@ class RegistryStore:
ownership_repo,
primary_rail,
json.dumps(supported_rails, sort_keys=True),
json.dumps(declaration_paths, sort_keys=True),
substrate_kind,
now,
now,
@ -188,9 +196,10 @@ class RegistryStore:
with self._connect() as db:
rows = db.execute(
"""
select slug, name, remote_url, default_branch, state_hub_repo_id,
select slug, name, path, remote_url, default_branch, state_hub_repo_id,
repo_family, ownership_repo, primary_rail,
supported_rails_json, substrate_kind, created_at, updated_at
supported_rails_json, declaration_paths_json, substrate_kind,
created_at, updated_at
from repositories
order by slug
"""
@ -201,9 +210,10 @@ class RegistryStore:
with self._connect() as db:
row = db.execute(
"""
select slug, name, remote_url, default_branch, state_hub_repo_id,
select slug, name, path, remote_url, default_branch, state_hub_repo_id,
repo_family, ownership_repo, primary_rail,
supported_rails_json, substrate_kind, created_at, updated_at
supported_rails_json, declaration_paths_json, substrate_kind,
created_at, updated_at
from repositories
where slug = ?
""",
@ -474,6 +484,13 @@ class RegistryStore:
for edge in graph.get("edges", []):
if isinstance(edge, dict):
edges.append(_edge_with_canon_metadata(edge))
projected_nodes, projected_edges = _project_repository_family_graph(self.list_repositories())
for node in projected_nodes:
node_id = str(node.get("id", ""))
if node_id:
nodes[node_id] = node
for edge in projected_edges:
edges.append(_edge_with_canon_metadata(edge))
return {
"apiVersion": "railiance.fabric/v1alpha1",
"kind": "FabricGraphExport",
@ -1538,11 +1555,17 @@ def _row_dict(row: sqlite3.Row) -> dict[str, Any]:
def _repository_dict(row: sqlite3.Row) -> dict[str, Any]:
data = _row_dict(row)
raw_supported_rails = data.pop("supported_rails_json", "[]")
raw_declaration_paths = data.pop("declaration_paths_json", "[]")
try:
decoded = json.loads(raw_supported_rails or "[]")
except json.JSONDecodeError:
decoded = []
data["supported_rails"] = decoded if isinstance(decoded, list) else []
try:
declaration_paths = json.loads(raw_declaration_paths or "[]")
except json.JSONDecodeError:
declaration_paths = []
data["declaration_paths"] = declaration_paths if isinstance(declaration_paths, list) else []
return data
@ -1552,10 +1575,12 @@ def _ensure_repository_columns(db: sqlite3.Connection) -> None:
for row in db.execute("pragma table_info(repositories)").fetchall()
}
additions = {
"path": "text",
"repo_family": "text",
"ownership_repo": "text",
"primary_rail": "text",
"supported_rails_json": "text not null default '[]'",
"declaration_paths_json": "text not null default '[]'",
"substrate_kind": "text",
}
for name, ddl in additions.items():
@ -1989,6 +2014,262 @@ def _optional_string_list(payload: dict[str, Any], key: str) -> list[str]:
return result
def _project_repository_family_graph(repositories: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
nodes: dict[str, dict[str, Any]] = {}
edges: dict[tuple[str, str, str], dict[str, Any]] = {}
for repository in repositories:
slug = str(repository.get("slug") or "").strip()
if not slug:
continue
projection = _repository_family_projection(repository)
nodes[_repository_graph_node_id(slug)] = _repository_projection_node(repository, projection)
ownership_repo = projection.get("ownership_repo")
if isinstance(ownership_repo, str) and ownership_repo:
target_id = _repository_graph_node_id(ownership_repo)
nodes.setdefault(target_id, _placeholder_repository_node(ownership_repo))
edge = _repository_projection_edge(
slug,
ownership_repo,
"governed_by",
projection,
)
if edge is not None:
edges[_edge_key(edge)] = edge
for rail_slug in _string_list(projection.get("supported_rails")):
target_id = _repository_graph_node_id(rail_slug)
nodes.setdefault(target_id, _placeholder_repository_node(rail_slug))
edge = _repository_projection_edge(slug, rail_slug, "supports_rail", projection)
if edge is not None:
edges[_edge_key(edge)] = edge
for rail_slug in _string_list(projection.get("hosted_rails")):
target_id = _repository_graph_node_id(rail_slug)
nodes.setdefault(target_id, _placeholder_repository_node(rail_slug))
edge = _repository_projection_edge(slug, rail_slug, "hosts_rail", projection)
if edge is not None:
edges[_edge_key(edge)] = edge
for rapp_slug in _string_list(projection.get("bound_rapps")):
target_id = _repository_graph_node_id(rapp_slug)
nodes.setdefault(target_id, _placeholder_repository_node(rapp_slug))
edge = _repository_projection_edge(slug, rapp_slug, "binds_rapp", projection)
if edge is not None:
edges[_edge_key(edge)] = edge
return [nodes[key] for key in sorted(nodes)], [edges[key] for key in sorted(edges)]
def _repository_family_projection(repository: dict[str, Any]) -> dict[str, Any]:
family = str(repository.get("repo_family") or "").strip()
repo_path = _repository_checkout_path(repository)
if repo_path is None or not repo_path.is_dir():
return {}
if family == "rail":
declaration_path = repo_path / "declarations" / "rail.yaml"
declaration = _load_yaml_mapping(declaration_path)
if declaration is None:
return {}
return {
"ownership_repo": _text_value(declaration, "ownership_repo"),
"source_links": [{"label": "Rail declaration", "path": str(declaration_path)}],
}
if family == "rapp":
declaration_path = repo_path / "declarations" / "rapp.yaml"
declaration = _load_yaml_mapping(declaration_path)
if declaration is None:
return {}
supported_rails = _string_list(declaration.get("supported_rails"))
if not supported_rails:
primary_rail = _text_value(declaration, "primary_rail")
if primary_rail:
supported_rails = [primary_rail]
return {
"ownership_repo": _text_value(declaration, "ownership_repo"),
"supported_rails": supported_rails,
"source_links": [{"label": "Rapp declaration", "path": str(declaration_path)}],
}
if family == "reef":
declaration_path = repo_path / "declarations" / "reef.yaml"
declaration = _load_yaml_mapping(declaration_path)
if declaration is None:
return {}
rails_path = repo_path / "bindings" / "rails.yaml"
rails_binding = _load_yaml_mapping(rails_path)
hosted_rails = _relation_id_list(
rails_binding.get("hosted_rails") if isinstance(rails_binding, dict) else None,
key="rail_id",
)
if not hosted_rails:
hosted_rails = _relation_id_list(declaration.get("hosted_rails"), key="rail_id")
if not hosted_rails:
primary_rail = _text_value(declaration, "primary_rail")
if primary_rail:
hosted_rails = [primary_rail]
rapps_path = repo_path / "bindings" / "rapps.yaml"
rapps_binding = _load_yaml_mapping(rapps_path)
bound_rapps = _relation_id_list(
rapps_binding.get("bound_rapps") if isinstance(rapps_binding, dict) else None,
key="rapp_id",
)
if not bound_rapps:
bound_rapps = _relation_id_list(declaration.get("bound_rapps"), key="rapp_id")
source_links = [{"label": "Reef declaration", "path": str(declaration_path)}]
if rails_binding is not None:
source_links.append({"label": "Reef rail bindings", "path": str(rails_path)})
if rapps_binding is not None:
source_links.append({"label": "Reef rapp bindings", "path": str(rapps_path)})
return {
"ownership_repo": _text_value(declaration, "ownership_repo"),
"hosted_rails": hosted_rails,
"bound_rapps": bound_rapps,
"source_links": source_links,
}
return {}
def _repository_checkout_path(repository: dict[str, Any]) -> Path | None:
raw_path = repository.get("path")
if not isinstance(raw_path, str) or not raw_path.strip():
return None
return Path(raw_path).expanduser().resolve()
def _repository_graph_node_id(slug: str) -> str:
return f"repo:{slug}"
def _repository_projection_node(repository: dict[str, Any], projection: dict[str, Any]) -> dict[str, Any]:
slug = str(repository.get("slug") or "")
repo_id = _repository_graph_node_id(slug)
repo_name = str(repository.get("name") or slug)
source_links = projection.get("source_links") if isinstance(projection.get("source_links"), list) else []
attributes: dict[str, Any] = {
"repo_family": str(repository.get("repo_family") or ""),
"ownership_repo": str(repository.get("ownership_repo") or ""),
"primary_rail": str(repository.get("primary_rail") or ""),
"supported_rails": _string_list(repository.get("supported_rails")),
"substrate_kind": str(repository.get("substrate_kind") or ""),
"path": str(repository.get("path") or ""),
"declaration_paths": _string_list(repository.get("declaration_paths")),
}
if source_links:
attributes["source_path"] = str(source_links[0].get("path") or "")
attributes["source_links"] = source_links
canon_mapping = node_canon_mapping("Repository")
return {
"id": repo_id,
"kind": "Repository",
"name": repo_name,
"repo": slug,
"domain": "railiance",
"lifecycle": "active",
"canon_category": canon_mapping.category,
"canon_anchor": canon_mapping.canon_anchor,
"mapping_fit": canon_mapping.fit,
"evidence_state": "declared",
"attributes": {key: value for key, value in attributes.items() if value not in ("", [], None)},
}
def _placeholder_repository_node(slug: str) -> dict[str, Any]:
canon_mapping = node_canon_mapping("Repository")
return {
"id": _repository_graph_node_id(slug),
"kind": "Repository",
"name": slug,
"repo": slug,
"domain": "railiance",
"lifecycle": "registered-only",
"canon_category": canon_mapping.category,
"canon_anchor": canon_mapping.canon_anchor,
"mapping_fit": canon_mapping.fit,
"evidence_state": "inferred",
"attributes": {},
}
def _repository_projection_edge(
source_slug: str,
target_slug: str,
edge_type: str,
projection: dict[str, Any],
) -> dict[str, Any] | None:
if not source_slug or not target_slug:
return None
source_links = projection.get("source_links") if isinstance(projection.get("source_links"), list) else []
attributes: dict[str, Any] = {}
if source_links:
attributes["source_path"] = str(source_links[0].get("path") or "")
attributes["source_links"] = source_links
return {
"from": _repository_graph_node_id(source_slug),
"to": _repository_graph_node_id(target_slug),
"type": edge_type,
"evidence_state": "declared",
"attributes": attributes,
}
def _load_yaml_mapping(path: Path) -> dict[str, Any] | None:
if not path.is_file():
return None
try:
data = load_yaml(path)
except Exception:
return None
return data if isinstance(data, dict) else None
def _text_value(payload: dict[str, Any], key: str) -> str:
value = payload.get(key)
return value.strip() if isinstance(value, str) else ""
def _string_list(value: object) -> list[str]:
if not isinstance(value, list):
return []
result: list[str] = []
seen: set[str] = set()
for item in value:
if not isinstance(item, str):
continue
cleaned = item.strip()
if not cleaned or cleaned in seen:
continue
seen.add(cleaned)
result.append(cleaned)
return result
def _relation_id_list(value: object, *, key: str) -> list[str]:
if isinstance(value, list):
result: list[str] = []
seen: set[str] = set()
for item in value:
relation_id = ""
if isinstance(item, str):
relation_id = item.strip()
elif isinstance(item, dict):
raw = item.get(key)
relation_id = raw.strip() if isinstance(raw, str) else ""
if not relation_id or relation_id in seen:
continue
seen.add(relation_id)
result.append(relation_id)
return result
return []
def _utc_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")