generated from coulomb/repo-seed
refactoring for canon conformity
This commit is contained in:
198
railiance_fabric/canon.py
Normal file
198
railiance_fabric/canon.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
CANONICAL_NODE_CATEGORIES = (
|
||||
"source-repository",
|
||||
"software-system",
|
||||
"service",
|
||||
"endpoint",
|
||||
"deployment",
|
||||
"runtime-resource",
|
||||
"datastore",
|
||||
"flow",
|
||||
"policy",
|
||||
"control",
|
||||
"evidence",
|
||||
"task",
|
||||
"consumer-purpose",
|
||||
"telemetry-signal",
|
||||
)
|
||||
|
||||
CANONICAL_EDGE_TYPES = (
|
||||
"built_from",
|
||||
"implements",
|
||||
"exposes",
|
||||
"depends_on",
|
||||
"deploys",
|
||||
"flows_to",
|
||||
"governed_by",
|
||||
"evidenced_by",
|
||||
"observed_by",
|
||||
"part_of",
|
||||
"reads_or_writes",
|
||||
"creates_task",
|
||||
)
|
||||
|
||||
DISPLAY_ONLY_EDGE_TYPES = (
|
||||
"collapsed_into",
|
||||
"declares",
|
||||
"grouped_with",
|
||||
"highlight_path",
|
||||
"near",
|
||||
"owns_deployment",
|
||||
"same_color_group",
|
||||
)
|
||||
|
||||
EVIDENCE_STATES = ("observed", "declared", "inferred", "proposed", "gap")
|
||||
MAPPING_FITS = ("direct", "partial", "conflict", "gap", "unknown")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanonNodeMapping:
|
||||
category: str
|
||||
canon_anchor: str
|
||||
fit: str
|
||||
notes: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanonEdgeMapping:
|
||||
canonical_type: str
|
||||
canon_anchor: str
|
||||
fit: str
|
||||
display_only: bool = False
|
||||
notes: str = ""
|
||||
|
||||
|
||||
UNKNOWN_NODE_MAPPING = CanonNodeMapping(
|
||||
category="unknown",
|
||||
canon_anchor="",
|
||||
fit="gap",
|
||||
notes="No canon mapping has been selected for this Fabric node kind yet.",
|
||||
)
|
||||
|
||||
UNKNOWN_EDGE_MAPPING = CanonEdgeMapping(
|
||||
canonical_type="",
|
||||
canon_anchor="",
|
||||
fit="gap",
|
||||
notes="No canon mapping has been selected for this Fabric edge type yet.",
|
||||
)
|
||||
|
||||
NODE_KIND_CANON_MAP: dict[str, CanonNodeMapping] = {
|
||||
"ApplicationEndpoint": CanonNodeMapping("endpoint", "model/network", "direct"),
|
||||
"BindingAssertion": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"CapabilityDeclaration": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"ContainerBuild": CanonNodeMapping("deployment", "model/devsecops", "partial"),
|
||||
"DependencyDeclaration": CanonNodeMapping("service", "model/landscape", "gap"),
|
||||
"DeploymentService": CanonNodeMapping("deployment", "model/devsecops", "direct"),
|
||||
"DomainName": CanonNodeMapping("endpoint", "model/network", "partial"),
|
||||
"ExternalLibrary": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"InterfaceDeclaration": CanonNodeMapping("endpoint", "model/network", "partial"),
|
||||
"Library": CanonNodeMapping("software-system", "model/landscape", "partial"),
|
||||
"Lockfile": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"NetworkPort": CanonNodeMapping("endpoint", "model/network", "direct"),
|
||||
"Repository": CanonNodeMapping("source-repository", "model/devsecops", "direct"),
|
||||
"RuntimeService": CanonNodeMapping("runtime-resource", "model/landscape", "direct"),
|
||||
"ScoreWorkload": CanonNodeMapping("deployment", "model/devsecops", "direct"),
|
||||
"Server": CanonNodeMapping("runtime-resource", "model/landscape", "partial"),
|
||||
"ServiceConfig": CanonNodeMapping("evidence", "model/observability", "partial"),
|
||||
"ServiceDeclaration": CanonNodeMapping("service", "model/landscape", "direct"),
|
||||
}
|
||||
|
||||
EDGE_TYPE_CANON_MAP: dict[str, CanonEdgeMapping] = {
|
||||
"available_via": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"binds": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"builds_container": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"cataloged_as": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"consumes": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"declares": CanonEdgeMapping("part_of", "model/devsecops", "partial", display_only=True),
|
||||
"declares_package": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"defines_deployment": CanonEdgeMapping("built_from", "model/devsecops", "partial"),
|
||||
"defines_runtime_object": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"defines_workload": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"deployed_as": CanonEdgeMapping("deploys", "model/devsecops", "partial"),
|
||||
"depends_on_library": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"documents_interface": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"exposes": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"exposes_port": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"listens_on": CanonEdgeMapping("exposes", "model/network", "direct"),
|
||||
"names_endpoint": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"opens_port": CanonEdgeMapping("exposes", "model/network", "partial"),
|
||||
"owns_deployment": CanonEdgeMapping("part_of", "model/devsecops", "partial", display_only=True),
|
||||
"provides": CanonEdgeMapping("implements", "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"),
|
||||
"suggests_capability": CanonEdgeMapping("creates_task", "model/task", "partial"),
|
||||
"uses_config": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
"uses_interface": CanonEdgeMapping("depends_on", "model/landscape", "partial"),
|
||||
"uses_lockfile": CanonEdgeMapping("evidenced_by", "model/observability", "partial"),
|
||||
}
|
||||
|
||||
|
||||
def node_canon_mapping(kind: str) -> CanonNodeMapping:
|
||||
if kind in NODE_KIND_CANON_MAP:
|
||||
return NODE_KIND_CANON_MAP[kind]
|
||||
if kind.startswith("Kubernetes"):
|
||||
return CanonNodeMapping("runtime-resource", "model/landscape", "direct")
|
||||
return UNKNOWN_NODE_MAPPING
|
||||
|
||||
|
||||
def edge_canon_mapping(edge_type: str) -> CanonEdgeMapping:
|
||||
normalized = str(edge_type or "").strip()
|
||||
if normalized.startswith("binds:"):
|
||||
return EDGE_TYPE_CANON_MAP["binds"]
|
||||
if normalized in EDGE_TYPE_CANON_MAP:
|
||||
return EDGE_TYPE_CANON_MAP[normalized]
|
||||
if normalized in CANONICAL_EDGE_TYPES:
|
||||
return CanonEdgeMapping(normalized, _anchor_for_canonical_edge(normalized), "direct")
|
||||
if normalized in DISPLAY_ONLY_EDGE_TYPES:
|
||||
return CanonEdgeMapping("", "", "gap", display_only=True)
|
||||
return UNKNOWN_EDGE_MAPPING
|
||||
|
||||
|
||||
def evidence_state_for(
|
||||
*,
|
||||
origin: str = "",
|
||||
source_kind: str = "",
|
||||
review_state: str = "",
|
||||
confidence: float | None = None,
|
||||
) -> str:
|
||||
if review_state == "rejected":
|
||||
return "gap"
|
||||
if origin == "llm":
|
||||
return "proposed"
|
||||
if confidence is not None and confidence < 0.5:
|
||||
return "inferred"
|
||||
if source_kind in {"package_registry", "container_registry", "service_catalog", "fabric_registry"}:
|
||||
return "observed"
|
||||
if source_kind in {"llm"}:
|
||||
return "proposed"
|
||||
if not source_kind and origin == "deterministic":
|
||||
return "inferred"
|
||||
return "declared"
|
||||
|
||||
|
||||
def source_kind_from_anchor(source_anchor: dict[str, Any]) -> str:
|
||||
return str(source_anchor.get("source_kind") or "")
|
||||
|
||||
|
||||
def _anchor_for_canonical_edge(edge_type: str) -> str:
|
||||
return {
|
||||
"built_from": "model/devsecops",
|
||||
"implements": "model/security",
|
||||
"exposes": "model/network",
|
||||
"depends_on": "model/landscape",
|
||||
"deploys": "model/devsecops",
|
||||
"flows_to": "model/network",
|
||||
"governed_by": "model/governance",
|
||||
"evidenced_by": "model/observability",
|
||||
"observed_by": "model/observability",
|
||||
"part_of": "model/landscape",
|
||||
"reads_or_writes": "model/data",
|
||||
"creates_task": "model/task",
|
||||
}.get(edge_type, "")
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .loader import load_declarations
|
||||
from .canon import edge_canon_mapping, node_canon_mapping
|
||||
from .model import Declaration
|
||||
|
||||
|
||||
@@ -186,6 +187,7 @@ class FabricGraph:
|
||||
edges: list[dict[str, str]] = []
|
||||
|
||||
for declaration in sorted(self.declarations, key=lambda item: (item.kind, item.id)):
|
||||
canon_mapping = node_canon_mapping(declaration.kind)
|
||||
nodes.append(
|
||||
{
|
||||
"id": declaration.id,
|
||||
@@ -194,35 +196,39 @@ class FabricGraph:
|
||||
"repo": declaration.metadata.get("repo", ""),
|
||||
"domain": declaration.metadata.get("domain", ""),
|
||||
"lifecycle": declaration.spec.get("lifecycle", ""),
|
||||
"canon_category": canon_mapping.category,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"evidence_state": "declared",
|
||||
"attributes": _export_attributes(declaration),
|
||||
}
|
||||
)
|
||||
|
||||
for service in self.services.values():
|
||||
for capability_id in service.spec.get("provides_capabilities", []):
|
||||
edges.append({"from": service.id, "to": capability_id, "type": "provides"})
|
||||
edges.append(_export_edge(service.id, capability_id, "provides"))
|
||||
for interface_id in service.spec.get("exposes_interfaces", []):
|
||||
edges.append({"from": service.id, "to": interface_id, "type": "exposes"})
|
||||
edges.append(_export_edge(service.id, interface_id, "exposes"))
|
||||
|
||||
for capability in self.capabilities.values():
|
||||
for interface_id in capability.spec.get("interface_ids", []):
|
||||
edges.append({"from": capability.id, "to": interface_id, "type": "available_via"})
|
||||
edges.append(_export_edge(capability.id, interface_id, "available_via"))
|
||||
|
||||
for dependency in self.dependencies.values():
|
||||
consumer = str(dependency.spec.get("consumer_service_id", ""))
|
||||
if consumer:
|
||||
edges.append({"from": consumer, "to": dependency.id, "type": "consumes"})
|
||||
edges.append(_export_edge(consumer, dependency.id, "consumes"))
|
||||
for binding in self.bindings_by_dependency.get(dependency.id, []):
|
||||
edges.append(
|
||||
{
|
||||
"from": dependency.id,
|
||||
"to": str(binding.spec.get("provider_capability_id", "")),
|
||||
"type": f"binds:{binding.spec.get('status', '')}",
|
||||
}
|
||||
_export_edge(
|
||||
dependency.id,
|
||||
str(binding.spec.get("provider_capability_id", "")),
|
||||
f"binds:{binding.spec.get('status', '')}",
|
||||
)
|
||||
)
|
||||
interface_id = str(binding.spec.get("provider_interface_id", ""))
|
||||
if interface_id:
|
||||
edges.append({"from": dependency.id, "to": interface_id, "type": "uses_interface"})
|
||||
edges.append(_export_edge(dependency.id, interface_id, "uses_interface"))
|
||||
|
||||
return {
|
||||
"apiVersion": "railiance.fabric/v1alpha1",
|
||||
@@ -265,6 +271,20 @@ def _escape_mermaid(value: str) -> str:
|
||||
return value.replace('"', '\\"')
|
||||
|
||||
|
||||
def _export_edge(source: str, target: str, edge_type: str) -> dict[str, Any]:
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
return {
|
||||
"from": source,
|
||||
"to": target,
|
||||
"type": edge_type,
|
||||
"canonical_type": canon_mapping.canonical_type,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"display_only": canon_mapping.display_only,
|
||||
"evidence_state": "declared",
|
||||
}
|
||||
|
||||
|
||||
def _export_attributes(declaration: Declaration) -> dict[str, Any]:
|
||||
spec = declaration.spec
|
||||
base = _base_export_attributes(declaration)
|
||||
|
||||
@@ -6,6 +6,8 @@ from re import sub
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .canon import edge_canon_mapping
|
||||
|
||||
|
||||
DISPLAY_STATES = ("show", "blur", "hide", "highlight", "remove")
|
||||
LAYER_ORDER = (
|
||||
@@ -57,8 +59,18 @@ _LAYER_COLORS = {
|
||||
}
|
||||
|
||||
_EDGE_STRENGTH = {
|
||||
"provides": "strong",
|
||||
"built_from": "medium",
|
||||
"depends_on": "medium",
|
||||
"deploys": "strong",
|
||||
"evidenced_by": "medium",
|
||||
"exposes": "strong",
|
||||
"flows_to": "medium",
|
||||
"governed_by": "medium",
|
||||
"implements": "medium",
|
||||
"observed_by": "medium",
|
||||
"part_of": "weak",
|
||||
"reads_or_writes": "medium",
|
||||
"provides": "strong",
|
||||
"available_via": "medium",
|
||||
"consumes": "medium",
|
||||
"uses_interface": "medium",
|
||||
@@ -139,6 +151,9 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
"kind",
|
||||
"layer",
|
||||
"edgeType",
|
||||
"canonicalType",
|
||||
"canonCategory",
|
||||
"evidenceState",
|
||||
],
|
||||
"filter": {
|
||||
"actions": list(DISPLAY_STATES),
|
||||
@@ -153,6 +168,10 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
{"id": "reviewState", "label": "Review State", "type": "string"},
|
||||
{"id": "unresolved", "label": "Unresolved", "type": "boolean"},
|
||||
{"id": "edgeType", "label": "Edge Type", "type": "string"},
|
||||
{"id": "canonicalType", "label": "Canonical Edge", "type": "string"},
|
||||
{"id": "canonCategory", "label": "Canon Category", "type": "string"},
|
||||
{"id": "evidenceState", "label": "Evidence State", "type": "string"},
|
||||
{"id": "displayOnly", "label": "Display Only", "type": "boolean"},
|
||||
{"id": "strength", "label": "Strength", "type": "string"},
|
||||
{"id": "sameLayer", "label": "Same Layer", "type": "boolean"},
|
||||
{"id": "text", "label": "Text", "type": "string"},
|
||||
@@ -174,6 +193,8 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
"repo",
|
||||
"domain",
|
||||
"lifecycle",
|
||||
"canonCategory",
|
||||
"evidenceState",
|
||||
"unresolved",
|
||||
"description",
|
||||
"sourceReferences",
|
||||
@@ -181,6 +202,9 @@ def fabric_graph_explorer_manifest(base_url: str = "") -> dict[str, Any]:
|
||||
],
|
||||
"edge_fields": [
|
||||
"edgeType",
|
||||
"canonicalType",
|
||||
"displayOnly",
|
||||
"evidenceState",
|
||||
"strength",
|
||||
"source",
|
||||
"target",
|
||||
@@ -329,6 +353,14 @@ def fabric_graph_explorer_payload(
|
||||
"repo": str(node.get("repo", "")),
|
||||
"domain": str(node.get("domain", "")),
|
||||
"lifecycle": str(node.get("lifecycle", "")),
|
||||
"canonCategory": str(
|
||||
node.get("canon_category") or attributes.get("canon_category") or ""
|
||||
),
|
||||
"canonAnchor": str(node.get("canon_anchor") or attributes.get("canon_anchor") or ""),
|
||||
"mappingFit": str(node.get("mapping_fit") or attributes.get("mapping_fit") or ""),
|
||||
"evidenceState": str(
|
||||
node.get("evidence_state") or attributes.get("evidence_state") or ""
|
||||
),
|
||||
"reviewState": review_state,
|
||||
"freshnessState": "current",
|
||||
"unresolved": is_unresolved,
|
||||
@@ -378,7 +410,17 @@ def fabric_graph_explorer_payload(
|
||||
edge_type = _presentation_edge_type(str(edge.get("type", "")), source, target, node_kinds)
|
||||
if not source or not target:
|
||||
continue
|
||||
elements.append(_edge_element(edge_index, source, target, edge_type, node_layers, node_repos))
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
source,
|
||||
target,
|
||||
edge_type,
|
||||
node_layers,
|
||||
node_repos,
|
||||
**_edge_metadata(edge, edge_type),
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
for slug in sorted(repo_slugs):
|
||||
@@ -389,7 +431,17 @@ def fabric_graph_explorer_payload(
|
||||
continue
|
||||
if str(node.get("repo", "")) != slug or not node_id:
|
||||
continue
|
||||
elements.append(_edge_element(edge_index, repo_id, node_id, "declares", node_layers, node_repos))
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
repo_id,
|
||||
node_id,
|
||||
"declares",
|
||||
node_layers,
|
||||
node_repos,
|
||||
display_only=True,
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
visible_nodes = [element for element in elements if "source" not in element["data"]]
|
||||
@@ -468,6 +520,17 @@ def _presentation_edge_type(edge_type: str, source: str, target: str, node_kinds
|
||||
return edge_type
|
||||
|
||||
|
||||
def _edge_metadata(edge: dict[str, Any], edge_type: str) -> dict[str, Any]:
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
return {
|
||||
"canonical_type": str(edge.get("canonical_type") or canon_mapping.canonical_type),
|
||||
"canon_anchor": str(edge.get("canon_anchor") or canon_mapping.canon_anchor),
|
||||
"mapping_fit": str(edge.get("mapping_fit") or canon_mapping.fit),
|
||||
"display_only": bool(edge.get("display_only", canon_mapping.display_only)),
|
||||
"evidence_state": str(edge.get("evidence_state") or "declared"),
|
||||
}
|
||||
|
||||
|
||||
def _edge_strength(edge_type: str) -> str:
|
||||
if edge_type.startswith("binds:"):
|
||||
status = edge_type.split(":", 1)[1]
|
||||
@@ -526,7 +589,7 @@ def _append_infrastructure_elements(
|
||||
server_ids_by_host, port_ids_by_endpoint = _runtime_node_indexes(source_nodes)
|
||||
generated_edge_keys: set[tuple[str, str, str]] = set()
|
||||
|
||||
def append_edge(source: str, target: str, edge_type: str) -> None:
|
||||
def append_edge(source: str, target: str, edge_type: str, *, display_only: bool = True) -> None:
|
||||
nonlocal edge_index
|
||||
if not source or not target:
|
||||
return
|
||||
@@ -534,7 +597,17 @@ def _append_infrastructure_elements(
|
||||
if key in generated_edge_keys:
|
||||
return
|
||||
generated_edge_keys.add(key)
|
||||
elements.append(_edge_element(edge_index, source, target, edge_type, node_layers, node_repos))
|
||||
elements.append(
|
||||
_edge_element(
|
||||
edge_index,
|
||||
source,
|
||||
target,
|
||||
edge_type,
|
||||
node_layers,
|
||||
node_repos,
|
||||
display_only=display_only,
|
||||
)
|
||||
)
|
||||
edge_index += 1
|
||||
|
||||
service_nodes = sorted(
|
||||
@@ -816,12 +889,24 @@ def _edge_element(
|
||||
edge_type: str,
|
||||
node_layers: dict[str, str],
|
||||
node_repos: dict[str, str],
|
||||
*,
|
||||
canonical_type: str = "",
|
||||
canon_anchor: str = "",
|
||||
mapping_fit: str = "",
|
||||
display_only: bool = False,
|
||||
evidence_state: str = "",
|
||||
) -> dict[str, Any]:
|
||||
source_layer = node_layers.get(source, "unknown")
|
||||
target_layer = node_layers.get(target, "unknown")
|
||||
source_repo = node_repos.get(source, "")
|
||||
target_repo = node_repos.get(target, "")
|
||||
same_repo = bool(source_repo and source_repo == target_repo)
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
canonical_type = canonical_type or canon_mapping.canonical_type
|
||||
canon_anchor = canon_anchor or canon_mapping.canon_anchor
|
||||
mapping_fit = mapping_fit or canon_mapping.fit
|
||||
display_only = display_only or canon_mapping.display_only
|
||||
evidence_state = evidence_state or "declared"
|
||||
strength = _edge_strength(edge_type)
|
||||
layout = _layout_hints(edge_type, source_layer, target_layer, same_repo)
|
||||
edge_id = f"edge:{edge_index}:{source}:{edge_type}:{target}"
|
||||
@@ -840,6 +925,11 @@ def _edge_element(
|
||||
"targetRepo": target_repo,
|
||||
"edgeType": edge_type,
|
||||
"dependencyType": edge_type,
|
||||
"canonicalType": canonical_type,
|
||||
"canonAnchor": canon_anchor,
|
||||
"mappingFit": mapping_fit,
|
||||
"displayOnly": display_only,
|
||||
"evidenceState": evidence_state,
|
||||
"strength": strength,
|
||||
"edgeWidth": _edge_width(strength),
|
||||
"sameLayer": source_layer == target_layer,
|
||||
@@ -860,6 +950,7 @@ def _edge_element(
|
||||
edge_type.replace(":", "-"),
|
||||
strength,
|
||||
str(layout["affinity"]),
|
||||
"display-only" if display_only else "canonical",
|
||||
"same-layer" if source_layer == target_layer else "",
|
||||
"same-repo" if same_repo else "",
|
||||
)
|
||||
|
||||
@@ -1226,7 +1226,7 @@ def graph_explorer_page() -> str:
|
||||
detailTitle.textContent = data.name || data.label || data.id;
|
||||
detailSummary.textContent = data.description || data.id;
|
||||
const nodeType = data.layer ? nodeTypeLabels[data.layer] || humanize(data.layer) : "";
|
||||
detailPills.innerHTML = [data.kind, nodeType, data.repo, data.reviewState, data.displayState]
|
||||
detailPills.innerHTML = [data.kind, nodeType, data.canonCategory || data.canonicalType, data.evidenceState, data.repo, data.reviewState, data.displayState]
|
||||
.map((value) => value ? `<span class="pill">${escapeHtml(value)}</span>` : "")
|
||||
.join("");
|
||||
const links = data.deepLinks || {};
|
||||
@@ -1236,6 +1236,10 @@ def graph_explorer_page() -> str:
|
||||
["source", data.source],
|
||||
["target", data.target],
|
||||
["edge", data.edgeType],
|
||||
["canonical", data.canonicalType || data.canonCategory],
|
||||
["evidence", data.evidenceState],
|
||||
["mapping", data.mappingFit],
|
||||
["display only", data.displayOnly === true ? "yes" : ""],
|
||||
["strength", data.strength],
|
||||
...Object.entries(links),
|
||||
...refs.map((ref) => [ref.label || ref.kind || "source", ref.path || ref.url || ref.ref || ""])
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .canon import edge_canon_mapping, node_canon_mapping
|
||||
from .loader import repo_root
|
||||
from .schema_validation import draft202012_validator
|
||||
|
||||
@@ -416,13 +417,7 @@ class RegistryStore:
|
||||
nodes[str(node.get("id", ""))] = node
|
||||
for edge in graph.get("edges", []):
|
||||
if isinstance(edge, dict):
|
||||
edges.append(
|
||||
{
|
||||
"from": str(edge.get("from", "")),
|
||||
"to": str(edge.get("to", "")),
|
||||
"type": str(edge.get("type", "")),
|
||||
}
|
||||
)
|
||||
edges.append(_edge_with_canon_metadata(edge))
|
||||
return {
|
||||
"apiVersion": "railiance.fabric/v1alpha1",
|
||||
"kind": "FabricGraphExport",
|
||||
@@ -1485,7 +1480,18 @@ def _project_discovery_snapshot(
|
||||
target = key_to_graph_id.get(str(candidate.get("target_key") or ""))
|
||||
if not source or not target:
|
||||
continue
|
||||
edge = {"from": source, "to": target, "type": str(candidate.get("edge_type") or "")}
|
||||
edge = _edge_with_canon_metadata(
|
||||
{
|
||||
"from": source,
|
||||
"to": target,
|
||||
"type": str(candidate.get("edge_type") or ""),
|
||||
"canonical_type": candidate.get("canonical_type", ""),
|
||||
"canon_anchor": candidate.get("canon_anchor", ""),
|
||||
"mapping_fit": candidate.get("mapping_fit", ""),
|
||||
"display_only": candidate.get("display_only", False),
|
||||
"evidence_state": candidate.get("evidence_state", ""),
|
||||
}
|
||||
)
|
||||
edge_key = _edge_key(edge)
|
||||
if edge["type"] and edge_key not in existing_edges:
|
||||
existing_edges.add(edge_key)
|
||||
@@ -1496,6 +1502,7 @@ def _project_discovery_snapshot(
|
||||
|
||||
def _project_candidate_node(candidate: dict[str, Any], graph_id: str) -> dict[str, Any]:
|
||||
attributes = candidate.get("attributes") if isinstance(candidate.get("attributes"), dict) else {}
|
||||
canon_mapping = node_canon_mapping(str(candidate.get("kind") or "DiscoveredEntity"))
|
||||
return {
|
||||
"id": graph_id,
|
||||
"kind": str(candidate.get("kind") or "DiscoveredEntity"),
|
||||
@@ -1503,6 +1510,10 @@ def _project_candidate_node(candidate: dict[str, Any], graph_id: str) -> dict[st
|
||||
"repo": str(candidate.get("repo") or ""),
|
||||
"domain": str(candidate.get("domain") or ""),
|
||||
"lifecycle": str(candidate.get("lifecycle") or "active"),
|
||||
"canon_category": str(candidate.get("canon_category") or canon_mapping.category),
|
||||
"canon_anchor": str(candidate.get("canon_anchor") or canon_mapping.canon_anchor),
|
||||
"mapping_fit": str(candidate.get("mapping_fit") or canon_mapping.fit),
|
||||
"evidence_state": str(candidate.get("evidence_state") or "declared"),
|
||||
"attributes": {
|
||||
**attributes,
|
||||
"discovery_stable_key": candidate.get("stable_key"),
|
||||
@@ -1604,6 +1615,22 @@ def _edge_key(edge: dict[str, Any]) -> tuple[str, str, str]:
|
||||
return (str(edge.get("from", "")), str(edge.get("to", "")), str(edge.get("type", "")))
|
||||
|
||||
|
||||
def _edge_with_canon_metadata(edge: dict[str, Any]) -> dict[str, Any]:
|
||||
edge_type = str(edge.get("type") or "")
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
return {
|
||||
"from": str(edge.get("from", "")),
|
||||
"to": str(edge.get("to", "")),
|
||||
"type": edge_type,
|
||||
"canonical_type": str(edge.get("canonical_type") or canon_mapping.canonical_type),
|
||||
"canon_anchor": str(edge.get("canon_anchor") or canon_mapping.canon_anchor),
|
||||
"mapping_fit": str(edge.get("mapping_fit") or canon_mapping.fit),
|
||||
"display_only": bool(edge.get("display_only", canon_mapping.display_only)),
|
||||
"evidence_state": str(edge.get("evidence_state") or "declared"),
|
||||
"attributes": edge.get("attributes", {}) if isinstance(edge.get("attributes"), dict) else {},
|
||||
}
|
||||
|
||||
|
||||
def _stable_json(value: Any) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
|
||||
from .canon import edge_canon_mapping, evidence_state_for, node_canon_mapping, source_kind_from_anchor
|
||||
from .connectors import ConnectorConfig, apply_connectors
|
||||
from .discovery import (
|
||||
attribute_stable_key,
|
||||
@@ -158,12 +159,24 @@ class CandidateAccumulator:
|
||||
attributes: dict[str, object] | None = None,
|
||||
lifecycle: str | None = None,
|
||||
domain: str | None = None,
|
||||
evidence_state: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
canon_mapping = node_canon_mapping(kind)
|
||||
candidate: dict[str, object] = {
|
||||
"stable_key": stable_key,
|
||||
"kind": kind,
|
||||
"label": label,
|
||||
"repo": self.repo_slug,
|
||||
"canon_category": canon_mapping.category,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"evidence_state": evidence_state
|
||||
or evidence_state_for(
|
||||
origin=origin,
|
||||
source_kind=source_kind_from_anchor(source_anchor),
|
||||
review_state=review_state,
|
||||
confidence=confidence,
|
||||
),
|
||||
"origin": origin,
|
||||
"review_state": review_state,
|
||||
"status": status,
|
||||
@@ -203,6 +216,8 @@ class CandidateAccumulator:
|
||||
confidence: float = 0.8,
|
||||
aliases: Iterable[str] = (),
|
||||
attributes: dict[str, object] | None = None,
|
||||
display_only: bool | None = None,
|
||||
evidence_state: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
stable_key = relationship_stable_key(
|
||||
source_key,
|
||||
@@ -210,9 +225,21 @@ class CandidateAccumulator:
|
||||
target_key,
|
||||
evidence_scope=replacement_scope,
|
||||
)
|
||||
canon_mapping = edge_canon_mapping(edge_type)
|
||||
candidate: dict[str, object] = {
|
||||
"stable_key": stable_key,
|
||||
"edge_type": edge_type,
|
||||
"canonical_type": canon_mapping.canonical_type,
|
||||
"canon_anchor": canon_mapping.canon_anchor,
|
||||
"mapping_fit": canon_mapping.fit,
|
||||
"display_only": canon_mapping.display_only if display_only is None else display_only,
|
||||
"evidence_state": evidence_state
|
||||
or evidence_state_for(
|
||||
origin=origin,
|
||||
source_kind=source_kind_from_anchor(source_anchor),
|
||||
review_state=review_state,
|
||||
confidence=confidence,
|
||||
),
|
||||
"source_key": source_key,
|
||||
"target_key": target_key,
|
||||
"origin": origin,
|
||||
|
||||
Reference in New Issue
Block a user