Subject: zambo.dev/verify (AER-1 live MCP receipt verifier, draft-zambo-aer1-09)
Found by: Shin, night desk · first measured 2026-10-05 12:56 UTC · re-measured live 2026-10-06 09:35 UTC
In scope: /verify/aer1-verifier.js, /verify/hosted-verifier.js, /aer-1/schema.json, /aer-1/hosted-profile.schema.json
provenance_class and verification_status are required receipt fields, but they are not inside canonical_bytes and not in the anchor envelope. So the verifier's strongest green result, COMMITMENT MATCH, says nothing about the one field that carries the actual claim ("what ran"). A relayed or copied receipt can therefore carry a provenance claim the bytes never committed to.
verifyAER1ReceiptJson returns hashMatches (sha256 over canonical_bytes equals output_hash), schemaValid, and valid (both). It also reports signatureCheck and chainCheck, both not_checked for a receipt consumed this way.
keys inside canonical_bytes: caller, output, receipt_schema_version, side_effects, tool keys inside anchor.envelope: schema_version, receipt_uuid, output_sha256, evidence_sha256, evidence_byte_length, evidence_url, anchored_at provenance_class inside canonical_bytes? false verification_status inside canonical_bytes? false timestamp inside canonical_bytes? false provenance_class in anchor.envelope? false
Take a real receipt returned by the service (I used pdf_extract over a public page). Leave every byte of canonical_bytes untouched. Relabel only the metadata:
r.provenance_class = "OBSERVED VIA GATEWAY"; // was "EXECUTED BY ZAMBO" r.verification_status = "asserted_not_observed"; // was "execution_observed"
Run it through verifyAER1ReceiptJson:
BASELINE valid: true hashMatch: true schemaValid: true provenance_class: EXECUTED BY ZAMBO | verification_status: execution_observed MUTATED class: OBSERVED VIA GATEWAY | status: asserted_not_observed valid: true hashMatch: true schemaValid: true schemaErrors: []
valid = true, COMMITMENT MATCH, schema conforms. The label moved from "this ran on our infrastructure" to "observed through a gateway, not observed" and the check never flinched.
The service's own stated line is honest: a verifiable receipt proves the saved result was not changed, not that the model was right. The gap is that the verifier's strongest result is displayed adjacent to the claim, and a reader collapses the two, reading a green COMMITMENT MATCH on "EXECUTED BY ZAMBO" as "the execution is verified." The bytes only prove non-alteration. The provenance class is an unauthenticated label riding next to the proof, and when a receipt is relayed between parties (exactly the AER-1 use case) nothing binds the label to the commitment.
(a) Bind the claim into the commitment. Add provenance_class and verification_status to the hashed envelope alongside tool/output/side_effects/caller; or, if the envelope shape is frozen, add sha256(provenance_class || verification_status) to anchor.envelope. Then a relabeled class changes the commitment and the verifier fails closed. Schema change; bump receipt_schema_version.
(b) If the fields must stay contextual, stop rendering them as verified. In the verifier UI, render the two fields in a visibly separate block labelled as not covered by the commitment, and keep COMMITMENT MATCH scoped to what it proves: two lines, bytes: match / provenance claim: unverified. Stopgap that removes the misread without a schema break.
Reported publicly on Nostr first (event bc10420e…, 2026-10-05 17:45 UTC). This is the formal write-up plus the patch. Repro script: aer1_gap_test.mjs + sampled receipt pdf_call.json.