Public verification guide

The AER-1 Internet-Draft: Receipt Verifier and Fixtures

Short answer: the included verifier fetches a public receipt page, reads its visible core fields, checks the public JSON projection, validates the UUID and RFC 3339 timestamp, decodes canonical_bytes, and recomputes the SHA-256 output commitment. It prints PASS only when the required record checks succeed.

This page describes a local conformance aid for the published draft-00 text. The final human-review status of the draft text was not confirmed at build time, so these examples identify draft-00 and do not imply that a later revision was reviewed.

Browse the five JSON fixtures or download the Python verifier.

What a passing result means

The verifier checks a particular public record at retrieval time. It confirms that the requested identifier resolves to the same receipt identifier, required fields are present, the timestamp has a valid timezone-aware RFC 3339 form, and the SHA-256 digest of the decoded canonical bytes equals the published output_hash. It also requires the public projection to report verification_status as verified.

A passing hash check confirms byte integrity against the recorded commitment. It does not prove that the input was true, the tool's answer was correct, the request was authorized, a human saw the result, or an external system changed. When a task depends on payment, delivery, publication, a ticket update, or another outside effect, inspect evidence from the system that owns that state.

The verifier follows the published draft-00 procedure: resolve the public receipt URL, confirm the record identifier, obtain the exact canonical bytes, compute SHA-256, then review the tool, time, caller scope, observed result, evidence, and anchor status as separate facts. It does not turn those separate facts into a stronger claim than the stored observation supports.

Fields checked by the verifier

The record is read as a JSON object from the public projection. The verifier requires the following core members and checks their types before it reports success.

  • id is a canonical UUID and matches the UUID requested in the URL.
  • receipt_schema_version is present as a non-empty version string.
  • created_at is a parseable, timezone-aware RFC 3339 timestamp.
  • tool contains non-empty name, version, and scope values.
  • provenance_class is one of the defined receipt provenance labels.
  • canonical_bytes is strict Base64 and decodes to UTF-8 bytes.
  • output_hash uses the sha256: prefix and 64 lowercase hexadecimal digits.
  • verification_status equals verified in the public receipt projection.

The verifier hashes the exact decoded bytes. It does not parse and reserialize the payload, because that could change whitespace, Unicode representation, or key order and produce different bytes. The digest comparison is exact, not a comparison of a shortened display value.

Audit page, public projection, and transport response

The input is a public /run/<uuid> URL. The verifier first fetches that HTML audit page and extracts the receipt ID, schema version, timestamp, tool fields, provenance badge, output hash, and canonical Base64 value from the rendered receipt details. It then fetches the unauthenticated /api/receipt/<uuid> projection, checks that the displayed fields agree, and reads the projection's verification_status.

A JSON-RPC tool-call response is a separate transport envelope. Its nested _receipt can use the key timestamp and may report verification_status as execution_observed. The public receipt projection instead uses created_at and can report verification_status as verified after the stored record passes its verification procedure. The verifier intentionally uses the public projection for the receipt contract, not the transport object.

The audit page and JSON projection have separate roles. The page is the public resolution surface and its visible fields must agree with the structured record. The projection provides machine-readable values for checks that are not printed as a literal field on the page. A mismatch is a failure, not a reason to silently choose one source.

Five fixtures and their expected outcomes

Each fixture isolates one condition. The local fixture mode tests field validation without needing network access. The unknown-UUID case records the expected HTTP 404 result; a live run also checks that the public lookup currently returns not found.

FixtureExpected resultReason
valid-receipt.jsonPASSAll required fields are present, the timestamp and UUID are valid, the public verification status is verified, and the output hash matches.
tampered-output.jsonFAILThe digest was changed while canonical bytes were left intact. Expected reason: SHA-256 mismatch.
malformed-uuid.jsonFAILThe identifier is not a UUID. Expected reason: UUID-format.
missing-timestamp.jsonFAILThe required creation time is absent. Expected reason: missing timestamp.
unknown-uuid.jsonFAILThe identifier is well formed but no record exists. Expected reason: not-found.

For every negative fixture, the outer fixture check passes only when the verifier produces the expected failure reason. A test runner can therefore report that a tampered record was correctly rejected without confusing that result with a valid receipt.

Run the verifier with two commands

Python 3 is sufficient. Download the single source file, then pass a current public run URL. Replace the placeholder with a receipt UUID you obtained from a real run page.

curl -fsS https://zambo.dev/aer1/fixtures/verify_aer1.py -o verify_aer1.py
python3 verify_aer1.py https://zambo.dev/run/<receipt-uuid>

A successful run prints a PASS line. A failure prints a reason such as not-found, missing timestamp, or SHA-256 mismatch and exits with a non-zero status. The script has no hardcoded receipt identifier and does not ask for an account, API key, SDK, or package installation.

To run the local fixture set after downloading the JSON files, use python3 verify_aer1.py --fixtures-dir ./fixtures. The command reports the expected and observed verdict for each file and exits unsuccessfully if any fixture produces the wrong result.

Why the verifier uses built-in Python modules

The code uses only modules included with Python: URL handling, HTML parsing, JSON decoding, Base64, UUID validation, timestamps, and SHA-256. That keeps the audit path inspectable and avoids a vendor SDK or a package dependency between the reviewer and the bytes being checked.

The network client is restricted to HTTPS pages on zambo.dev and follows the run page with the matching public receipt projection. The script does not send credentials, does not accept a caller-supplied API host, and does not perform a tool call. It reads an existing public record and reports the local result.

For local tests, the fixture mode reads one JSON file or every JSON file in a directory. It uses the fixture's public projection rather than the raw MCP transport envelope. The unknown record fixture represents the observed not-found response; it does not fabricate a successful receipt.

Integrity is not correctness

SHA-256 answers a narrow question: do these bytes match the digest written in the receipt? It does not tell a reviewer whether the upstream source was accurate, the selected tool was appropriate, the returned value was fresh, or the agent's decision was wise. Those questions require different evidence and, where needed, human judgment.

The page can also show provenance and anchor information. Review each one separately. “Executed by Zambo” describes the recorded execution boundary. “Observed via gateway” describes an observation of request and response bytes from an external executor. “Logged by agent” describes a report whose underlying action is not independently observed. These categories should not be collapsed into one claim.

Anchor state is another independent property. Pending or partial publication is not the same as a completed external anchor. The verifier reports field and byte checks; it does not upgrade an anchor state or a downstream side effect.

If a receipt is missing, expired, or unavailable, preserve that result. A missing record is not evidence that an action did or did not happen. Look for the source system's own history and state what remains uncertain.

Review status and maintenance

The final human-review status of the draft text was not confirmed for this build. This page therefore follows the published draft-00 text retrieved for implementation and does not imply that a later text was reviewed. If the draft changes, compare the required fields and verification procedure before updating this verifier or its fixtures.

When a production record changes shape, do not hide the mismatch with a fallback. Update the documented mapping, add a fixture for the new condition, and verify the public run page and projection together. Keep the original fixture payloads available so reviewers can reproduce earlier tests.

Further reading

Try a bounded call

The free tier does not require an account. Check the pricing page for the current per-tool allowance, then run a small request and inspect its public receipt before relying on a reported outside result.

Open the Zambo install guide