feat: validate financial fabric graph exports

This commit is contained in:
2026-05-24 00:57:14 +02:00
parent 3318d2c1b9
commit 3a47a92729
5 changed files with 612 additions and 2 deletions

View File

@@ -261,6 +261,48 @@ def test_graph_export_validation_rejects_unflagged_display_edges() -> None:
raise AssertionError("expected RegistryError for unflagged display-only edge")
def test_financial_graph_export_requires_resolvable_owner() -> None:
graph = _financial_graph()
del graph["nodes"][0]["ownership"]
try:
validate_graph_export(graph)
except RegistryError as exc:
assert "ownership must be an object" in exc.message
else:
raise AssertionError("expected RegistryError for accepted node without ownership")
def test_registry_accepts_financial_graph_and_materializes_vnext_fields(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository({"slug": "state-hub", "name": "State Hub"})
snapshot = store.add_snapshot(
"state-hub",
{
"commit": "financial-vnext",
"generated_at": "2026-05-24T00:00:00Z",
"graph": _financial_graph(),
},
)
graph = snapshot["graph"]
edge = graph["edges"][0]
assert graph["apiVersion"] == "railiance.fabric/v1alpha2"
assert graph["schema_version"] == "financial-fabric-v1"
assert graph["nodes"][0]["evidence"]["review_state"] == "accepted"
assert edge["relationship_category"] == "utility"
assert edge["boundary"]["crosses_fabric_boundary"] is False
assert edge["boundary"]["crosses_subfabric_boundary"] is True
combined = store.combined_graph()
assert combined["apiVersion"] == "railiance.fabric/v1alpha2"
assert combined["actors"][0]["id"] == "actor.coulomb.tenant"
assert combined["fabrics"][1]["id"] == "subfabric.railiance.tenant.coulomb"
assert combined["edges"][0]["relationship_category"] == "utility"
def test_registry_reset_archive_and_guarded_reset(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
@@ -426,6 +468,119 @@ def _post_json(url: str, payload: dict) -> dict:
return json.loads(response.read())
def _financial_graph() -> dict:
return {
"apiVersion": "railiance.fabric/v1alpha2",
"kind": "FabricGraphExport",
"schema_version": "financial-fabric-v1",
"generated_at": "2026-05-24T00:00:00Z",
"netkingdom": {
"id": "railiance.netkingdom",
"name": "Railiance Netkingdom",
"king_actor_id": "actor.railiance.king",
},
"actors": [
{"id": "actor.railiance.king", "kind": "FabricActor", "role": "king", "name": "Railiance King"},
{"id": "actor.railiance.primary-lord", "kind": "FabricActor", "role": "lord", "name": "Railiance Lord"},
{"id": "actor.coulomb.tenant", "kind": "FabricActor", "role": "tenant", "name": "Coulomb Tenant"},
],
"fabrics": [
{
"id": "fabric.railiance.primary",
"kind": "Fabric",
"name": "Railiance Primary Fabric",
"netkingdom_id": "railiance.netkingdom",
"lord_actor_id": "actor.railiance.primary-lord",
"status": "active",
},
{
"id": "subfabric.railiance.tenant.coulomb",
"kind": "Subfabric",
"name": "Coulomb Tenant Subfabric",
"netkingdom_id": "railiance.netkingdom",
"parent_fabric_id": "fabric.railiance.primary",
"tenant_actor_id": "actor.coulomb.tenant",
"status": "planned",
},
],
"nodes": [
{
"id": "state-hub.http",
"kind": "UtilityInterface",
"name": "State Hub HTTP API",
"repo": "state-hub",
"domain": "custodian",
"lifecycle": "active",
"containment": {
"netkingdom_id": "railiance.netkingdom",
"fabric_id": "fabric.railiance.primary",
"subfabric_id": None,
"environment": "local",
},
"ownership": {
"owner_actor_id": "actor.railiance.primary-lord",
"owner_role": "lord",
"resolution": "inherited",
"inherited_from": "fabric.railiance.primary",
},
},
{
"id": "coulomb.automation-client",
"kind": "Service",
"name": "Coulomb Automation Client",
"repo": "coulomb-automation",
"domain": "railiance",
"lifecycle": "planned",
"containment": {
"netkingdom_id": "railiance.netkingdom",
"fabric_id": "fabric.railiance.primary",
"subfabric_id": "subfabric.railiance.tenant.coulomb",
"environment": "local",
},
"ownership": {
"owner_actor_id": "actor.coulomb.tenant",
"owner_role": "tenant",
"resolution": "explicit",
},
"accounting": {
"cost_center_id": "cc.coulomb.automation",
"allocation_model": "direct",
},
},
],
"edges": [
{
"id": "utility:state-hub-http:coulomb-client",
"from": "state-hub.http",
"to": "coulomb.automation-client",
"type": "provides_utility_to",
"provider": {
"owner_actor_id": "actor.railiance.primary-lord",
"fabric_id": "fabric.railiance.primary",
"subfabric_id": None,
},
"consumer": {
"owner_actor_id": "actor.coulomb.tenant",
"fabric_id": "fabric.railiance.primary",
"subfabric_id": "subfabric.railiance.tenant.coulomb",
},
"utility": {
"utility_type": "coordination_api",
"contract_id": "state-hub.http",
"payment_schema_id": "payment.internal-tenant-access",
"metering_basis": "unknown",
"business_model": "tenant_utility",
},
"accounting": {
"provider_profit_center_id": "pc.tenant-utilities",
"consumer_cost_center_id": "cc.coulomb.automation",
"allocation_model": "usage_weighted",
},
}
],
}
def _cyclonedx_bom() -> dict:
return {
"bomFormat": "CycloneDX",