Project repo-family relations from local declarations
This commit is contained in:
parent
1d8f59f1de
commit
9af9ee3e72
13 changed files with 671 additions and 28 deletions
|
|
@ -114,6 +114,7 @@ EDGE_TYPE_CANON_MAP: dict[str, CanonEdgeMapping] = {
|
|||
"available_via": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"attributed_to_cost_center": CanonEdgeMapping("governed_by", "model/governance", "gap"),
|
||||
"attributed_to_profit_center": CanonEdgeMapping("governed_by", "model/governance", "gap"),
|
||||
"binds_rapp": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"binds": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"builds_container": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"cataloged_as": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
|
|
@ -135,12 +136,14 @@ EDGE_TYPE_CANON_MAP: dict[str, CanonEdgeMapping] = {
|
|||
"operated_by": CanonEdgeMapping("governed_by", "model/governance", "partial"),
|
||||
"owned_by": CanonEdgeMapping("governed_by", "model/governance", "partial"),
|
||||
"owns_deployment": CanonEdgeMapping("part_of", "model/devsecops", "partial", display_only=True),
|
||||
"hosts_rail": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"provides": CanonEdgeMapping("implements", "model/landscape", "partial"),
|
||||
"provides_utility_to": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"resolves_to": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"routes_to_port": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"routes_to_service": CanonEdgeMapping("flows_to", "model/network", "partial"),
|
||||
"runs_on": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"supports_rail": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"suggests_capability": CanonEdgeMapping("creates_task", "model/task", "partial"),
|
||||
"uses_config": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"uses_interface": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ def _registry_sync(args: argparse.Namespace) -> int:
|
|||
{
|
||||
"slug": repo_slug,
|
||||
"name": args.name or repo_path.name,
|
||||
"path": str(repo_path),
|
||||
"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,
|
||||
|
|
@ -457,6 +458,7 @@ def _registry_sync(args: argparse.Namespace) -> int:
|
|||
"ownership_repo": args.ownership_repo,
|
||||
"primary_rail": args.primary_rail,
|
||||
"supported_rails": args.supported_rail,
|
||||
"declaration_paths": [str(path.resolve()) for path in args.paths],
|
||||
"substrate_kind": args.substrate_kind,
|
||||
},
|
||||
)
|
||||
|
|
@ -834,7 +836,7 @@ def _scan_manifest_repo(
|
|||
repository = _registry_post_checked(
|
||||
registry_url,
|
||||
"/repositories",
|
||||
_manifest_repository_payload(item, repo_path, slug),
|
||||
_manifest_repository_payload(item, repo_path, slug, manifest_dir),
|
||||
)
|
||||
stored = _registry_post_checked(
|
||||
registry_url,
|
||||
|
|
@ -1234,7 +1236,7 @@ def _sync_manifest_repo(registry_url: str, manifest_dir: Path, item: object) ->
|
|||
repository = _registry_post_checked(
|
||||
registry_url,
|
||||
"/repositories",
|
||||
_manifest_repository_payload(item, repo_path, slug),
|
||||
_manifest_repository_payload(item, repo_path, slug, manifest_dir),
|
||||
)
|
||||
result["repository"] = repository
|
||||
except RegistryRequestError as exc:
|
||||
|
|
@ -1595,10 +1597,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]:
|
||||
def _manifest_repository_payload(
|
||||
item: dict[str, Any],
|
||||
repo_path: Path | None,
|
||||
slug: str,
|
||||
manifest_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
declaration_paths = (
|
||||
[str(path) for path in _manifest_paths(item.get("declaration_paths"), manifest_dir)]
|
||||
if manifest_dir is not None
|
||||
else []
|
||||
)
|
||||
return {
|
||||
"slug": slug,
|
||||
"name": item.get("name") or (repo_path.name if repo_path else slug),
|
||||
"path": str(repo_path) if repo_path is not None else None,
|
||||
"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"),
|
||||
|
|
@ -1606,6 +1619,7 @@ def _manifest_repository_payload(item: dict[str, Any], repo_path: Path | None, s
|
|||
"ownership_repo": item.get("ownership_repo"),
|
||||
"primary_rail": item.get("primary_rail"),
|
||||
"supported_rails": item.get("supported_rails"),
|
||||
"declaration_paths": declaration_paths,
|
||||
"substrate_kind": item.get("substrate_kind"),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,12 +61,14 @@ _LAYER_COLORS = {
|
|||
|
||||
_EDGE_STRENGTH = {
|
||||
"built_from": "medium",
|
||||
"binds_rapp": "strong",
|
||||
"depends_on": "medium",
|
||||
"deploys": "strong",
|
||||
"evidenced_by": "medium",
|
||||
"exposes": "strong",
|
||||
"flows_to": "medium",
|
||||
"governed_by": "medium",
|
||||
"hosts_rail": "strong",
|
||||
"implements": "medium",
|
||||
"observed_by": "medium",
|
||||
"part_of": "weak",
|
||||
|
|
@ -86,6 +88,7 @@ _EDGE_STRENGTH = {
|
|||
"resolves_to": "medium",
|
||||
"runs_on": "strong",
|
||||
"owns_deployment": "weak",
|
||||
"supports_rail": "medium",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -325,6 +328,7 @@ def fabric_graph_explorer_payload(
|
|||
source_repo_slugs = {
|
||||
str(node.get("repo", "")).strip()
|
||||
for node in source_nodes
|
||||
if str(node.get("kind", "")) != "Repository"
|
||||
if str(node.get("repo", "")).strip()
|
||||
}
|
||||
registered_repo_slugs = {
|
||||
|
|
@ -347,6 +351,12 @@ def fabric_graph_explorer_payload(
|
|||
and str(node.get("id", ""))
|
||||
and str(node.get("repo", "")).strip()
|
||||
}
|
||||
repository_source_references = {
|
||||
str(node.get("repo", "")).strip(): _source_references(node)
|
||||
for node in source_nodes
|
||||
if str(node.get("kind", "")) == "Repository"
|
||||
and str(node.get("repo", "")).strip()
|
||||
}
|
||||
for slug in sorted(repo_slugs):
|
||||
repo = repository_index.get(slug, {})
|
||||
has_snapshot = slug in snapshot_repo_slugs or slug in source_repo_slugs
|
||||
|
|
@ -383,7 +393,7 @@ def fabric_graph_explorer_payload(
|
|||
"displayState": "show",
|
||||
"visibilitySource": "default",
|
||||
"visibilityReason": "default",
|
||||
"sourceReferences": [],
|
||||
"sourceReferences": repository_source_references.get(slug, []),
|
||||
"deepLinks": _repository_links(repo),
|
||||
},
|
||||
"classes": "repository accepted" if has_snapshot else "repository candidate unresolved",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue