Files
aios/tests/test_governed_release_evidence_producer.py
T

445 lines
18 KiB
Python

"""Governed release evidence producer contract tests (V2R2C2 P0).
Proves that ``governed.release.evidence.produce`` (the producer half) writes a
self-consistent evidence closure from real build artifacts, and that the closure
can be re-validated and projected byte-for-byte by ``TrustedBuilderEvidenceMaterializer``
(the projection half). Also proves fail-closed behavior on tampered/inconsistent
input and idempotency/conflict handling, plus operation-registry registration.
"""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[1]
import sys
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from aios_trusted_candidate_builder import ( # noqa: E402
load_builder_policy,
TrustedCandidateBuilderConfig,
)
from governed_release_evidence_producer import ( # noqa: E402
EvidenceProduceError,
GovernedBuildArtifacts,
producer_operation_definition,
producer_registry,
produce_evidence_closure,
)
from release_evidence_materializer import ( # noqa: E402
TrustedBuilderEvidenceMaterializer,
)
POLICY_PATH = ROOT / "config" / "gitops" / "trusted-candidate-builder-policy.json"
def canonical(value: dict) -> bytes:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=True
).encode()
def digest(label: str) -> str:
return hashlib.sha256(label.encode()).hexdigest()
def make_provider_receipt(provider: str, body: dict, source_commit: str,
tree_oid: str, issued_at: str) -> dict:
"""Bind a trusted-provider receipt to an evidence body (content-digest bound)."""
return {
"schema": "aios.trusted-provider.evidence.v1",
"provider_identity": provider,
"operation_run_id": f"{provider}-run-{digest('run-' + str(body.get('passed', '')))[:12]}",
"result_reference": f"{provider}/{digest('result')[:16]}/result.json",
"content_sha256": hashlib.sha256(canonical(dict(body))).hexdigest(),
"source_oid": source_commit,
"tree_oid": tree_oid,
"environment": "trusted-builder-gitops-live",
"issued_at": issued_at,
}
def bind_receipt(evidence: dict, provider: str, source_commit: str,
tree_oid: str, issued_at: str) -> dict:
return {**dict(evidence), "receipt": make_provider_receipt(
provider, dict(evidence), source_commit, tree_oid, issued_at)}
def build_input(tmp_path: Path, *, tamper_artifact: bool = False,
bad_secret_scan: bool = False) -> tuple[GovernedBuildArtifacts, str, str]:
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
recipe = config.fixed_recipe("V2R2C2")
version = recipe["version"]
tree_oid = "a" * 40
artifact_bytes = b"artifact-bytes"
if tamper_artifact:
artifact_bytes = b"artifact-tampered"
real_artifact_sha = hashlib.sha256(artifact_bytes).hexdigest()
closure_bytes = b"checksums-v1\n"
closure_sha = hashlib.sha256(closure_bytes).hexdigest()
manifest_dict = {
"artifact_sha256": real_artifact_sha,
"package_closure_sha256": closure_sha,
}
# manifest_sha256 is the digest of the manifest evidence file; the producer
# derives it from the manifest content (never self-referential).
manifest_sha = hashlib.sha256(canonical(manifest_dict)).hexdigest()
policy_dict = {"trusted": "release-policy"}
# policy_sha256 is the digest of the policy evidence file; derived by the
# producer from policy content (not a self-referential field).
policy_sha = hashlib.sha256(canonical(policy_dict)).hexdigest()
issued_at = "2026-09-09T00:00:00+00:00"
src = recipe["source_commit"]
tests = bind_receipt({"passed": True}, "aios.tests-runner", src, tree_oid, issued_at)
canary = bind_receipt({"passed": True}, "aios.canary-gate", src, tree_oid, issued_at)
gitea = bind_receipt(
{
"consistent": True,
"remote_ref_oid": recipe["source_commit"],
"repository": config.repository,
"source_commit": recipe["source_commit"],
"tree_oid": tree_oid,
},
"aios.gitea-integrity", src, tree_oid, issued_at,
)
rollback_sha = digest("rollback-contract")
rollback = bind_receipt(
{
"ready": True,
"rollback_sha256": rollback_sha,
"source_commit": recipe["source_commit"],
},
"aios.rollback-controller", src, tree_oid, issued_at,
)
if bad_secret_scan:
secret_scan = bind_receipt(
{"artifact_sha256": digest("wrong"), "passed": True},
"aios.secret-scanner", src, tree_oid, issued_at,
)
else:
secret_scan = bind_receipt(
{"artifact_sha256": real_artifact_sha, "passed": True},
"aios.secret-scanner", src, tree_oid, issued_at,
)
deployment = {
"service": "aios-silent-release-controller",
"release_path": "/srv/release/r1",
"release_root": "/srv/release",
"cutover_link": "/srv/release/current",
"dropin_path": "/srv/release/r1/deploy/systemd/controller.service",
"dropin_sha256": digest("dropin"),
"systemd_unit_sha256": digest("unit"),
"rollback_path": "/srv/release/rollback-r1",
"rollback_sha256": rollback_sha,
"authoritative_git_dir": "/data/aios-gitops/repo.git",
"repository_identity": config.repository,
"baseline_before_sha256": digest("baseline"),
"registry_before_sha256": digest("registry"),
"production_tag_refset_before_sha256": digest("tagset"),
"health_contract_version": "trusted-builder/v1",
"timeout_policy": {"seconds": 10},
"retry_policy": {"attempts": 1},
"health_contract": {"checked": True},
}
tag = {
"version": "trusted-v2r2c2-001",
"tag_type": "annotated",
"expected_tag_oid": "b" * 40,
"expected_target_oid": recipe["source_commit"],
"evidence_closure_oid": "c" * 40,
"approval_plan_id": "owner-plan-trusted-v2r2c2",
"repository_identity": config.repository,
"gitea_owner": "aios",
"gitea_repository": "aios",
"current_remote_tag_oid": None,
"remote_absent_evidence_sha256": digest("tag-absence"),
"verification_contract": "trusted-builder/v1",
}
finalization = {
"version": "trusted-v2r2c2-001",
"expected_tag_oid": "b" * 40,
"expected_target_oid": recipe["source_commit"],
"expected_previous_baseline_sha256": digest("baseline"),
"registry_expected_record": {"base": recipe["source_commit"]},
"baseline_expected_record": {"base": recipe["source_commit"]},
"final_consistency_contract": "trusted-builder/v1",
}
request = GovernedBuildArtifacts(
version=version,
candidate_id=recipe["candidate_id"],
source_commit=recipe["source_commit"],
tree_oid=tree_oid,
artifact=artifact_bytes,
manifest=manifest_dict,
package_closure=closure_bytes,
policy=policy_dict,
tests=tests,
canary=canary,
gitea=gitea,
rollback=rollback,
secret_scan=secret_scan,
deployment=deployment,
tag=tag,
finalization=finalization,
rollback_sha256=rollback_sha,
issued_at="2026-09-09T00:00:00+00:00",
)
return request, recipe["candidate_id"], tree_oid
def fake_git(tree_oid: str):
def runner(argv, **_kwargs):
if "rev-parse" in argv:
return SimpleNamespace(stdout=tree_oid + "\n", returncode=0)
return SimpleNamespace(stdout="", returncode=0)
return runner
def make_materializer(tmp_path: Path, source_root: Path, output_root: Path, tree_oid: str):
authority = tmp_path / "repo.git"
authority.mkdir(mode=0o700, exist_ok=True)
output_root.mkdir(mode=0o700, exist_ok=True)
return TrustedBuilderEvidenceMaterializer(
builder_policy=POLICY_PATH,
authority_git_dir=authority,
source_root=source_root,
output_root=output_root,
expected_owner_uid=None,
git_runner=fake_git(tree_oid),
)
def test_produce_writes_closure_that_revalidates_under_builder_verification(tmp_path: Path) -> None:
"""Producer closure re-validates and is projected byte-for-byte by the materializer.
The producer must write a self-consistent closure that the deterministic builder
verification re-validates, and that TrustedBuilderEvidenceMaterializer projects
with destination bytes exactly equal to the source (byte-exact, including a
package-closure fixture containing an LF byte).
"""
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
output_root = tmp_path / "output"
request, candidate_id, tree_oid = build_input(tmp_path)
version = request.version
result = produce_evidence_closure(
builder_policy=POLICY_PATH,
source_root=source_root,
request=request,
expected_owner_uid=None,
)
assert result.status == "EVIDENCE_PRODUCED"
assert result.idempotent is False
assert (source_root / version / "receipt.json").is_file()
for name in ("artifact.tar.gz", "manifest.json", "checksums.sha256",
"trusted-release-policy.json", "tests.json", "canary.json",
"gitea.json", "rollback.json", "secret-scan.json"):
assert (source_root / version / name).is_file(), f"missing {name}"
# Re-validate the produced closure with the deterministic builder verification
# (the exact collector the materializer uses); it must collect cleanly and
# reproduce the producer's evidence digests.
from aios_trusted_candidate_builder import ( # noqa: F811
DeterministicEvidenceCollector,
ServerOwnedEvidenceSource,
)
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(POLICY_PATH))
authority = tmp_path / "repo.git"
authority.mkdir(mode=0o700)
source = ServerOwnedEvidenceSource(
config, authority, source_root.resolve(),
expected_owner_uid=None, git_runner=fake_git(tree_oid),
)
collector = DeterministicEvidenceCollector(config, evidence_source=source)
recipe = config.fixed_recipe(version)
bundle = collector.collect(version, recipe)
assert set(bundle["evidence_digests"]) == set(result.evidence_digests)
for name, digest in result.evidence_digests.items():
assert bundle["evidence_digests"][name] == digest, f"digest mismatch {name}"
# Byte-exact projection: source closure -> materializer -> destination closure,
# with destination bytes exactly equal to source (incl. the LF-bearing
# package-closure file) and a destination collector PASS.
materializer = make_materializer(tmp_path, source_root, output_root, tree_oid)
outcome = materializer.materialize(
version=version,
candidate_id=candidate_id,
source_commit=request.source_commit,
)
assert outcome["status"] == "EVIDENCE_MATERIALIZED"
for name in ("receipt.json", "artifact.tar.gz", "manifest.json",
"checksums.sha256", "trusted-release-policy.json",
"tests.json", "canary.json", "gitea.json",
"rollback.json", "secret-scan.json"):
src = (source_root / version / name).read_bytes()
dst = (output_root / version / name).read_bytes()
assert src == dst, f"byte-exact projection failed for {name}"
assert hashlib.sha256(src).hexdigest() == hashlib.sha256(dst).hexdigest()
# Destination closure passes the collector too.
dst_source = ServerOwnedEvidenceSource(
config, authority, output_root.resolve(),
expected_owner_uid=None, git_runner=fake_git(tree_oid),
)
dst_collector = DeterministicEvidenceCollector(config, evidence_source=dst_source)
dst_bundle = dst_collector.collect(version, recipe)
assert set(dst_bundle["evidence_digests"]) == set(result.evidence_digests)
def test_produce_is_idempotent_on_identical_closure(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
request, _, _ = build_input(tmp_path)
first = produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
second = produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
assert first.idempotent is False
assert second.idempotent is True
assert second.status == "EVIDENCE_PRODUCED"
def test_produce_conflicts_on_tampered_artifact(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
request, _, _ = build_input(tmp_path)
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
tampered, _, _ = build_input(tmp_path, tamper_artifact=True)
with pytest.raises(EvidenceProduceError):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=tampered,
expected_owner_uid=None,
)
def test_produce_fails_closed_on_secret_scan_artifact_mismatch(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
bad, _, _ = build_input(tmp_path, bad_secret_scan=True)
with pytest.raises(EvidenceProduceError):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=bad,
expected_owner_uid=None,
)
assert not (source_root / bad.version).exists()
def test_produce_registers_governed_operation() -> None:
definition = producer_operation_definition()
assert definition.operation_id == "governed.release.evidence.produce"
registry = producer_registry()
assert (
registry.get("governed.release.evidence.produce").operation_id
== "governed.release.evidence.produce"
)
assert "governed.release.evidence.produce" in definition.required_permissions
def _clone_with(request: GovernedBuildArtifacts, **overrides) -> GovernedBuildArtifacts:
data = {k: getattr(request, k) for k in request.__dataclass_fields__}
data.update(overrides)
return GovernedBuildArtifacts(**data)
def test_produce_requires_trusted_provider_receipt_on_every_evidence(tmp_path: Path) -> None:
# A bare {"passed": true} without a trusted-provider receipt must fail closed
# for EVERY governed evidence type (tests/canary/gitea/rollback/secret_scan).
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
good, _, _ = build_input(tmp_path)
for etype in ("tests", "canary", "gitea", "rollback", "secret_scan"):
bad_evidence = dict(getattr(good, etype))
bad_evidence.pop("receipt", None)
request = _clone_with(good, **{etype: bad_evidence})
with pytest.raises(EvidenceProduceError, match="EVIDENCE_RECEIPT_MISSING"):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
assert not (source_root / good.version).exists()
def test_produce_rejects_receipt_content_digest_mismatch(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
good, _, _ = build_input(tmp_path)
bad_evidence = dict(getattr(good, "tests"))
bad_evidence["receipt"] = {**bad_evidence["receipt"], "content_sha256": "0" * 64}
request = _clone_with(good, tests=bad_evidence)
with pytest.raises(EvidenceProduceError, match="EVIDENCE_RECEIPT_CONTENT_MISMATCH"):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
def test_produce_rejects_missing_receipt_fields(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
good, _, _ = build_input(tmp_path)
bad_evidence = dict(getattr(good, "canary"))
bad_evidence["receipt"] = {k: v for k, v in bad_evidence["receipt"].items()
if k != "provider_identity"}
request = _clone_with(good, canary=bad_evidence)
with pytest.raises(EvidenceProduceError, match="EVIDENCE_RECEIPT_INVALID"):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
def test_produce_enforces_source_tree_oid_binding_in_receipt(tmp_path: Path) -> None:
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
good, _, _ = build_input(tmp_path)
bad_evidence = dict(getattr(good, "rollback"))
bad_evidence["receipt"] = {**bad_evidence["receipt"], "source_oid": "f" * 40}
request = _clone_with(good, rollback=bad_evidence)
with pytest.raises(EvidenceProduceError, match="EVIDENCE_RECEIPT_SOURCE_OID_MISMATCH"):
produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
def test_produce_embeds_trusted_provider_binding_in_closure(tmp_path: Path) -> None:
# Positive: with valid receipts the closure is produced and the receipt.json
# carries the provider binding for each governed evidence type.
source_root = tmp_path / "source"
source_root.mkdir(mode=0o700)
request, candidate_id, tree_oid = build_input(tmp_path)
result = produce_evidence_closure(
builder_policy=POLICY_PATH, source_root=source_root, request=request,
expected_owner_uid=None,
)
assert result.status == "EVIDENCE_PRODUCED"
receipt = json.loads(
(source_root / request.version / "receipt.json").read_text(encoding="utf-8")
)
candidate = receipt["candidate"]
assert candidate["source_commit"] == request.source_commit
assert candidate["tree_oid"] == tree_oid
for etype in ("tests", "canary", "gitea", "rollback", "secret_scan"):
ev = candidate.get(etype)
if isinstance(ev, dict):
assert ev.get("passed") is True or ev.get("consistent") is True