This is Hands-On 14A in the Local-First Agent Operations series. It accompanies Part 14, What a Prerelease Still Has to Prove, where I separated implemented capability, exact-artifact acceptance, and observable publication into three ledgers that do not necessarily move together.

A release conversation can go wrong before anyone runs a single test. One person is talking about what the source implements, another is looking at a green CI run, and a third is looking at a public download. All three may be stating true facts about different ledgers.

This lab gives those facts somewhere separate to live. It validates an invented release-evidence packet, binds every result to one artifact identity, and refuses to turn publication metadata into runtime acceptance. Its successful result is deliberately narrow: EVIDENCE PACKET READY FOR REVIEW.

An invented release evidence packet passes through closed-schema, identity, gate, time, and claim-support checks. Invalid packets stop with a bounded not-ready result, while a valid packet becomes ready for human review without any release, approval, or publication inference.
Open full-size diagram

The validator checks whether the packet is coherent enough to review. It does not decide whether the artifact should be released.

Here we can break one of those ledgers safely and see exactly where it stops.

Download the Lab

Download the complete Hands-On 14A package and its SHA-256 checksum. Every source file and fixture is also available through the site’s collapsed source viewer:

README.md markdown View source
# Release Evidence Ledger

This model-free teaching package validates an invented release-evidence packet. It demonstrates that implementation capability, exact-artifact acceptance, and release publication are separate records. Exact artifact identity includes the version, expected release tag, source commit, archive digest, and manifest digest.

The clean fixture contains observable prerelease metadata and archive evidence while installation and lifecycle gates remain incomplete. A successful validation therefore says exactly:

```text
EVIDENCE PACKET READY FOR REVIEW
```

It never infers `RELEASE READY`, `APPROVED`, or `PUBLISHED`.

## Requirements

- Python 3.10 or newer
- Python standard library only

## Run the clean packet

```bash
python3 -B release_evidence.py fixtures/review-ready.json
```

## Run the failure cases

```bash
python3 -B release_evidence.py fixtures/missing-gate.json
python3 -B release_evidence.py fixtures/identity-mismatch.json
python3 -B release_evidence.py fixtures/manifest-mismatch.json
python3 -B release_evidence.py fixtures/stale-evidence.json
python3 -B release_evidence.py fixtures/unsupported-approval.json
```

Each command above returns status 1 with a bounded error code. Rejected private-looking values are never echoed.

## Run acceptance and unit tests

```bash
python3 -B run_lab.py
python3 -B -m unittest -v test_release_evidence.py
```

## Boundary

The package reads only the JSON file supplied on the command line. It performs no build, installation, network access, Git action, CI query, signing, upload, approval, or publication. Its publication state is supplied evidence, not a state discovered or inferred by the program.
manifest.json json View source
{
  "schema_version": 1,
  "name": "release-evidence-ledger",
  "runtime": "python>=3.10",
  "capabilities": ["read_supplied_packet", "validate_evidence_identity", "render_review_summary"],
  "forbidden_capabilities": ["build", "install", "open_network", "run_git", "query_ci", "sign", "upload", "approve", "publish", "infer_release_readiness"],
  "files": {
    "README.md": "09d218452d83850a8fb91e60b3418cf2d484b2da95b2b0f492f25973230b1b6e",
    "fixtures/identity-mismatch.json": "c6eacc79c5fdf3494c0ba0a3ce8468c5e4d5448b0fcdfb46154d5df9178fd5d1",
    "fixtures/manifest-mismatch.json": "5e0737f18d46524acec96435f1c9cfbd69e28ceeeaa0289d442a6fe80a2eefe3",
    "fixtures/missing-gate.json": "e0effed79b9f522600eb5e75ac6d4edb8d0abf08747e62b708a9c638d12a6fd1",
    "fixtures/review-ready.json": "5aab52d38e8fbd9667a62b621eca278aff104818a3e3c3784233ee2081a5c197",
    "fixtures/stale-evidence.json": "c1f356f3ead134d5054203d471acbcdda3059cf74df8ec955ee643effed16acc",
    "fixtures/unsupported-approval.json": "f0d570fb6ab3638c391425ebe93eb4496a2f4785fd0b12488475c6af4f80c834",
    "release_evidence.py": "4eb77d10b5de8130070d2585328c39ea4d6973f7b908cedda0c49c2f3fbf0f07",
    "run_lab.py": "394ff43e853cca8f705261b106872c77edfbf8512d298110bd3268d9215f5f41",
    "test_release_evidence.py": "46b7a5c5bf9e42f3957515b7d32a4c64f336cd34fc5d4c7bf72a5a49e42df376"
  }
}
release_evidence.py python View source
#!/usr/bin/env python3
"""Validate an invented release-evidence packet without external side effects."""

from __future__ import annotations

import argparse
import copy
import json
import re
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any


READY = "EVIDENCE PACKET READY FOR REVIEW"
NOT_READY = "EVIDENCE PACKET NOT READY"

TOP_FIELDS = {
    "schema_version",
    "packet_id",
    "evaluation_time",
    "max_evidence_age_days",
    "artifact",
    "publication_observation",
    "required_gates",
    "evidence",
    "claims",
}
ARTIFACT_FIELDS = {
    "name",
    "version",
    "release_tag",
    "source_commit",
    "archive_sha256",
    "manifest_sha256",
}
PUBLICATION_FIELDS = {"state", "tag", "observed_at", "evidence_ref"}
GATE_FIELDS = {"gate_id", "required"}
EVIDENCE_FIELDS = {
    "evidence_id",
    "gate_id",
    "artifact_version",
    "source_commit",
    "archive_sha256",
    "manifest_sha256",
    "observed_at",
    "result",
    "architectures",
}
CLAIM_FIELDS = {"acceptance", "approval", "approval_evidence_ref"}

PUBLICATION_STATES = {"none", "draft", "prerelease", "release"}
EVIDENCE_RESULTS = {"pass", "fail", "incomplete"}
ACCEPTANCE_CLAIMS = {"incomplete", "complete"}
APPROVAL_CLAIMS = {"unobserved", "approved"}
SYMBOL = re.compile(r"^[a-z0-9][a-z0-9._-]{0,79}$")
VERSION = re.compile(r"^[0-9]+(?:\.[0-9]+){1,3}(?:[a-z][0-9]+)?$")
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
PRIVATE_PATTERNS = (
    re.compile(r"(?:^|\s)/(?:Users|home|private|var|Volumes)/"),
    re.compile(r"(?:^|\s)~/"),
    re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"),
    re.compile(r"\b(?:https?|ssh)://", re.IGNORECASE),
    re.compile(r"\b(?:token|password|secret|api[_-]?key)\s*[:=]", re.IGNORECASE),
    re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
)


class PacketError(Exception):
    """Bounded validation failure that never includes rejected input."""

    def __init__(self, code: str, field: str):
        super().__init__(code)
        self.code = code
        self.field = field


def _object(value: Any, field: str) -> dict[str, Any]:
    if not isinstance(value, dict):
        raise PacketError("E_TYPE", field)
    return value


def _list(value: Any, field: str) -> list[Any]:
    if not isinstance(value, list):
        raise PacketError("E_TYPE", field)
    return value


def _string(value: Any, field: str) -> str:
    if not isinstance(value, str):
        raise PacketError("E_TYPE", field)
    for pattern in PRIVATE_PATTERNS:
        if pattern.search(value):
            raise PacketError("E_PRIVATE", field)
    return value


def _symbol(value: Any, field: str) -> str:
    text = _string(value, field)
    if not SYMBOL.fullmatch(text):
        raise PacketError("E_SYMBOL", field)
    return text


def _closed(obj: dict[str, Any], allowed: set[str], field: str) -> None:
    if set(obj) != allowed:
        raise PacketError("E_FIELDS", field)


def _timestamp(value: Any, field: str) -> datetime:
    text = _string(value, field)
    try:
        parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
    except ValueError as exc:
        raise PacketError("E_TIME", field) from exc
    if parsed.tzinfo is None:
        raise PacketError("E_TIME", field)
    return parsed.astimezone(timezone.utc)


def _unique(values: list[str], field: str) -> None:
    if len(values) != len(set(values)):
        raise PacketError("E_DUPLICATE", field)


def validate_packet(packet: dict[str, Any]) -> dict[str, Any]:
    """Return a deterministic review summary or raise PacketError."""

    original = copy.deepcopy(packet)
    _closed(packet, TOP_FIELDS, "packet")
    if packet["schema_version"] != 1 or isinstance(packet["schema_version"], bool):
        raise PacketError("E_SCHEMA", "schema_version")

    packet_id = _symbol(packet["packet_id"], "packet_id")
    evaluation_time = _timestamp(packet["evaluation_time"], "evaluation_time")
    max_age = packet["max_evidence_age_days"]
    if isinstance(max_age, bool) or not isinstance(max_age, int) or not 1 <= max_age <= 3650:
        raise PacketError("E_RANGE", "max_evidence_age_days")

    artifact = _object(packet["artifact"], "artifact")
    _closed(artifact, ARTIFACT_FIELDS, "artifact")
    artifact_name = _symbol(artifact["name"], "artifact.name")
    artifact_version = _string(artifact["version"], "artifact.version")
    if not VERSION.fullmatch(artifact_version):
        raise PacketError("E_VERSION", "artifact.version")
    release_tag = _symbol(artifact["release_tag"], "artifact.release_tag")
    source_commit = _string(artifact["source_commit"], "artifact.source_commit")
    archive_sha = _string(artifact["archive_sha256"], "artifact.archive_sha256")
    manifest_sha = _string(artifact["manifest_sha256"], "artifact.manifest_sha256")
    if not HEX40.fullmatch(source_commit):
        raise PacketError("E_DIGEST", "artifact.source_commit")
    if not HEX64.fullmatch(archive_sha) or not HEX64.fullmatch(manifest_sha):
        raise PacketError("E_DIGEST", "artifact")

    publication = _object(packet["publication_observation"], "publication_observation")
    _closed(publication, PUBLICATION_FIELDS, "publication_observation")
    publication_state = _string(publication["state"], "publication_observation.state")
    if publication_state not in PUBLICATION_STATES:
        raise PacketError("E_VALUE", "publication_observation.state")
    publication_tag = _symbol(publication["tag"], "publication_observation.tag")
    if publication_tag != release_tag:
        raise PacketError("E_TAG", "publication_observation.tag")
    publication_time = _timestamp(publication["observed_at"], "publication_observation.observed_at")
    publication_ref = _symbol(publication["evidence_ref"], "publication_observation.evidence_ref")
    if publication_time > evaluation_time:
        raise PacketError("E_TIME_ORDER", "publication_observation.observed_at")

    gates_raw = _list(packet["required_gates"], "required_gates")
    if not gates_raw:
        raise PacketError("E_EMPTY", "required_gates")
    gates: list[tuple[str, bool]] = []
    for index, raw in enumerate(gates_raw):
        gate = _object(raw, f"required_gates[{index}]")
        _closed(gate, GATE_FIELDS, f"required_gates[{index}]")
        gate_id = _symbol(gate["gate_id"], f"required_gates[{index}].gate_id")
        if not isinstance(gate["required"], bool):
            raise PacketError("E_TYPE", f"required_gates[{index}].required")
        gates.append((gate_id, gate["required"]))
    gate_ids = [gate_id for gate_id, _ in gates]
    _unique(gate_ids, "required_gates.gate_id")
    required_gate_ids = {gate_id for gate_id, required in gates if required}
    if not required_gate_ids:
        raise PacketError("E_REQUIRED_GATE", "required_gates")

    evidence_raw = _list(packet["evidence"], "evidence")
    evidence_by_id: dict[str, dict[str, Any]] = {}
    evidence_by_gate: dict[str, dict[str, Any]] = {}
    for index, raw in enumerate(evidence_raw):
        item = _object(raw, f"evidence[{index}]")
        _closed(item, EVIDENCE_FIELDS, f"evidence[{index}]")
        evidence_id = _symbol(item["evidence_id"], f"evidence[{index}].evidence_id")
        gate_id = _symbol(item["gate_id"], f"evidence[{index}].gate_id")
        if evidence_id in evidence_by_id or gate_id in evidence_by_gate:
            raise PacketError("E_DUPLICATE", "evidence")
        if gate_id not in set(gate_ids):
            raise PacketError("E_GATE", f"evidence[{index}].gate_id")
        item_version = _string(item["artifact_version"], f"evidence[{index}].artifact_version")
        item_commit = _string(item["source_commit"], f"evidence[{index}].source_commit")
        item_archive = _string(item["archive_sha256"], f"evidence[{index}].archive_sha256")
        item_manifest = _string(item["manifest_sha256"], f"evidence[{index}].manifest_sha256")
        if (item_version, item_commit, item_archive, item_manifest) != (
            artifact_version,
            source_commit,
            archive_sha,
            manifest_sha,
        ):
            raise PacketError("E_IDENTITY", f"evidence[{index}]")
        observed_at = _timestamp(item["observed_at"], f"evidence[{index}].observed_at")
        if observed_at > evaluation_time:
            raise PacketError("E_TIME_ORDER", f"evidence[{index}].observed_at")
        if evaluation_time - observed_at > timedelta(days=max_age):
            raise PacketError("E_STALE", f"evidence[{index}].observed_at")
        result = _string(item["result"], f"evidence[{index}].result")
        if result not in EVIDENCE_RESULTS:
            raise PacketError("E_VALUE", f"evidence[{index}].result")
        architectures_raw = _list(item["architectures"], f"evidence[{index}].architectures")
        architectures = [
            _symbol(value, f"evidence[{index}].architectures") for value in architectures_raw
        ]
        if result == "pass" and not architectures:
            raise PacketError("E_EVIDENCE", f"evidence[{index}].architectures")
        _unique(architectures, f"evidence[{index}].architectures")
        normalized = dict(item)
        normalized["observed_at"] = observed_at
        evidence_by_id[evidence_id] = normalized
        evidence_by_gate[gate_id] = normalized

    if publication_ref not in evidence_by_id:
        raise PacketError("E_REFERENCE", "publication_observation.evidence_ref")
    if publication_state in {"prerelease", "release"}:
        publication_item = evidence_by_id[publication_ref]
        if publication_item["gate_id"] != "release-metadata" or publication_item["result"] != "pass":
            raise PacketError("E_PUBLICATION", "publication_observation.evidence_ref")

    if required_gate_ids - set(evidence_by_gate):
        raise PacketError("E_MISSING_GATE", "evidence")

    claims = _object(packet["claims"], "claims")
    _closed(claims, CLAIM_FIELDS, "claims")
    acceptance = _string(claims["acceptance"], "claims.acceptance")
    approval = _string(claims["approval"], "claims.approval")
    if acceptance not in ACCEPTANCE_CLAIMS or approval not in APPROVAL_CLAIMS:
        raise PacketError("E_VALUE", "claims")
    approval_ref = claims["approval_evidence_ref"]
    if approval_ref is not None:
        approval_ref = _symbol(approval_ref, "claims.approval_evidence_ref")

    required_results = [evidence_by_gate[gate_id]["result"] for gate_id in sorted(required_gate_ids)]
    all_required_pass = all(result == "pass" for result in required_results)
    if acceptance == "complete" and not all_required_pass:
        raise PacketError("E_ACCEPTANCE_CLAIM", "claims.acceptance")
    if approval == "approved":
        if approval_ref is None or approval_ref not in evidence_by_id:
            raise PacketError("E_APPROVAL_CLAIM", "claims.approval")
        approval_item = evidence_by_id[approval_ref]
        if approval_item["gate_id"] != "release-approval" or approval_item["result"] != "pass":
            raise PacketError("E_APPROVAL_CLAIM", "claims.approval")
    elif approval_ref is not None:
        raise PacketError("E_APPROVAL_CLAIM", "claims.approval_evidence_ref")

    if packet != original:
        raise PacketError("E_MUTATION", "packet")

    passed = sum(1 for gate_id in required_gate_ids if evidence_by_gate[gate_id]["result"] == "pass")
    incomplete = sum(
        1 for gate_id in required_gate_ids if evidence_by_gate[gate_id]["result"] == "incomplete"
    )
    failed = sum(1 for gate_id in required_gate_ids if evidence_by_gate[gate_id]["result"] == "fail")
    return {
        "decision": READY,
        "packet_id": packet_id,
        "artifact": artifact_name,
        "artifact_version": artifact_version,
        "publication_observation": publication_state,
        "acceptance_claim": acceptance,
        "approval_claim": approval,
        "required_gates": len(required_gate_ids),
        "passed_gates": passed,
        "incomplete_gates": incomplete,
        "failed_gates": failed,
        "release_ready_inferred": False,
        "approval_inferred": False,
        "next_gate": "human review of the identity-bound evidence packet",
    }


def render_text(summary: dict[str, Any]) -> str:
    lines = [
        summary["decision"],
        f"Packet: {summary['packet_id']}",
        f"Artifact: {summary['artifact']} {summary['artifact_version']}",
        f"Publication observation: {summary['publication_observation']} (supplied evidence)",
        f"Acceptance claim: {summary['acceptance_claim']}",
        f"Approval claim: {summary['approval_claim']}",
        f"Required gates: {summary['required_gates']}",
        f"Passed gates: {summary['passed_gates']}",
        f"Incomplete gates: {summary['incomplete_gates']}",
        f"Failed gates: {summary['failed_gates']}",
        "Release readiness inferred: no",
        "Approval inferred: no",
        f"Next gate: {summary['next_gate']}.",
    ]
    return "\n".join(lines)


def rejection(error: PacketError) -> dict[str, str]:
    return {"decision": NOT_READY, "error": error.code, "field": error.field}


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("packet", type=Path)
    parser.add_argument("--format", choices=("text", "json"), default="text")
    args = parser.parse_args(argv)
    try:
        raw = json.loads(args.packet.read_text(encoding="utf-8"))
        packet = _object(raw, "packet")
        result = validate_packet(packet)
    except (OSError, json.JSONDecodeError):
        result = {"decision": NOT_READY, "error": "E_INPUT", "field": "packet"}
        status = 2
    except PacketError as error:
        result = rejection(error)
        status = 1
    else:
        status = 0
    if args.format == "json":
        print(json.dumps(result, indent=2, sort_keys=True))
    elif status == 0:
        print(render_text(result))
    else:
        print(f"{result['decision']}\nError: {result['error']}\nField: {result['field']}")
    return status


if __name__ == "__main__":
    sys.exit(main())
run_lab.py python View source
#!/usr/bin/env python3
"""Run the fixed acceptance cases for the release-evidence teaching package."""

from __future__ import annotations

import json
from pathlib import Path

from release_evidence import PacketError, READY, validate_packet


ROOT = Path(__file__).resolve().parent


def load(name: str) -> dict:
    return json.loads((ROOT / "fixtures" / name).read_text(encoding="utf-8"))


def expect_ready(name: str) -> dict:
    result = validate_packet(load(name))
    assert result["decision"] == READY
    return result


def expect_error(name: str, code: str) -> None:
    expect_packet_error(load(name), code)


def expect_packet_error(packet: dict, code: str) -> None:
    try:
        validate_packet(packet)
    except PacketError as error:
        assert error.code == code, (error.code, code)
    else:
        raise AssertionError(f"packet unexpectedly passed instead of {code}")


def main() -> int:
    result = expect_ready("review-ready.json")
    conditions = [
        ("ready decision", result["decision"] == READY),
        ("publication observed", result["publication_observation"] == "prerelease"),
        ("acceptance remains incomplete", result["acceptance_claim"] == "incomplete"),
        ("approval remains unobserved", result["approval_claim"] == "unobserved"),
        ("four required gates", result["required_gates"] == 4),
        ("two passed gates", result["passed_gates"] == 2),
        ("two incomplete gates", result["incomplete_gates"] == 2),
        ("no failed gates", result["failed_gates"] == 0),
        ("no inferred release readiness", result["release_ready_inferred"] is False),
        ("no inferred approval", result["approval_inferred"] is False),
    ]
    for fixture, code in (
        ("missing-gate.json", "E_MISSING_GATE"),
        ("identity-mismatch.json", "E_IDENTITY"),
        ("manifest-mismatch.json", "E_IDENTITY"),
        ("stale-evidence.json", "E_STALE"),
        ("unsupported-approval.json", "E_APPROVAL_CLAIM"),
    ):
        expect_error(fixture, code)
        conditions.append((f"{fixture} rejected as {code}", True))

    no_required = load("review-ready.json")
    for gate in no_required["required_gates"]:
        gate["required"] = False
    expect_packet_error(no_required, "E_REQUIRED_GATE")
    conditions.append(("an all-optional contract is rejected", True))

    wrong_publication_gate = load("review-ready.json")
    wrong_publication_gate["publication_observation"]["evidence_ref"] = "archive-check"
    expect_packet_error(wrong_publication_gate, "E_PUBLICATION")
    conditions.append(("publication must cite release metadata", True))

    wrong_tag = load("review-ready.json")
    wrong_tag["publication_observation"]["tag"] = "1.4.0b4"
    expect_packet_error(wrong_tag, "E_TAG")
    conditions.append(("publication tag matches artifact identity", True))

    exact_age = load("review-ready.json")
    exact_age["evidence"][1]["observed_at"] = "2026-08-21T12:00:00Z"
    assert validate_packet(exact_age)["decision"] == READY
    conditions.append(("exact evidence-age boundary passes", True))

    over_age = load("review-ready.json")
    over_age["evidence"][1]["observed_at"] = "2026-08-21T11:59:59Z"
    expect_packet_error(over_age, "E_STALE")
    conditions.append(("one second over evidence age is stale", True))

    second = expect_ready("review-ready.json")
    conditions.extend(
        [
            ("deterministic decision", second == result),
            ("artifact identity retained", result["artifact_version"] == "1.4.0b3"),
            ("human review is next", result["next_gate"].startswith("human review")),
            ("publication is supplied evidence", result["publication_observation"] != "none"),
            ("success does not equal approval", result["approval_claim"] != "approved"),
            ("success does not equal complete acceptance", result["acceptance_claim"] != "complete"),
        ]
    )
    passed = 0
    for label, ok in conditions:
        if not ok:
            raise AssertionError(label)
        passed += 1
        print(f"PASS {label}")
    print(f"PASS {passed} of {len(conditions)} conditions")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
test_release_evidence.py python View source
from __future__ import annotations

import ast
import copy
import hashlib
import json
import tempfile
import unittest
from pathlib import Path

from release_evidence import NOT_READY, PacketError, READY, main, render_text, validate_packet


ROOT = Path(__file__).resolve().parent
FIXTURES = ROOT / "fixtures"


def load(name: str = "review-ready.json") -> dict:
    return json.loads((FIXTURES / name).read_text(encoding="utf-8"))


class ReleaseEvidenceTests(unittest.TestCase):
    def assert_code(self, packet: dict, code: str) -> None:
        with self.assertRaises(PacketError) as caught:
            validate_packet(packet)
        self.assertEqual(caught.exception.code, code)

    def test_ready_packet_is_review_ready(self) -> None:
        result = validate_packet(load())
        self.assertEqual(result["decision"], READY)
        self.assertEqual(result["passed_gates"], 2)
        self.assertEqual(result["incomplete_gates"], 2)
        self.assertFalse(result["release_ready_inferred"])
        self.assertFalse(result["approval_inferred"])

    def test_ready_result_is_deterministic(self) -> None:
        self.assertEqual(validate_packet(load()), validate_packet(load()))

    def test_text_never_emits_inferred_terminal_claims(self) -> None:
        text = render_text(validate_packet(load()))
        self.assertIn(READY, text)
        self.assertNotIn("RELEASE READY", text)
        self.assertNotIn("APPROVED", text)
        self.assertNotIn("PUBLISHED", text)

    def test_validation_does_not_mutate_input(self) -> None:
        packet = load()
        original = copy.deepcopy(packet)
        validate_packet(packet)
        self.assertEqual(packet, original)

    def test_missing_gate_is_rejected(self) -> None:
        self.assert_code(load("missing-gate.json"), "E_MISSING_GATE")

    def test_identity_mismatch_is_rejected(self) -> None:
        self.assert_code(load("identity-mismatch.json"), "E_IDENTITY")

    def test_manifest_mismatch_is_rejected(self) -> None:
        self.assert_code(load("manifest-mismatch.json"), "E_IDENTITY")

    def test_stale_evidence_is_rejected(self) -> None:
        self.assert_code(load("stale-evidence.json"), "E_STALE")

    def test_unsupported_approval_is_rejected(self) -> None:
        self.assert_code(load("unsupported-approval.json"), "E_APPROVAL_CLAIM")

    def test_complete_acceptance_requires_all_required_passes(self) -> None:
        packet = load()
        packet["claims"]["acceptance"] = "complete"
        self.assert_code(packet, "E_ACCEPTANCE_CLAIM")

    def test_complete_acceptance_is_declaratively_valid_when_all_pass(self) -> None:
        packet = load()
        for item in packet["evidence"]:
            item["result"] = "pass"
            item["architectures"] = ["test-architecture"]
        packet["claims"]["acceptance"] = "complete"
        result = validate_packet(packet)
        self.assertEqual(result["acceptance_claim"], "complete")
        self.assertFalse(result["release_ready_inferred"])

    def test_supported_approval_requires_matching_gate_evidence(self) -> None:
        packet = load()
        packet["evidence"].append(
            {
                "evidence_id": "approval-record",
                "gate_id": "release-approval",
                "artifact_version": packet["artifact"]["version"],
                "source_commit": packet["artifact"]["source_commit"],
                "archive_sha256": packet["artifact"]["archive_sha256"],
                "manifest_sha256": packet["artifact"]["manifest_sha256"],
                "observed_at": "2026-09-19T10:00:00Z",
                "result": "pass",
                "architectures": ["governance-record"],
            }
        )
        packet["claims"]["approval"] = "approved"
        packet["claims"]["approval_evidence_ref"] = "approval-record"
        result = validate_packet(packet)
        self.assertEqual(result["approval_claim"], "approved")
        self.assertFalse(result["approval_inferred"])

    def test_unobserved_approval_rejects_reference(self) -> None:
        packet = load()
        packet["claims"]["approval_evidence_ref"] = "public-release-record"
        self.assert_code(packet, "E_APPROVAL_CLAIM")

    def test_duplicate_gate_is_rejected(self) -> None:
        packet = load()
        packet["required_gates"].append(copy.deepcopy(packet["required_gates"][0]))
        self.assert_code(packet, "E_DUPLICATE")

    def test_duplicate_evidence_id_is_rejected(self) -> None:
        packet = load()
        duplicate = copy.deepcopy(packet["evidence"][0])
        duplicate["gate_id"] = "release-approval"
        packet["evidence"].append(duplicate)
        self.assert_code(packet, "E_DUPLICATE")

    def test_duplicate_gate_evidence_is_rejected(self) -> None:
        packet = load()
        duplicate = copy.deepcopy(packet["evidence"][0])
        duplicate["evidence_id"] = "another-record"
        packet["evidence"].append(duplicate)
        self.assert_code(packet, "E_DUPLICATE")

    def test_duplicate_architecture_is_rejected(self) -> None:
        packet = load()
        packet["evidence"][0]["architectures"] = ["public-metadata", "public-metadata"]
        self.assert_code(packet, "E_DUPLICATE")

    def test_pass_requires_architecture_or_evidence_scope(self) -> None:
        packet = load()
        packet["evidence"][0]["architectures"] = []
        self.assert_code(packet, "E_EVIDENCE")

    def test_unknown_gate_reference_is_rejected(self) -> None:
        packet = load()
        packet["evidence"][0]["gate_id"] = "unknown-gate"
        self.assert_code(packet, "E_GATE")

    def test_publication_reference_must_exist(self) -> None:
        packet = load()
        packet["publication_observation"]["evidence_ref"] = "missing-record"
        self.assert_code(packet, "E_REFERENCE")

    def test_prerelease_reference_must_pass(self) -> None:
        packet = load()
        packet["evidence"][0]["result"] = "incomplete"
        packet["evidence"][0]["architectures"] = []
        self.assert_code(packet, "E_PUBLICATION")

    def test_optional_gate_may_lack_evidence(self) -> None:
        result = validate_packet(load())
        self.assertEqual(result["required_gates"], 4)

    def test_at_least_one_gate_must_be_required(self) -> None:
        packet = load()
        for gate in packet["required_gates"]:
            gate["required"] = False
        self.assert_code(packet, "E_REQUIRED_GATE")

    def test_prerelease_reference_must_name_release_metadata(self) -> None:
        packet = load()
        packet["publication_observation"]["evidence_ref"] = "archive-check"
        self.assert_code(packet, "E_PUBLICATION")

    def test_publication_tag_must_match_artifact_release_tag(self) -> None:
        packet = load()
        packet["publication_observation"]["tag"] = "1.4.0b4"
        self.assert_code(packet, "E_TAG")

    def test_evidence_at_exact_age_boundary_passes(self) -> None:
        packet = load()
        packet["evidence"][1]["observed_at"] = "2026-08-21T12:00:00Z"
        result = validate_packet(packet)
        self.assertEqual(result["decision"], READY)

    def test_evidence_past_exact_age_boundary_is_stale(self) -> None:
        packet = load()
        packet["evidence"][1]["observed_at"] = "2026-08-21T11:59:59Z"
        self.assert_code(packet, "E_STALE")

    def test_future_evidence_is_rejected(self) -> None:
        packet = load()
        packet["evidence"][0]["observed_at"] = "2026-09-21T12:00:00Z"
        self.assert_code(packet, "E_TIME_ORDER")

    def test_future_publication_observation_is_rejected(self) -> None:
        packet = load()
        packet["publication_observation"]["observed_at"] = "2026-09-21T12:00:00Z"
        self.assert_code(packet, "E_TIME_ORDER")

    def test_invalid_calendar_time_is_rejected(self) -> None:
        packet = load()
        packet["evaluation_time"] = "2026-02-30T12:00:00Z"
        self.assert_code(packet, "E_TIME")

    def test_boolean_max_age_is_rejected(self) -> None:
        packet = load()
        packet["max_evidence_age_days"] = True
        self.assert_code(packet, "E_RANGE")

    def test_invalid_commit_is_rejected(self) -> None:
        packet = load()
        packet["artifact"]["source_commit"] = "short"
        self.assert_code(packet, "E_DIGEST")

    def test_invalid_digest_is_rejected(self) -> None:
        packet = load()
        packet["artifact"]["archive_sha256"] = "not-a-digest"
        self.assert_code(packet, "E_DIGEST")

    def test_invalid_version_is_rejected(self) -> None:
        packet = load()
        packet["artifact"]["version"] = "latest"
        self.assert_code(packet, "E_VERSION")

    def test_unknown_top_field_is_rejected(self) -> None:
        packet = load()
        packet["extra"] = "value"
        self.assert_code(packet, "E_FIELDS")

    def test_unknown_nested_field_is_rejected(self) -> None:
        packet = load()
        packet["artifact"]["path"] = "artifact"
        self.assert_code(packet, "E_FIELDS")

    def test_absolute_path_is_rejected_without_echo(self) -> None:
        packet = load()
        packet["packet_id"] = "/Users/example/private"
        with self.assertRaises(PacketError) as caught:
            validate_packet(packet)
        self.assertEqual(caught.exception.code, "E_PRIVATE")
        self.assertNotIn("Users", str(caught.exception))

    def test_ip_address_is_rejected(self) -> None:
        packet = load()
        packet["packet_id"] = "host-192.0.2.1"
        self.assert_code(packet, "E_PRIVATE")

    def test_uri_is_rejected(self) -> None:
        packet = load()
        packet["packet_id"] = "https://example.invalid/release"
        self.assert_code(packet, "E_PRIVATE")

    def test_secret_assignment_is_rejected(self) -> None:
        packet = load()
        packet["packet_id"] = "token=example"
        self.assert_code(packet, "E_PRIVATE")

    def test_runtime_source_has_no_execution_or_network_import(self) -> None:
        tree = ast.parse((ROOT / "release_evidence.py").read_text(encoding="utf-8"))
        banned = {"subprocess", "socket", "http", "urllib", "requests", "ssh", "paramiko"}
        imports = set()
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                imports.update(alias.name.split(".")[0] for alias in node.names)
            elif isinstance(node, ast.ImportFrom) and node.module:
                imports.add(node.module.split(".")[0])
        self.assertFalse(imports & banned)

    def test_manifest_hashes_match_listed_files(self) -> None:
        manifest = json.loads((ROOT / "manifest.json").read_text(encoding="utf-8"))
        for relative, expected in manifest["files"].items():
            actual = hashlib.sha256((ROOT / relative).read_bytes()).hexdigest()
            self.assertEqual(actual, expected, relative)

    def test_cli_rejects_malformed_json_without_traceback(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            path = Path(directory) / "bad.json"
            path.write_text("{", encoding="utf-8")
            self.assertEqual(main([str(path), "--format", "json"]), 2)


if __name__ == "__main__":
    unittest.main()
review-ready.json json View source
{
  "schema_version": 1,
  "packet_id": "example-prerelease-review",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true},
    {"gate_id": "archive-integrity", "required": true},
    {"gate_id": "isolated-install", "required": true},
    {"gate_id": "lifecycle-matrix", "required": true},
    {"gate_id": "release-approval", "required": false}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-18T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    },
    {
      "evidence_id": "archive-check",
      "gate_id": "archive-integrity",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-18T10:15:00Z",
      "result": "pass",
      "architectures": ["artifact"]
    },
    {
      "evidence_id": "install-pending",
      "gate_id": "isolated-install",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-19T09:00:00Z",
      "result": "incomplete",
      "architectures": []
    },
    {
      "evidence_id": "lifecycle-pending",
      "gate_id": "lifecycle-matrix",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-19T09:05:00Z",
      "result": "incomplete",
      "architectures": []
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "unobserved",
    "approval_evidence_ref": null
  }
}
missing-gate.json json View source
{
  "schema_version": 1,
  "packet_id": "missing-gate",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true},
    {"gate_id": "isolated-install", "required": true}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-18T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "unobserved",
    "approval_evidence_ref": null
  }
}
identity-mismatch.json json View source
{
  "schema_version": 1,
  "packet_id": "identity-mismatch",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b2",
      "source_commit": "2222222222222222222222222222222222222222",
      "archive_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
      "manifest_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
      "observed_at": "2026-09-18T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "unobserved",
    "approval_evidence_ref": null
  }
}
manifest-mismatch.json json View source
{
  "schema_version": 1,
  "packet_id": "manifest-mismatch",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
      "observed_at": "2026-09-18T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "unobserved",
    "approval_evidence_ref": null
  }
}
stale-evidence.json json View source
{
  "schema_version": 1,
  "packet_id": "stale-evidence",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-07-01T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "unobserved",
    "approval_evidence_ref": null
  }
}
unsupported-approval.json json View source
{
  "schema_version": 1,
  "packet_id": "unsupported-approval",
  "evaluation_time": "2026-09-20T12:00:00Z",
  "max_evidence_age_days": 30,
  "artifact": {
    "name": "example-ops-kit",
    "version": "1.4.0b3",
    "release_tag": "1.4.0b3",
    "source_commit": "1111111111111111111111111111111111111111",
    "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "publication_observation": {
    "state": "prerelease",
    "tag": "1.4.0b3",
    "observed_at": "2026-09-18T10:00:00Z",
    "evidence_ref": "public-release-record"
  },
  "required_gates": [
    {"gate_id": "release-metadata", "required": true}
  ],
  "evidence": [
    {
      "evidence_id": "public-release-record",
      "gate_id": "release-metadata",
      "artifact_version": "1.4.0b3",
      "source_commit": "1111111111111111111111111111111111111111",
      "archive_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "manifest_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "observed_at": "2026-09-18T10:00:00Z",
      "result": "pass",
      "architectures": ["public-metadata"]
    }
  ],
  "claims": {
    "acceptance": "incomplete",
    "approval": "approved",
    "approval_evidence_ref": null
  }
}

1. Start With the Boundary

The package reads one local JSON document and prints a deterministic result. It does not build software, install an archive, inspect Git, query CI, make a network request, sign anything, upload anything, approve anything, or publish anything.

That is not a missing feature. It is the point of the exercise. A packet validator should be able to say, “These records refer to the same artifact, every required gate has an entry, and the claims agree with the evidence.” It should not be able to promote an artifact because somebody chose an optimistic filename.

The package contains eleven files:

release-evidence-ledger/
|-- README.md
|-- manifest.json
|-- release_evidence.py
|-- run_lab.py
|-- test_release_evidence.py
`-- fixtures/
    |-- review-ready.json
    |-- missing-gate.json
    |-- identity-mismatch.json
    |-- manifest-mismatch.json
    |-- stale-evidence.json
    `-- unsupported-approval.json

It requires Python 3.10 or newer and uses only the standard library.

2. Verify and Extract the Package

Place the reviewed ZIP and checksum in one directory, then verify the archive before extracting it:

shasum -a 256 -c release-evidence-ledger.zip.sha256
mkdir release-evidence-lab
cd release-evidence-lab
unzip ../release-evidence-ledger.zip
cd release-evidence-ledger
python3 --version

Nothing needs to be installed with pip. The package manifest lists the files and their SHA-256 values, while the outer checksum binds the ZIP readers actually download.

3. Read the Review-Ready Fixture

Open fixtures/review-ready.json. The artifact is invented. Its version, commit, archive digest, manifest digest, evidence identifiers, and dates do not describe a real project or deployment.

The packet declares four required gates. Artifact identity and publication observation have passing evidence. Installation and lifecycle acceptance remain incomplete. The publication observation says that a prerelease was seen, but the approval claim remains unobserved.

That combination is intentional. A prerelease can be public while the larger acceptance record remains unfinished. The packet does not need to deny the publication event to preserve that distinction.

Each evidence item repeats the artifact version, source commit, archive digest, and manifest digest. The publication observation must also name the release tag declared by the artifact and point to passing release-metadata evidence. This is tedious in exactly the right way. It prevents a result from an older candidate or an unrelated publication record from drifting into the current packet just because the gate name looks familiar.

4. Validate the Clean Packet

Run the validator:

python3 -B release_evidence.py fixtures/review-ready.json

The report should begin like this:

EVIDENCE PACKET READY FOR REVIEW
Packet: example-release-review
Artifact: example-control-kit 1.4.0b3
Publication observation: prerelease (supplied evidence)
Acceptance claim: incomplete
Approval claim: unobserved
Required gates: 4
Passed gates: 2
Incomplete gates: 2
Failed gates: 0
Release readiness inferred: no
Approval inferred: no
Next gate: human review of the identity-bound evidence packet.

The first line is not a synonym for release readiness. It says a human can now inspect a coherent packet. The two no lines make the stopping point hard to miss.

For structured output, add --format json:

python3 -B release_evidence.py fixtures/review-ready.json --format json

That form is useful in a review workflow because a later tool can display the counts and claims without scraping terminal prose. It still carries the same bounded decision.

5. Remove a Required Gate

The first broken fixture declares a required gate but supplies no corresponding evidence record:

python3 -B release_evidence.py fixtures/missing-gate.json
test $? -eq 1

The validator returns:

EVIDENCE PACKET NOT READY
Error: E_MISSING_GATE
Field: evidence

An absent record remains absent. The validator does not reinterpret silence as not applicable, borrow a similarly named result, or reduce the gate count until the packet passes.

That last behavior matters. A release process becomes meaningless if the easiest way to satisfy it is to stop declaring the inconvenient checks.

6. Mix Two Artifact Identities

Now run the identity mismatch:

python3 -B release_evidence.py fixtures/identity-mismatch.json
test $? -eq 1

This fixture changes the version, source commit, archive digest, and manifest digest on one evidence record. The result is E_IDENTITY, even though the gate name and reported result still look plausible.

The supplied manifest-mismatch.json fixture narrows the same test to one field:

python3 -B release_evidence.py fixtures/manifest-mismatch.json
test $? -eq 1

It changes only the evidence record’s manifest digest and receives the same E_IDENTITY result. In a real release packet, the matching tuple includes the version, source commit, archive digest, and manifest digest. Architecture and observation time then describe where and when that exact artifact was exercised. If any of those facts are unknown, the honest state is unknown. A nearby result from another build is useful history, but it is not exact-artifact acceptance.

7. Let Evidence Age Out

Evidence also has a time boundary. The clean packet supplies an evaluation time and a maximum evidence age. The stale fixture moves one observation outside that window:

python3 -B release_evidence.py fixtures/stale-evidence.json
test $? -eq 1

The validator returns E_STALE. It does not decide that every engineering result expires after the same number of days. The packet declares its own review window, and the validator enforces the full duration: evidence exactly thirty days old passes a thirty-day limit, while evidence thirty days and one second old does not.

This gives reviewers a visible place to argue about policy. If ninety days is appropriate for one gate and not another, the schema should evolve deliberately. Quietly accepting an old observation because rerunning it is inconvenient is not a policy.

8. Try to Invent Approval

The final supplied failure case claims approval without a matching approval record:

python3 -B release_evidence.py fixtures/unsupported-approval.json
test $? -eq 1

The result is E_APPROVAL_CLAIM. The validator permits an approval claim only when the packet contains a passing release-approval evidence item and the claim points to that exact evidence identifier.

The clean fixture takes the safer path. It records the public prerelease observation and leaves approval provenance unobserved. That does not imply the publication was unauthorized. It means this packet does not contain the decision record, so the program refuses to reconstruct one from the public result.

9. Keep Private Material Out of the Packet

The fixtures use symbolic identifiers rather than paths, hosts, addresses, URLs, credentials, or real evidence locations. The validator rejects common private-looking forms such as absolute home and runtime paths, IP addresses, URI authorities, credential assignments, and private-key markers.

Rejected values are not echoed in the bounded error output. This is defense in depth for a teaching package, not a complete secret scanner. A real collector still needs an explicit privacy review before its records are attached to a public issue, release, or article.

The right public evidence reference is often a neutral identifier such as ci-run-tag or archive-checksum-record, with the private storage location retained separately under normal access controls.

10. Run the Fixed Walkthrough

The runner exercises the clean packet and the four supplied failures:

python3 -B run_lab.py

It currently checks twenty-six conditions. Those checks include the observed prerelease state, incomplete acceptance, unobserved approval, gate counts, deterministic output, full artifact identity, five supplied failure fixtures, a contract with no required gate, a publication reference to the wrong gate, a mismatched release tag, both sides of the exact age boundary, and the explicit refusal to infer readiness or approval.

Then run the unit suite:

python3 -B -m unittest -v test_release_evidence.py

The forty-three tests cover closed fields, strict types, malformed timestamps and digests, duplicate gates and evidence, a contract with no required gate, missing records, full identity mismatch, exact-duration staleness, future evidence, publication gate and tag binding, unsupported acceptance and approval claims, privacy rejection, deterministic non-mutating validation, command-line behavior, source boundaries, and manifest integrity.

An abstract-syntax-tree test checks that the runtime does not import network, subprocess, SSH, HTTP, Git, or project-control surfaces. That is a bounded review of this package, not a claim that source scanning can prove every possible Python behavior.

11. Adapt the Ledger Without Turning It Into a Release Bot

The fixture is small enough to understand in one sitting, but its separation is useful in a larger workflow:

Packet section What supplies it What the validator may conclude
Artifact identity Build or release metadata retained elsewhere Evidence either matches the declared artifact or it does not
Publication observation A retained observation supplied to the packet The stated tag or release state was observed according to that evidence
Required gates The reviewed release contract Every required gate has one unambiguous record
Evidence Test, inspection, and review procedures Results are current enough and identity-bound
Claims The packet author Acceptance and approval claims have the required support

I would keep evidence collection separate from this validator. Collectors may need network access, Git, CI credentials, build tools, target hosts, or privileged inspection. Mixing those powers into the decision layer makes the result harder to reproduce and much harder to audit.

I would also keep human authorization outside the automatic success path. A validator can establish that an approval record exists and matches the packet. It cannot decide that the approver had the right authority, understood the remaining risk, or intended to authorize this exact public action unless the surrounding process defines and reviews those facts.

12. Clean Up

Return to the directory above the disposable lab and remove it:

cd ../..
rm -rf release-evidence-lab

The walkthrough changes no service, repository, release, or remote state. The only files created are the extracted teaching package inside the directory you remove.

Current State

The companion validates an invented release-evidence packet with one clean case and five focused failures. It binds results to an exact artifact identity, requires at least one required gate and one record for every required gate, enforces a declared evidence-age window without truncating partial days, binds publication observations to the expected tag and release-metadata evidence, keeps publication observation separate from acceptance, and rejects unsupported approval claims.

Its successful state is EVIDENCE PACKET READY FOR REVIEW. It never infers RELEASE READY, APPROVED, or PUBLISHED, and it performs no operation that could produce any of those states.

Next Work

A production version would need a reviewed schema for the project’s real gates, retention policy, architectures, evidence types, authority model, and privacy boundary. Separate collectors could then produce candidate records, while this layer remained deterministic and offline.

The next gate would still be human review. Only after the exact-artifact acceptance record and the authorization record are complete should a separate, explicitly authorized release procedure do anything public.