railiance-fabric/tests/test_registry.py

871 lines
33 KiB
Python
Raw Permalink Normal View History

2026-05-17 20:06:44 +02:00
from __future__ import annotations
import json
import threading
import urllib.request
from http.server import ThreadingHTTPServer
from pathlib import Path
from railiance_fabric.cli import main as cli_main
2026-05-17 20:06:44 +02:00
from railiance_fabric.graph import build_graph
from railiance_fabric.financial import materialize_financial_graph_export
from railiance_fabric.financial_baseline import financial_export_from_legacy
2026-05-17 20:06:44 +02:00
from railiance_fabric.registry import (
RESET_CONFIRMATION_TOKEN,
RegistryError,
2026-05-17 20:06:44 +02:00
RegistryStore,
2026-05-17 20:31:12 +02:00
backstage_projection,
2026-05-17 20:06:44 +02:00
blast_radius,
consumers,
library_xregistry_projection,
2026-05-17 20:06:44 +02:00
providers,
unresolved_dependencies,
validate_graph_export,
2026-05-17 20:31:12 +02:00
xregistry_projection,
2026-05-17 20:06:44 +02:00
)
from railiance_fabric.schema_validation import draft202012_validator
2026-05-17 20:06:44 +02:00
from railiance_fabric.server import RegistryHandler
def test_registry_accepts_snapshot_and_queries_graph(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository(
{
"slug": "railiance-fabric",
"name": "Railiance Fabric",
"remote_url": "https://example.invalid/railiance-fabric.git",
"repo_family": "ownership",
2026-05-17 20:06:44 +02:00
}
)
graph = build_graph([Path(".")])
snapshot = store.add_snapshot(
"railiance-fabric",
{
"commit": "test-commit",
"generated_at": "2026-05-17T00:00:00Z",
"graph": graph.to_export(),
},
)
2026-05-17 20:49:28 +02:00
changed_export = graph.to_export()
changed_export["nodes"] = [
node
for node in changed_export["nodes"]
if node["id"] != "repo-scoping.scope-generator"
]
changed_snapshot = store.add_snapshot(
"railiance-fabric",
{
"commit": "test-commit-2",
"generated_at": "2026-05-17T00:01:00Z",
"graph": changed_export,
},
)
2026-05-17 20:06:44 +02:00
combined = store.combined_graph()
2026-05-17 20:31:12 +02:00
artifact = store.add_artifact(
{
"repo_slug": "railiance-fabric",
"target_id": "flex-auth.api.http-api",
"target_kind": "InterfaceDeclaration",
"artifact_type": "openapi",
"name": "flex-auth OpenAPI",
"uri": "docs/contracts/flex-auth.openapi.yaml",
"media_type": "application/vnd.oai.openapi+yaml",
"version": "0.1.0",
"metadata": {"source": "test"},
}
)
libraries = store.ingest_cyclonedx("railiance-fabric", _cyclonedx_bom())
2026-05-17 20:06:44 +02:00
assert snapshot["repo_slug"] == "railiance-fabric"
assert store.get_repository("railiance-fabric")["repo_family"] == "ownership"
2026-05-17 20:49:28 +02:00
assert changed_snapshot["commit"] == "test-commit-2"
2026-05-17 20:31:12 +02:00
assert artifact["artifact_type"] == "openapi"
assert libraries["component_count"] == 2
assert store.list_libraries(name="jsonschema")[0]["purl"] == "pkg:pypi/jsonschema@4.18.0"
2026-05-17 20:49:28 +02:00
inventory = store.repository_inventory("railiance-fabric")
assert inventory["counts"]["snapshots"] == 2
assert inventory["counts"]["libraries"] == 2
diff = store.snapshot_diff("railiance-fabric")
assert diff["from"]["commit"] == "test-commit"
assert diff["to"]["commit"] == "test-commit-2"
assert diff["graph"]["removed_nodes"][0]["id"] == "repo-scoping.scope-generator"
assert store.search("jsonschema")["libraries"][0]["name"] == "jsonschema"
2026-05-17 20:31:12 +02:00
assert store.graph_node_detail("flex-auth.api.http-api")["artifacts"][0]["name"] == "flex-auth OpenAPI"
2026-05-17 20:06:44 +02:00
assert providers(combined, "runtime-secrets")[0]["provider_id"] == "railiance-platform.openbao.runtime-secrets"
assert {match["status"] for match in consumers(combined, "railiance-platform.openbao.kv-v2")} >= {"exact"}
assert unresolved_dependencies(combined) == []
assert blast_radius(combined, "openbao-kv-v2-mount")
2026-05-17 20:31:12 +02:00
assert any(item["kind"] == "Component" for item in backstage_projection(combined)["items"])
assert "services" in xregistry_projection(combined)["groups"]
assert "libraries" in library_xregistry_projection(store.list_libraries())["groups"]
2026-05-17 20:06:44 +02:00
def test_registry_projects_repo_family_relations_from_repo_local_declarations(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository({"slug": "railiance-cluster", "name": "Railiance Cluster"})
store.upsert_repository({"slug": "railiance-infra", "name": "Railiance Infra"})
store.upsert_repository({"slug": "railiance-platform", "name": "Railiance Platform"})
rail_repo = tmp_path / "rail-kubernetes"
(rail_repo / "declarations").mkdir(parents=True)
(rail_repo / "declarations" / "rail.yaml").write_text(
"""
kind: execution-rail
rail_id: rail-kubernetes
repo: rail-kubernetes
ownership_repo: railiance-cluster
2026-07-26 13:34:56 +02:00
contract_version: 1.0.0
composition_kind: base
readiness_state: verified
""".lstrip(),
encoding="utf-8",
)
rapp_repo = tmp_path / "rapp-openbao"
(rapp_repo / "declarations").mkdir(parents=True)
(rapp_repo / "declarations" / "rapp.yaml").write_text(
"""
kind: managed-workload-package
rapp_id: rapp-openbao
repo: rapp-openbao
ownership_repo: railiance-platform
primary_rail: rail-kubernetes
supported_rails:
- rail-kubernetes
""".lstrip(),
encoding="utf-8",
)
reef_repo = tmp_path / "reef-railiance"
(reef_repo / "declarations").mkdir(parents=True)
(reef_repo / "bindings").mkdir(parents=True)
(reef_repo / "declarations" / "reef.yaml").write_text(
"""
kind: substrate-reef
reef_id: reef-railiance
repo: reef-railiance
ownership_repo: railiance-infra
primary_rail: rail-kubernetes
""".lstrip(),
encoding="utf-8",
)
(reef_repo / "bindings" / "rails.yaml").write_text(
"""
reef_id: reef-railiance
hosted_rails:
- rail_id: rail-kubernetes
role: primary
2026-07-26 13:34:56 +02:00
readiness_state: declared
contract_version: 1.0.0
evidence:
- evidence/admission/rail-kubernetes-baseline.json
""".lstrip(),
encoding="utf-8",
)
(reef_repo / "bindings" / "rapps.yaml").write_text(
"""
reef_id: reef-railiance
bound_rapps:
- rapp_id: rapp-openbao
status: approved
""".lstrip(),
encoding="utf-8",
)
store.upsert_repository(
{
"slug": "rail-kubernetes",
"name": "rail-kubernetes",
"path": str(rail_repo),
"repo_family": "rail",
"ownership_repo": "railiance-cluster",
"declaration_paths": [str(rail_repo / "declarations" / "rail.yaml")],
}
)
store.upsert_repository(
{
"slug": "rapp-openbao",
"name": "rapp-openbao",
"path": str(rapp_repo),
"repo_family": "rapp",
"ownership_repo": "railiance-platform",
"primary_rail": "rail-kubernetes",
"supported_rails": ["rail-kubernetes"],
"declaration_paths": [str(rapp_repo / "declarations" / "rapp.yaml")],
}
)
store.upsert_repository(
{
"slug": "reef-railiance",
"name": "reef-railiance",
"path": str(reef_repo),
"repo_family": "reef",
"ownership_repo": "railiance-infra",
"primary_rail": "rail-kubernetes",
"declaration_paths": [str(reef_repo / "declarations" / "reef.yaml")],
}
)
combined = store.combined_graph()
nodes = {node["id"]: node for node in combined["nodes"] if node["kind"] == "Repository"}
edges = {
(edge["from"], edge["type"], edge["to"]): edge
for edge in combined["edges"]
}
reef_record = store.get_repository("reef-railiance")
assert reef_record["path"] == str(reef_repo.resolve())
assert reef_record["declaration_paths"] == [str((reef_repo / "declarations" / "reef.yaml").resolve())]
assert nodes["repo:reef-railiance"]["attributes"]["source_path"].endswith("declarations/reef.yaml")
2026-07-26 13:34:56 +02:00
assert nodes["repo:rail-kubernetes"]["attributes"]["contract_version"] == "1.0.0"
assert nodes["repo:rail-kubernetes"]["attributes"]["composition_kind"] == "base"
assert nodes["repo:rail-kubernetes"]["attributes"]["readiness_state"] == "verified"
assert (
edges[("repo:rail-kubernetes", "governed_by", "repo:railiance-cluster")]["canonical_type"]
== "governed_by"
)
assert (
edges[("repo:rapp-openbao", "supports_rail", "repo:rail-kubernetes")]["canonical_type"]
== "depends_on"
)
2026-07-26 13:34:56 +02:00
reef_rail = edges[("repo:reef-railiance", "hosts_rail", "repo:rail-kubernetes")]
assert reef_rail["canonical_type"] == "deploys"
assert reef_rail["attributes"]["readiness_state"] == "declared"
assert reef_rail["attributes"]["contract_version"] == "1.0.0"
assert reef_rail["attributes"]["readiness_evidence"] == [
"evidence/admission/rail-kubernetes-baseline.json"
]
reef_binding = edges[("repo:reef-railiance", "binds_rapp", "repo:rapp-openbao")]
assert reef_binding["canonical_type"] == "deploys"
assert any(
str(link.get("path") or "").endswith("bindings/rapps.yaml")
for link in reef_binding["attributes"]["source_links"]
)
2026-07-26 13:34:56 +02:00
def test_registry_projects_derived_rail_base_dependency(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
base_repo = tmp_path / "rail-kubernetes"
derived_repo = tmp_path / "rail-knative"
(base_repo / "declarations").mkdir(parents=True)
(derived_repo / "declarations").mkdir(parents=True)
(base_repo / "declarations" / "rail.yaml").write_text(
"rail_id: rail-kubernetes\nownership_repo: railiance-cluster\n",
encoding="utf-8",
)
(derived_repo / "declarations" / "rail.yaml").write_text(
"""
rail_id: rail-knative
ownership_repo: railiance-cluster
contract_version: 1.0.0
composition_kind: derived
base_rail: rail-kubernetes
base_rail_contract: 1.0.0
readiness_state: declared
""".lstrip(),
encoding="utf-8",
)
for slug, path in (("rail-kubernetes", base_repo), ("rail-knative", derived_repo)):
store.upsert_repository(
{
"slug": slug,
"name": slug,
"path": str(path),
"repo_family": "rail",
"declaration_paths": [str(path / "declarations" / "rail.yaml")],
}
)
combined = store.combined_graph()
nodes = {node["id"]: node for node in combined["nodes"] if node["kind"] == "Repository"}
edges = {(edge["from"], edge["type"], edge["to"]): edge for edge in combined["edges"]}
assert nodes["repo:rail-knative"]["attributes"]["base_rail"] == "rail-kubernetes"
assert nodes["repo:rail-knative"]["attributes"]["base_rail_contract"] == "1.0.0"
dependency = edges[("repo:rail-knative", "base_rail", "repo:rail-kubernetes")]
assert dependency["canonical_type"] == "depends_on"
2026-05-17 20:06:44 +02:00
def test_registry_http_service_serves_queries(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository({"slug": "railiance-fabric", "name": "Railiance Fabric"})
store.add_snapshot(
"railiance-fabric",
{
"commit": "test-commit",
"generated_at": "2026-05-17T00:00:00Z",
"graph": build_graph([Path(".")]).to_export(),
},
)
class Handler(RegistryHandler):
pass
Handler.store = store
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
base_url = f"http://127.0.0.1:{server.server_port}"
assert cli_main(
[
"registry",
"sync",
"--registry-url",
base_url,
"--repo-slug",
"railiance-fabric",
"--commit",
"test-cli",
".",
]
) == 0
assert store.latest_snapshot("railiance-fabric")["commit"] == "test-cli"
2026-05-17 20:49:28 +02:00
second_export = build_graph([Path(".")]).to_export()
removed_node_id = second_export["nodes"][-1]["id"]
2026-05-17 20:49:28 +02:00
second_export["nodes"] = second_export["nodes"][:-1]
second_export["edges"] = [
edge
for edge in second_export["edges"]
if edge["from"] != removed_node_id and edge["to"] != removed_node_id
]
2026-05-17 20:49:28 +02:00
_post_json(
f"{base_url}/repositories/railiance-fabric/snapshots",
{
"commit": "test-cli-2",
"generated_at": "2026-05-17T00:02:00Z",
"graph": second_export,
},
)
2026-05-17 20:06:44 +02:00
with urllib.request.urlopen(f"{base_url}/health", timeout=5) as response:
assert json.loads(response.read())["status"] == "ok"
with urllib.request.urlopen(f"{base_url}/status", timeout=5) as response:
status_payload = json.loads(response.read())
2026-05-17 20:49:28 +02:00
with urllib.request.urlopen(
f"{base_url}/repositories/railiance-fabric/snapshots",
timeout=5,
) as response:
snapshots_payload = json.loads(response.read())
with urllib.request.urlopen(
f"{base_url}/repositories/railiance-fabric/snapshots/diff",
timeout=5,
) as response:
drift_payload = json.loads(response.read())
2026-05-17 20:06:44 +02:00
with urllib.request.urlopen(
f"{base_url}/graph/providers?capability_type=runtime-secrets",
timeout=5,
) as response:
providers_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/graph/edges", timeout=5) as response:
edges_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/graph/summary", timeout=5) as response:
graph_summary_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/exports/state-hub", timeout=5) as response:
state_hub_export = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/exports/financial", timeout=5) as response:
financial_export = json.loads(response.read())
2026-05-17 20:31:12 +02:00
artifact_payload = _post_json(
f"{base_url}/artifacts",
{
"repo_slug": "railiance-fabric",
"target_id": "railiance-platform.openbao.kv-v2",
"target_kind": "InterfaceDeclaration",
"artifact_type": "openapi",
"name": "OpenBao KV API",
"uri": "https://example.invalid/openbao.yaml",
},
)
sbom_path = tmp_path / "bom.json"
sbom_path.write_text(json.dumps(_cyclonedx_bom()), encoding="utf-8")
assert cli_main(
[
"registry",
"ingest-cyclonedx",
str(sbom_path),
"--registry-url",
base_url,
"--repo-slug",
"railiance-fabric",
]
) == 0
library_payload = _post_json(
f"{base_url}/repositories/railiance-fabric/libraries/cyclonedx",
_cyclonedx_bom(),
)
2026-05-17 20:31:12 +02:00
with urllib.request.urlopen(
f"{base_url}/artifacts?target_id=railiance-platform.openbao.kv-v2",
timeout=5,
) as response:
artifacts_payload = json.loads(response.read())
with urllib.request.urlopen(
f"{base_url}/repositories/railiance-fabric/libraries",
timeout=5,
) as response:
libraries_payload = json.loads(response.read())
2026-05-17 20:49:28 +02:00
with urllib.request.urlopen(
f"{base_url}/repositories/railiance-fabric/inventory",
timeout=5,
) as response:
inventory_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/search?q=jsonschema", timeout=5) as response:
search_payload = json.loads(response.read())
2026-05-17 20:31:12 +02:00
with urllib.request.urlopen(f"{base_url}/exports/backstage", timeout=5) as response:
backstage_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/exports/xregistry", timeout=5) as response:
xregistry_payload = json.loads(response.read())
with urllib.request.urlopen(f"{base_url}/exports/libraries/xregistry", timeout=5) as response:
library_projection_payload = json.loads(response.read())
2026-05-17 20:06:44 +02:00
assert providers_payload[0]["provider_id"] == "railiance-platform.openbao.runtime-secrets"
assert edges_payload
assert graph_summary_payload["node_count"] == len(state_hub_export["nodes"])
assert graph_summary_payload["edge_count"] == len(state_hub_export["edges"])
assert state_hub_export["source"]["producer"] == "railiance-fabric"
assert state_hub_export["source"]["commit"].startswith("snapshot-set:sha256:")
assert state_hub_export["source"]["snapshots"][-1]["commit"] == "test-cli-2"
assert financial_export["apiVersion"] == "railiance.fabric/v1alpha2"
assert financial_export["schema_version"] == "financial-fabric-v1"
assert financial_export["source"]["commit"] == state_hub_export["source"]["commit"]
2026-05-17 20:49:28 +02:00
assert snapshots_payload[0]["commit"] == "test-cli-2"
assert status_payload["counts"]["repositories"] == 1
assert status_payload["counts"]["snapshots"] == 3
2026-05-17 20:49:28 +02:00
assert drift_payload["graph"]["removed_nodes"]
2026-05-17 20:31:12 +02:00
assert artifact_payload["name"] == "OpenBao KV API"
assert artifacts_payload[0]["artifact_type"] == "openapi"
assert library_payload["component_count"] == 2
assert libraries_payload[0]["name"] == "jsonschema"
2026-05-17 20:49:28 +02:00
assert inventory_payload["counts"]["snapshots"] == 3
assert search_payload["libraries"][0]["name"] == "jsonschema"
2026-05-17 20:31:12 +02:00
assert backstage_payload["kind"] == "BackstageCatalogProjection"
assert "interfaces" in xregistry_payload["groups"]
assert "libraries" in library_projection_payload["groups"]
2026-05-17 20:06:44 +02:00
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
2026-05-17 20:31:12 +02:00
def test_graph_export_validation_rejects_unflagged_display_edges() -> None:
graph = {
"apiVersion": "railiance.fabric/v1alpha1",
"kind": "FabricGraphExport",
"nodes": [],
"edges": [
{
"from": "repo.fixture",
"to": "fixture.service",
"type": "declares",
"canonical_type": "part_of",
"canon_anchor": "model/devsecops",
"mapping_fit": "partial",
"display_only": False,
"evidence_state": "declared",
}
],
}
try:
validate_graph_export(graph)
except RegistryError as exc:
assert "display-only edge type" in exc.message
else:
raise AssertionError("expected RegistryError for unflagged display-only edge")
def test_state_hub_export_schema_accepts_legacy_and_financial_shapes() -> None:
validator = draft202012_validator(Path("schemas/state-hub-export.schema.yaml"))
legacy_graph = {
"apiVersion": "railiance.fabric/v1alpha1",
"kind": "FabricGraphExport",
"nodes": [],
"edges": [],
}
financial_graph = materialize_financial_graph_export(_financial_graph())
assert list(validator.iter_errors(legacy_graph)) == []
assert list(validator.iter_errors(financial_graph)) == []
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"
2026-05-24 15:55:05 +02:00
assert graph["nodes"][0]["deployment_overlay"]["deployment_environment"] == "dev"
assert graph["nodes"][0]["deployment_overlay"]["deployment_scenario"] == "bernd-laptop"
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_current_graph_projects_to_financial_baseline() -> None:
legacy_graph = build_graph([Path(".")]).to_export()
financial_graph = financial_export_from_legacy(legacy_graph)
validate_graph_export(financial_graph)
validator = draft202012_validator(Path("schemas/state-hub-export.schema.yaml"))
assert list(validator.iter_errors(financial_graph)) == []
assert financial_graph["netkingdom"]["id"] == "railiance.netkingdom"
assert financial_graph["fabrics"][0]["id"] == "fabric.railiance.primary"
assert financial_graph["generated_at"].endswith("Z")
assert financial_graph["unresolved"] == []
assert all(node["containment"]["fabric_id"] == "fabric.railiance.primary" for node in financial_graph["nodes"])
assert all(node["ownership"]["owner_actor_id"] == "actor.railiance.primary-lord" for node in financial_graph["nodes"])
def test_export_cli_can_print_financial_baseline(capsys) -> None:
assert cli_main(["export", "--format", "financial", "."]) == 0
payload = json.loads(capsys.readouterr().out)
validate_graph_export(payload)
assert payload["apiVersion"] == "railiance.fabric/v1alpha2"
assert payload["compatibility"]["projected_from_apiVersion"] == "railiance.fabric/v1alpha1"
def test_registry_reset_archive_and_guarded_reset(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository({"slug": "fixture-repo", "name": "Fixture Repo"})
store.add_snapshot(
"fixture-repo",
{
"commit": "abc123",
"generated_at": "2026-05-23T00:00:00Z",
"graph": build_graph([Path(".")]).to_export(),
},
)
archive = store.reset_archive()
assert archive["kind"] == "RegistryResetArchive"
assert archive["counts"]["repositories"] == 1
assert archive["counts"]["snapshots"] == 1
assert archive["snapshots"][0]["commit"] == "abc123"
try:
store.reset_graph_data(
{
"confirm": "nope",
"reason": "test reset",
"archive_sha256": "abc123",
}
)
except RegistryError as exc:
assert RESET_CONFIRMATION_TOKEN in exc.message
else:
raise AssertionError("expected RegistryError for missing reset confirmation")
event = store.reset_graph_data(
{
"confirm": RESET_CONFIRMATION_TOKEN,
"reason": "test reset",
"archive_path": str(tmp_path / "archive.json"),
"archive_sha256": "abc123",
}
)
assert event["dropped_counts"]["snapshots"] == 1
assert event["repositories_preserved"] == 1
assert store.status()["counts"]["repositories"] == 1
assert store.status()["counts"]["snapshots"] == 0
assert store.status()["counts"]["registry_reset_events"] == 1
def test_registry_cli_exports_archive_before_reset(tmp_path: Path, capsys) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
store.upsert_repository({"slug": "fixture-repo", "name": "Fixture Repo"})
store.add_snapshot(
"fixture-repo",
{
"commit": "abc123",
"generated_at": "2026-05-23T00:00:00Z",
"graph": build_graph([Path(".")]).to_export(),
},
)
class Handler(RegistryHandler):
pass
Handler.store = store
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
archive_path = tmp_path / "reset-archive.json"
assert cli_main(
[
"registry",
"reset-graph-data",
"--registry-url",
f"http://127.0.0.1:{server.server_port}",
"--archive",
str(archive_path),
"--confirm",
RESET_CONFIRMATION_TOKEN,
"--reason",
"test reset",
]
) == 0
output = capsys.readouterr().out
archive = json.loads(archive_path.read_text(encoding="utf-8"))
assert "reset event" in output
assert archive["counts"]["snapshots"] == 1
assert store.status()["counts"]["snapshots"] == 0
assert store.status()["counts"]["repositories"] == 1
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
2026-05-17 23:22:55 +02:00
def test_registry_sync_manifest_registers_multiple_repos(tmp_path: Path) -> None:
store = RegistryStore(tmp_path / "registry.sqlite3")
store.init_schema()
class Handler(RegistryHandler):
pass
Handler.store = store
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
base_url = f"http://127.0.0.1:{server.server_port}"
sbom_path = tmp_path / "bom.json"
sbom_path.write_text(json.dumps(_cyclonedx_bom()), encoding="utf-8")
manifest_path = tmp_path / "manifest.yaml"
manifest_path.write_text(
"\n".join(
[
"apiVersion: railiance.fabric/v1alpha1",
"kind: RegistryOnboardingManifest",
"repositories:",
" - slug: railiance-fabric",
" name: Railiance Fabric",
f" path: {Path('.').resolve()}",
" commit: manifest-commit",
" repo_family: ownership",
2026-05-17 23:22:55 +02:00
f" sbom: {sbom_path.name}",
" - slug: missing-repo",
" name: Missing Repo",
f" path: {tmp_path / 'missing-repo'}",
"",
]
),
encoding="utf-8",
)
assert cli_main(
[
"registry",
"sync-manifest",
str(manifest_path),
"--registry-url",
base_url,
]
) == 0
repositories = {repo["slug"]: repo for repo in store.list_repositories()}
assert set(repositories) == {"missing-repo", "railiance-fabric"}
assert repositories["railiance-fabric"]["repo_family"] == "ownership"
2026-05-17 23:22:55 +02:00
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"
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
2026-05-17 20:31:12 +02:00
def _post_json(url: str, payload: dict) -> dict:
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=5) as response:
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",
},
2026-05-24 15:55:05 +02:00
"deployment_overlay": {
"deployment_environment": "dev",
"deployment_scenario": "bernd-laptop",
"routing_authority": "loopback",
"access_zone": "private-dev",
"policy_authority": "local-loopback-binding",
"exposure_class": "local-only",
"route_evidence": {"host": "127.0.0.1", "port": 8000, "protocol": "tcp"},
},
"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",
"specVersion": "1.6",
"version": 1,
"components": [
{
"bom-ref": "pkg:pypi/jsonschema@4.18.0",
"type": "library",
"name": "jsonschema",
"version": "4.18.0",
"purl": "pkg:pypi/jsonschema@4.18.0",
"licenses": [{"license": {"id": "MIT"}}],
}
],
"services": [
{
"bom-ref": "urn:service:state-hub",
"name": "state-hub-api",
"version": "0.1.0",
"endpoints": ["http://127.0.0.1:8000"],
}
],
}