generated from coulomb/repo-seed
Add canon reset and reingest guardrails
This commit is contained in:
@@ -175,6 +175,10 @@ def _discovery_snapshot(
|
||||
"label": "Do Not Overwrite",
|
||||
"repo": "fixture-repo",
|
||||
"domain": "testing",
|
||||
"canon_category": "source-repository",
|
||||
"canon_anchor": "model/devsecops",
|
||||
"mapping_fit": "direct",
|
||||
"evidence_state": "declared",
|
||||
"aliases": ["fixture-repo"],
|
||||
"origin": "deterministic",
|
||||
"review_state": "accepted",
|
||||
@@ -192,6 +196,10 @@ def _discovery_snapshot(
|
||||
"repo": "fixture-repo",
|
||||
"domain": "testing",
|
||||
"lifecycle": "active",
|
||||
"canon_category": "service",
|
||||
"canon_anchor": "model/landscape",
|
||||
"mapping_fit": "direct",
|
||||
"evidence_state": "declared",
|
||||
"aliases": [accepted_label],
|
||||
"attributes": {"description": "Accepted discovery candidate."},
|
||||
"origin": "deterministic",
|
||||
@@ -207,6 +215,11 @@ def _discovery_snapshot(
|
||||
{
|
||||
"stable_key": relationship_stable_key(repo_key, "declares", service_key),
|
||||
"edge_type": "declares",
|
||||
"canonical_type": "part_of",
|
||||
"canon_anchor": "model/devsecops",
|
||||
"mapping_fit": "partial",
|
||||
"display_only": True,
|
||||
"evidence_state": "declared",
|
||||
"source_key": repo_key,
|
||||
"target_key": service_key,
|
||||
"origin": "deterministic",
|
||||
@@ -243,6 +256,10 @@ def _discovery_snapshot(
|
||||
"repo": "fixture-repo",
|
||||
"domain": "testing",
|
||||
"lifecycle": "active",
|
||||
"canon_category": "software-system",
|
||||
"canon_anchor": "model/landscape",
|
||||
"mapping_fit": "partial",
|
||||
"evidence_state": "declared",
|
||||
"origin": "deterministic",
|
||||
"review_state": "accepted",
|
||||
"status": "active",
|
||||
|
||||
@@ -9,6 +9,8 @@ from pathlib import Path
|
||||
from railiance_fabric.cli import main as cli_main
|
||||
from railiance_fabric.graph import build_graph
|
||||
from railiance_fabric.registry import (
|
||||
RESET_CONFIRMATION_TOKEN,
|
||||
RegistryError,
|
||||
RegistryStore,
|
||||
backstage_projection,
|
||||
blast_radius,
|
||||
@@ -16,6 +18,7 @@ from railiance_fabric.registry import (
|
||||
library_xregistry_projection,
|
||||
providers,
|
||||
unresolved_dependencies,
|
||||
validate_graph_export,
|
||||
xregistry_projection,
|
||||
)
|
||||
from railiance_fabric.server import RegistryHandler
|
||||
@@ -231,6 +234,130 @@ def test_registry_http_service_serves_queries(tmp_path: Path) -> None:
|
||||
thread.join(timeout=5)
|
||||
|
||||
|
||||
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_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)
|
||||
|
||||
|
||||
def test_registry_sync_manifest_registers_multiple_repos(tmp_path: Path) -> None:
|
||||
store = RegistryStore(tmp_path / "registry.sqlite3")
|
||||
store.init_schema()
|
||||
|
||||
@@ -74,6 +74,43 @@ def test_registry_scan_manifest_writes_default_cache_and_report(tmp_path: Path,
|
||||
assert not (cache_dir / "rescan.lock").exists()
|
||||
|
||||
|
||||
def test_registry_scan_manifest_disambiguates_normalized_snapshot_names(tmp_path: Path, capsys) -> None:
|
||||
repo_a = _minimal_repo(tmp_path, "vergabe-teilnahme")
|
||||
repo_b = _minimal_repo(tmp_path, "vergabe_teilnahme")
|
||||
manifest = _manifest(
|
||||
tmp_path,
|
||||
[
|
||||
{"slug": "vergabe-teilnahme", "name": "Vergabe Teilnahme", "path": str(repo_a)},
|
||||
{"slug": "vergabe_teilnahme", "name": "Vergabe Teilnahme Alt", "path": str(repo_b)},
|
||||
],
|
||||
)
|
||||
output_dir = tmp_path / "snapshots"
|
||||
|
||||
assert cli_main(
|
||||
[
|
||||
"registry",
|
||||
"scan-manifest",
|
||||
str(manifest),
|
||||
"--dry-run",
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
"--json",
|
||||
]
|
||||
) == 0
|
||||
|
||||
summary = json.loads(capsys.readouterr().out)
|
||||
output_paths = [item["output_path"] for item in summary["repositories"]]
|
||||
assert len(output_paths) == 2
|
||||
assert len(set(output_paths)) == 2
|
||||
assert (output_dir / "vergabe-teilnahme-deterministic.discovery.json").is_file()
|
||||
disambiguated = [
|
||||
path
|
||||
for path in output_dir.glob("vergabe-teilnahme-*-deterministic.discovery.json")
|
||||
if path.name != "vergabe-teilnahme-deterministic.discovery.json"
|
||||
]
|
||||
assert len(disambiguated) == 1
|
||||
|
||||
|
||||
def test_registry_scan_manifest_operational_exit_codes_and_lock(tmp_path: Path, capsys) -> None:
|
||||
repo = _minimal_repo(tmp_path, "fixture-repo")
|
||||
manifest = _manifest(tmp_path, [{"slug": "fixture-repo", "name": "Fixture Repo", "path": str(repo)}])
|
||||
|
||||
Reference in New Issue
Block a user