From 90d7f2e860d8185f73473370dc369f81973ec50b Mon Sep 17 00:00:00 2001 From: babyblueviper1 Date: Mon, 12 Oct 2026 18:11:43 +0000 Subject: [PATCH] conformance: v0.3 draft fixtures for the manifest reference (#483) Six fixtures for the optional signed manifest {id, digest, media_type}: digest match, mismatch, mismatch with a signed producer-asserted result, unresolvable (not a pass, record not rejected), manifest added after signing, and a no-manifest control. The draft rule is stated as a reference checker in the test; four wrong checkers are each caught. Signed with the repo's rfc8785 dependency; records are byte-identical to babyblueviper1/preaction-governance-conformance@2cd4ae7. Co-Authored-By: Claude Opus 5.5 Signed-off-by: babyblueviper1 --- .../tests/test_manifest_ref_draft_vectors.py | 97 ++++++++++++ .../01-digest-matches.json | 52 +++++++ .../02-digest-mismatch.json | 52 +++++++ .../03-mismatch-with-asserted-result.json | 53 +++++++ .../04-unresolvable.json | 50 ++++++ .../05-added-after-signing.json | 52 +++++++ .../06-control-no-manifest.json | 47 ++++++ .../vectors/manifest-ref-v0.3-draft/README.md | 28 ++++ .../manifest-ref-v0.3-draft/generate.py | 147 ++++++++++++++++++ 9 files changed, 578 insertions(+) create mode 100644 conformance/tests/test_manifest_ref_draft_vectors.py create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/01-digest-matches.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/02-digest-mismatch.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/03-mismatch-with-asserted-result.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/04-unresolvable.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/05-added-after-signing.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/06-control-no-manifest.json create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/README.md create mode 100644 conformance/tests/vectors/manifest-ref-v0.3-draft/generate.py diff --git a/conformance/tests/test_manifest_ref_draft_vectors.py b/conformance/tests/test_manifest_ref_draft_vectors.py new file mode 100644 index 00000000..66e45967 --- /dev/null +++ b/conformance/tests/test_manifest_ref_draft_vectors.py @@ -0,0 +1,97 @@ +"""v0.3 draft fixtures for trace-spec#483: a signed record names the agent manifest it ran under. + +No module implements the reference yet, so the draft rule is stated here as a small reference +checker. The record signature is verified first (§3.3 step 1); the manifest reference is then +appraised by recomputing sha256 over the bytes the resolver returns for ``manifest.id``. A +``manifest_verification_result`` the producer wrote is never read. Four wrong checkers must each +be caught by at least one fixture. +""" + +import base64 +import copy +import hashlib +import importlib.util +import json +from pathlib import Path + +import pytest +import rfc8785 +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + +VECTOR_DIR = Path(__file__).parent / "vectors" / "manifest-ref-v0.3-draft" +VECTORS = [json.loads(p.read_text()) for p in sorted(VECTOR_DIR.glob("[0-9][0-9]-*.json"))] +MUTANTS = ( + "trusts_asserted_result", + "unresolved_is_pass", + "manifest_before_signature", + "rejects_record_on_unresolved", +) + + +def _b64u(s: str) -> bytes: + b = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) + if base64.urlsafe_b64encode(b).rstrip(b"=").decode() != s: + raise ValueError("non-canonical base64url") + return b + + +def _signature_ok(rec: dict) -> bool: + try: + jwk = rec["cnf"]["jwk"] + if jwk.get("kty") != "OKP" or jwk.get("crv") != "Ed25519": + return False + body = {k: v for k, v in rec.items() if k != "signature"} + Ed25519PublicKey.from_public_bytes(_b64u(jwk["x"])).verify( + _b64u(rec["signature"]), rfc8785.dumps(body) + ) + return True + except Exception: + return False + + +def check(vector: dict, mutant: str | None = None) -> dict: + rec, store = vector["record"], vector["resolver"] + if mutant != "manifest_before_signature" and not _signature_ok(rec): + return {"record": "signature-invalid", "manifest": "not-evaluated"} + ref = rec.get("manifest") + if ref is None: + return {"record": "valid", "manifest": "no-manifest-named"} + if mutant == "trusts_asserted_result" and rec.get("manifest_verification_result") == "verified": + return {"record": "valid", "manifest": "established"} + if ref.get("id") not in store: + if mutant == "unresolved_is_pass": + return {"record": "valid", "manifest": "established"} + if mutant == "rejects_record_on_unresolved": + return {"record": "manifest-unresolved", "manifest": "not-evaluated"} + return {"record": "valid", "manifest": "manifest-unresolved"} + got = "sha256:" + hashlib.sha256(store[ref["id"]].encode("utf-8")).hexdigest() + return { + "record": "valid", + "manifest": "established" if got == ref.get("digest") else "manifest-digest-mismatch", + } + + +@pytest.mark.parametrize("vector", VECTORS, ids=lambda v: v["name"]) +def test_manifest_reference_vector(vector): + assert check(vector) == vector["expected"] + + +@pytest.mark.parametrize("mutant", MUTANTS) +def test_each_wrong_checker_is_caught(mutant): + assert any(check(v, mutant) != v["expected"] for v in VECTORS) + + +def test_editing_the_signed_digest_fails_the_signature(): + rec = copy.deepcopy(VECTORS[0]["record"]) + d = rec["manifest"]["digest"] + rec["manifest"]["digest"] = d[:-1] + ("0" if d[-1] != "0" else "1") + assert not _signature_ok(rec) + + +def test_fixtures_are_reproducible(): + spec = importlib.util.spec_from_file_location( + "manifest_ref_generator", VECTOR_DIR / "generate.py" + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + assert module.vectors() == VECTORS diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/01-digest-matches.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/01-digest-matches.json new file mode 100644 index 00000000..14716071 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/01-digest-matches.json @@ -0,0 +1,52 @@ +{ + "name": "01-digest-matches", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "manifest": { + "id": "urn:example:agent-manifest:payments-agent", + "digest": "sha256:a773501007076390d7532a16294e4ee8c1ae07aa030f90c85cf513fdeeeeb472", + "media_type": "application/json" + }, + "signature": "wnkUTs2U7NvWLbQb5baCkvn3TtiuP5Wb5ktJ_yVT-SO77noVRCACo5-upQJqoveMTrwnrN_i8RtL5Ta0zV8nDw" + }, + "resolver": { + "urn:example:agent-manifest:payments-agent": "{\"manifest_id\":\"urn:example:agent-manifest:payments-agent\",\"version\":\"1.4.0\",\"tools\":[\"refund\",\"lookup_payment\"],\"model_id\":\"example-model-1\"}" + }, + "expected": { + "record": "valid", + "manifest": "established" + }, + "why": "the signed digest equals sha256 of the manifest bytes the verifier resolved" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/02-digest-mismatch.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/02-digest-mismatch.json new file mode 100644 index 00000000..5e4b9299 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/02-digest-mismatch.json @@ -0,0 +1,52 @@ +{ + "name": "02-digest-mismatch", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "manifest": { + "id": "urn:example:agent-manifest:payments-agent", + "digest": "sha256:a773501007076390d7532a16294e4ee8c1ae07aa030f90c85cf513fdeeeeb472", + "media_type": "application/json" + }, + "signature": "wnkUTs2U7NvWLbQb5baCkvn3TtiuP5Wb5ktJ_yVT-SO77noVRCACo5-upQJqoveMTrwnrN_i8RtL5Ta0zV8nDw" + }, + "resolver": { + "urn:example:agent-manifest:payments-agent": "{\"manifest_id\":\"urn:example:agent-manifest:payments-agent\",\"version\":\"1.5.0\",\"tools\":[\"refund\",\"lookup_payment\",\"wire_transfer\"],\"model_id\":\"example-model-1\"}" + }, + "expected": { + "record": "valid", + "manifest": "manifest-digest-mismatch" + }, + "why": "the id now resolves to a later deployment (1.5.0 adds wire_transfer); the record ran under 1.4.0" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/03-mismatch-with-asserted-result.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/03-mismatch-with-asserted-result.json new file mode 100644 index 00000000..c3774748 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/03-mismatch-with-asserted-result.json @@ -0,0 +1,53 @@ +{ + "name": "03-mismatch-with-asserted-result", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "manifest": { + "id": "urn:example:agent-manifest:payments-agent", + "digest": "sha256:a773501007076390d7532a16294e4ee8c1ae07aa030f90c85cf513fdeeeeb472", + "media_type": "application/json" + }, + "manifest_verification_result": "verified", + "signature": "6i9nltz5j8ZeiVN-_Htg8v5qrmfm8w82EeKRHj-3rz3BB8EK68pWrKM96kY_U2ZvEtgahRUArceT1oSeYQy_AQ" + }, + "resolver": { + "urn:example:agent-manifest:payments-agent": "{\"manifest_id\":\"urn:example:agent-manifest:payments-agent\",\"version\":\"1.5.0\",\"tools\":[\"refund\",\"lookup_payment\",\"wire_transfer\"],\"model_id\":\"example-model-1\"}" + }, + "expected": { + "record": "valid", + "manifest": "manifest-digest-mismatch" + }, + "why": "the producer also signed manifest_verification_result=verified; the verifier recomputes and does not carry it" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/04-unresolvable.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/04-unresolvable.json new file mode 100644 index 00000000..6f83f48b --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/04-unresolvable.json @@ -0,0 +1,50 @@ +{ + "name": "04-unresolvable", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "manifest": { + "id": "urn:example:agent-manifest:payments-agent", + "digest": "sha256:a773501007076390d7532a16294e4ee8c1ae07aa030f90c85cf513fdeeeeb472", + "media_type": "application/json" + }, + "signature": "wnkUTs2U7NvWLbQb5baCkvn3TtiuP5Wb5ktJ_yVT-SO77noVRCACo5-upQJqoveMTrwnrN_i8RtL5Ta0zV8nDw" + }, + "resolver": {}, + "expected": { + "record": "valid", + "manifest": "manifest-unresolved" + }, + "why": "not a pass, and not a reason to reject the record (the §3.1.2 rule 3 posture for pointers)" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/05-added-after-signing.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/05-added-after-signing.json new file mode 100644 index 00000000..fb91e701 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/05-added-after-signing.json @@ -0,0 +1,52 @@ +{ + "name": "05-added-after-signing", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "signature": "1MgcfqL71SogLyNuz1p1a5xcqlBbdZDu8ffG_K4gvl8SAUEE-iUM5vkKMIMD6DiqkEeqUB-escskzwoSgVM9CQ", + "manifest": { + "id": "urn:example:agent-manifest:payments-agent", + "digest": "sha256:a773501007076390d7532a16294e4ee8c1ae07aa030f90c85cf513fdeeeeb472", + "media_type": "application/json" + } + }, + "resolver": { + "urn:example:agent-manifest:payments-agent": "{\"manifest_id\":\"urn:example:agent-manifest:payments-agent\",\"version\":\"1.4.0\",\"tools\":[\"refund\",\"lookup_payment\"],\"model_id\":\"example-model-1\"}" + }, + "expected": { + "record": "signature-invalid", + "manifest": "not-evaluated" + }, + "why": "manifest is outside the signed bytes; §3.3 step 1 fails before any field is trusted" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/06-control-no-manifest.json b/conformance/tests/vectors/manifest-ref-v0.3-draft/06-control-no-manifest.json new file mode 100644 index 00000000..ee0320c8 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/06-control-no-manifest.json @@ -0,0 +1,47 @@ +{ + "name": "06-control-no-manifest", + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": { + "provider": "example-provider", + "model_id": "example-model-1" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "enforcement_mode": "enforce" + }, + "data_class": "confidential", + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "appraisal": { + "status": "affirming", + "verifier": "https://verifier.example/v1" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "mcKFJmGaHdWlVL-4I-ETFbbmCsVheweKmgdxHkcqEBc" + } + }, + "signature": "1MgcfqL71SogLyNuz1p1a5xcqlBbdZDu8ffG_K4gvl8SAUEE-iUM5vkKMIMD6DiqkEeqUB-escskzwoSgVM9CQ" + }, + "resolver": { + "urn:example:agent-manifest:payments-agent": "{\"manifest_id\":\"urn:example:agent-manifest:payments-agent\",\"version\":\"1.4.0\",\"tools\":[\"refund\",\"lookup_payment\"],\"model_id\":\"example-model-1\"}" + }, + "expected": { + "record": "valid", + "manifest": "no-manifest-named" + }, + "why": "a v0.2 record with no manifest object verifies as today and names no deployment" +} diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/README.md b/conformance/tests/vectors/manifest-ref-v0.3-draft/README.md new file mode 100644 index 00000000..4a15a2cb --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/README.md @@ -0,0 +1,28 @@ +# Manifest reference, v0.3 draft fixtures (trace-spec#483) + +Six fixtures for an optional signed top-level `manifest {id, digest, media_type}` that names the agent manifest a +record ran under. Status: **v0.3 draft**. No conformance module implements the reference yet; +`tests/test_manifest_ref_draft_vectors.py` states the draft rule as a small reference checker. + +The rule under test is reference-only. The verifier checks the record signature first (§3.3 step 1). It then +recomputes sha256 over the bytes the manifest id resolves to and compares that with the signed digest. A +`manifest_verification_result` written by the producer is never read (agentrust-io/agent-manifest#498: a verifier +MUST NOT report the producer's result as its own). + +| Fixture | Record | Manifest | +|---|---|---| +| 01-digest-matches | valid | established | +| 02-digest-mismatch | valid | manifest-digest-mismatch | +| 03-mismatch-with-asserted-result | valid | manifest-digest-mismatch | +| 04-unresolvable | valid | manifest-unresolved (not a pass; the record is not rejected for it) | +| 05-added-after-signing | signature-invalid | not-evaluated | +| 06-control-no-manifest | valid | no-manifest-named | + +Each of four wrong checkers is caught by at least one fixture: one trusts the asserted result, one treats an +unresolvable manifest as a pass, one reads the manifest before the signature, and one rejects the record when the +manifest is unresolvable. + +Regenerate with `python tests/vectors/manifest-ref-v0.3-draft/generate.py`. Signing uses the `rfc8785` dependency +and a public test key derived from a fixed seed. The records are byte-identical to +[`trace-manifest-ref-v0`](https://github.com/babyblueviper1/preaction-governance-conformance/tree/2cd4ae7f47551903cb10627ef619d7396270528a/examples/trace-manifest-ref-v0) +at `2cd4ae7`, where they were first published. diff --git a/conformance/tests/vectors/manifest-ref-v0.3-draft/generate.py b/conformance/tests/vectors/manifest-ref-v0.3-draft/generate.py new file mode 100644 index 00000000..165f4067 --- /dev/null +++ b/conformance/tests/vectors/manifest-ref-v0.3-draft/generate.py @@ -0,0 +1,147 @@ +"""Regenerate the v0.3 draft manifest-reference fixtures (trace-spec#483) without importing the +verifier under test. + +Shape under test: an optional signed top-level ``manifest {id, digest, media_type}`` naming the +agent manifest the record ran under. Records are v0.2 records signed embedded per §3.2.2: +Ed25519 over RFC 8785 of the record with ``signature`` absent, base64url without padding, key in +``cnf.jwk``. The signing key is a PUBLIC TEST KEY derived from a fixed seed; never use it for +anything else. +""" + +from __future__ import annotations + +import base64 +import copy +import hashlib +import json +from pathlib import Path + +import rfc8785 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + +HERE = Path(__file__).parent +SEED = hashlib.sha256(b"trace-manifest-ref-v0 test key").digest() +KEY = Ed25519PrivateKey.from_private_bytes(SEED) +PUB = KEY.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) +MID = "urn:example:agent-manifest:payments-agent" + + +def b64u(b: bytes) -> str: + return base64.urlsafe_b64encode(b).rstrip(b"=").decode() + + +def digest(b: bytes) -> str: + return "sha256:" + hashlib.sha256(b).hexdigest() + + +def manifest(version: str, tools: list[str]) -> str: + return json.dumps( + {"manifest_id": MID, "version": version, "tools": tools, "model_id": "example-model-1"}, + separators=(",", ":"), + ) + + +# Two manifests served under the same id: the deployment the record ran under, and a later one. +M_RAN = manifest("1.4.0", ["refund", "lookup_payment"]) +M_LATER = manifest("1.5.0", ["refund", "lookup_payment", "wire_transfer"]) + +BASE = { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1785000000, + "subject": "spiffe://factory.example/agent/payments/prod", + "model": {"provider": "example-provider", "model_id": "example-model-1"}, + "runtime": {"platform": "software-only", "measurement": "sha256:" + "0" * 64}, + "policy": {"bundle_hash": "sha256:" + "a" * 64, "enforcement_mode": "enforce"}, + "data_class": "confidential", + "build_provenance": {"slsa_level": 0, "digest": "sha256:" + "b" * 64}, + "appraisal": {"status": "affirming", "verifier": "https://verifier.example/v1"}, + "cnf": {"jwk": {"kty": "OKP", "crv": "Ed25519", "x": b64u(PUB)}}, +} +REF = {"id": MID, "digest": digest(M_RAN.encode()), "media_type": "application/json"} + + +def sign(rec: dict) -> dict: + r = copy.deepcopy(rec) + r.pop("signature", None) + r["signature"] = b64u(KEY.sign(rfc8785.dumps(r))) + return r + + +def with_manifest(**extra) -> dict: + return {**copy.deepcopy(BASE), "manifest": dict(REF), **extra} + + +def vectors() -> list[dict]: + added_after = sign(BASE) + added_after["manifest"] = dict(REF) + cases = [ + ( + "01-digest-matches", + sign(with_manifest()), + {MID: M_RAN}, + "valid", + "established", + "the signed digest equals sha256 of the manifest bytes the verifier resolved", + ), + ( + "02-digest-mismatch", + sign(with_manifest()), + {MID: M_LATER}, + "valid", + "manifest-digest-mismatch", + "the id now resolves to a later deployment (1.5.0 adds wire_transfer); " + "the record ran under 1.4.0", + ), + ( + "03-mismatch-with-asserted-result", + sign(with_manifest(manifest_verification_result="verified")), + {MID: M_LATER}, + "valid", + "manifest-digest-mismatch", + "the producer also signed manifest_verification_result=verified; " + "the verifier recomputes and does not carry it", + ), + ( + "04-unresolvable", + sign(with_manifest()), + {}, + "valid", + "manifest-unresolved", + "not a pass, and not a reason to reject the record " + "(the §3.1.2 rule 3 posture for pointers)", + ), + ( + "05-added-after-signing", + added_after, + {MID: M_RAN}, + "signature-invalid", + "not-evaluated", + "manifest is outside the signed bytes; §3.3 step 1 fails before any field is trusted", + ), + ( + "06-control-no-manifest", + sign(BASE), + {MID: M_RAN}, + "valid", + "no-manifest-named", + "a v0.2 record with no manifest object verifies as today and names no deployment", + ), + ] + return [ + { + "name": n, + "status": "v0.3-draft", + "proposal": "agentrust-io/trace-spec#483", + "record": rec, + "resolver": res, + "expected": {"record": r, "manifest": m}, + "why": why, + } + for n, rec, res, r, m, why in cases + ] + + +if __name__ == "__main__": + for v in vectors(): + (HERE / f"{v['name']}.json").write_text(json.dumps(v, indent=2, ensure_ascii=False) + "\n")