feat: persist accountability evidence identities

This commit is contained in:
2026-05-24 09:38:57 +02:00
parent 26f1913d51
commit ab7e0ccab1
7 changed files with 771 additions and 5 deletions

View File

@@ -1,7 +1,12 @@
import json
from pathlib import Path
from railiance_fabric.accountability_roots import collect_accountability_root_evidence
from railiance_fabric.accountability_roots import (
AccountabilityEvidenceStore,
build_identity_projection,
collect_accountability_root_evidence,
load_accountability_root_manifest,
)
from railiance_fabric.cli import main as cli_main
from railiance_fabric.schema_validation import draft202012_validator
@@ -32,6 +37,46 @@ def test_collect_accountability_root_evidence_from_manifest(tmp_path: Path) -> N
assert "secret-value" not in json.dumps(secret_root)
def test_identity_projection_is_stable_and_reviewable(tmp_path: Path) -> None:
manifest_path = _fixture_manifest(tmp_path)
manifest = load_accountability_root_manifest(manifest_path)
first = build_identity_projection(collect_accountability_root_evidence(manifest_path), manifest)
second = build_identity_projection(collect_accountability_root_evidence(manifest_path), manifest)
validator = draft202012_validator(Path("schemas/accountability-identity-projection.schema.yaml"))
assert list(validator.iter_errors(first)) == []
first_keys = {candidate["stable_key"] for candidate in first["identity_candidates"]}
second_keys = {candidate["stable_key"] for candidate in second["identity_candidates"]}
assert first_keys == second_keys
assert {
"Actor",
"Fabric",
"Repository",
"Deployable",
"SecretRoot",
} <= {candidate["identity_type"] for candidate in first["identity_candidates"]}
assert first["candidate_graph"]["nodes"]
assert first["candidate_graph"]["edges"]
def test_evidence_store_persists_runs_items_and_identities(tmp_path: Path) -> None:
manifest_path = _fixture_manifest(tmp_path)
manifest = load_accountability_root_manifest(manifest_path)
evidence_run = collect_accountability_root_evidence(manifest_path)
projection = build_identity_projection(evidence_run, manifest)
store = AccountabilityEvidenceStore(tmp_path / "evidence.sqlite3")
stored = store.add_evidence_run(evidence_run, projection)
latest = store.latest_run()
assert latest is not None
assert latest["id"] == stored["run_id"]
assert stored["evidence_count"] == len(store.list_evidence(stored["run_id"]))
assert stored["identity_candidate_count"] == len(store.list_identity_candidates(stored["run_id"]))
def test_discover_roots_cli_prints_evidence_json(tmp_path: Path, capsys) -> None:
manifest = _fixture_manifest(tmp_path)
@@ -42,6 +87,29 @@ def test_discover_roots_cli_prints_evidence_json(tmp_path: Path, capsys) -> None
assert payload["roots"]
def test_discover_roots_cli_can_print_identities_and_store(tmp_path: Path, capsys) -> None:
manifest = _fixture_manifest(tmp_path)
store_path = tmp_path / "evidence.sqlite3"
assert (
cli_main(
[
"discover-roots",
"--manifest",
str(manifest),
"--identity-projection",
"--store-db",
str(store_path),
]
)
== 0
)
payload = json.loads(capsys.readouterr().out)
assert payload["kind"] == "AccountabilityIdentityProjection"
assert AccountabilityEvidenceStore(store_path).latest_run() is not None
def _fixture_manifest(tmp_path: Path) -> Path:
workspace = tmp_path / "workspace"
repo = workspace / "fixture-repo"