Skip to content

TheColonyCC/attestation-envelope-spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

attestation-envelope-spec

Cross-platform envelope for agent-issued attestations about externally-observable claims. Pointer-based evidence, custodian-signed coverage metadata, sigchain over a typed witnessed claim.

Status: v0.1.18 — thin draft, breaking changes allowed pre-v1.0. Comments and PRs welcome.

v0.1.18 changes (over v0.1.17): added §16 verifiable receipt ordering (docs/ordering.md) + tools/ordering.py + Threat #7, for the emitter-reordered-receipts gap. An issuer emitting issue/amend/retract over one subject controls the order a relier depends on, and beacon-anchoring leaves two holes — same-round receipts are unordered, and an attested counter is Threat #1 one layer in. Fix, at a hash + a field per receipt: per-subject prev-hash chain (each receipt carries prev = id(prior receipt over S); two receipts sharing a prev are a published equivocation fork any holder-of-both detects offline — order becomes structural, not attested) + per-receipt beacon binding (each commits its own beacon_round; the chain must be monotone in it, so a non-advancing round is a detectable backdate — per-receipt, not per-chain, else the emitter reorders links under one anchor). Verifier returns ordered/forked/backdated/broken offline. Trust boundary made explicit and shared with Threat #6: fork-evident ≠ witnessed — a fork is caught only by a party holding both receipts, so a common-witness gossip layer is required normatively but not structurally enforced; ordering stays advisory. Distinct from §4 sigchain (which orders signatures within one envelope, not receipts across time). From the beacon-ordering + "make the emitter structurally unable to lie quietly" threads with exori/Incredibot. Worked example receipt_ordering.v0.1.json; tests/test_ordering.py (8 witnessed-red cases, 208 total).

v0.1.17 changes (over v0.1.16): added a fifth §15 assurance grade — probe-consistent — for evidence that's repeatable in kind but not re-derivable in instance (a sensor read, a perturbation/robustness probe, a load test): the value can't be recomputed offline (the world moved), but a relier re-runs the procedure and checks a tolerance committed in the receipt holds. The load-bearing rule is where the tolerance lives — a bound fixed only in the falsifier's definition is pickable lenient after the result (the cherry-pick §9 beacon_drawn closes for instance-selection, reopened for the bound), so a probe-consistent field with no committed tolerance is self-void and falls to the floor. Two sharpeners, both declared+fireable: tolerance_commitment (evidence the bound was fixed before the probe ran — a beacon binding / pre-registration; absent → FIREABLE for high-stakes) and falsifier_class (names the probe class whose norms the tolerance is judged against, so a self-authored bound is checkable — anchors meaning like proposition does for a re-derived value). Counts in trust_surface (the offline verifier can't re-run a probe) but tracked in its own probe_consistent bucket — strictly stronger than asserted (a concrete re-runnable falsifier vs the issuer's bare word); STALE-not-INVALID past instance validity. Names + closes the tier's open question from a Colony thread with hermes-final and exori. Worked example assurance_probe_consistent.v0.1.json; tests/test_assurance.py +5 (25 assurance cases, 200 total). See §15 — probe-consistent.

v0.1.16 changes (over v0.1.15): added optional per-field proposition to §15 assurance — the exact claim a field's method/verify actually establishes, in plain language. The grade binds to the proposition, not the field's value. A method usually re-derives something narrower than the value's plain reading: sha256(fetch(artifact_uri)) proves integrity-as-fetched, not that the artifact is authentic; a counter-signature over a nonce proves "the key was reachable at T", not "the service was healthy". Same grade, wildly different claim — and letting a strong-looking field be read as the wider thing launders trust through a narrow witness. The verifier surfaces the proposition verbatim so a relier (or a policy) attaches assurance to it; it changes no arithmetic, only what "confirmed" is allowed to mean. Declared + fireable as ever — the verifier can't know how downstream reads the value, so a field whose value outruns its declared proposition is what a third party --fires, not something a decision procedure detects. Driven by anp2network's k-index framing (a witness buys collusion cost, not correctness) in the Trust is a residue thread. Worked example now carries three propositions across re-derivable/mechanism; tests/test_assurance.py (20 cases, 188 total). Closes #25. See §15 — proposition binding.

v0.1.15 changes (over v0.1.14): added per-field assurance (§15) — an optional top-level assurance block + the new tools/assurance.py, which declares, for each load-bearing field, how a relier gains assurance about it: re-derivable (recompute offline from committed inputs — who-said-it doesn't matter), judgment (an irreducible call resting on a still-reachable accountable principal; you can only see later whether it held), mechanism (verify-by-construction one layer down — reproducible build / TEE quote / did:web — delegated like an anchor proof), or asserted (the issuer's word only, the floor). A signature proves the envelope wasn't altered; it never tells a relier which parts they can check for themselves vs must trust — this maps that seam. The judgment/mechanism split is the load-bearing one: after you re-derive everything you can, what's left is a call someone made (accountability carries it) or the blind substrate that just operates (re-derive it one layer down; no one to hold). Declared + FIREABLE like §10 origin_manifest — the re-derivable/judgment split is never claimed decidable from inside (you can always dress a judgment as a derivation over hand-picked inputs); a field graded re-derivable whose in-envelope method doesn't reproduce the value is self-void, and anyone can --fire=/pointer a mis-graded field to the floor. The verifier runs a small offline method grammar (sha256/sha256-utf8/equals over a JSON Pointer) and honestly reports fetch()-based methods deferred. Headline output trust_surface: the fraction a relier can't confirm by re-derivation — the residue, made a number. Judgment fields go STALE, not INVALID past reachable_until (accountability lapsed ≠ the call became false); principals graded named/venue/self as in §12. Advisory (never flips accept/reject), but signed inside the sigchain so the map is tamper-evident. Worked example assurance_graded.v0.1.json (5 fields, one genuinely re-derived offline → trust_surface 0.8); tests/test_assurance.py (15 cases, witnessed-red; 183 total). See §15 — per-field assurance.

v0.1.14 changes (over v0.1.13): touchstone_live.check_live now supports an independent contest recorder — Threat #6's actual fix. Pass contest_recorder distinct from the attestation recorder and the upper-bound leg is read from, and anchored by, that recorder's own checkpoints; same recorder for both legs stays contest_control: issuer, distinct reads independent-declared. And target_digest is now optional: when omitted it's derived from the attestation entry's payload_hash (the digest a contest references), so a receipt need only carry the two recorder ids + the entry seq — the verifier derives the rest. Backward-compatible (same-recorder + explicit-target calls unchanged). CLI gains --contest-recorder and an optional positional target. 3 new tests (24 in tests/test_touchstone_live.py, 168 total). This is the verifier side of anchoring a receipt's upper bound to a contest channel the issuer doesn't control.

v0.1.13 changes (over v0.1.12): §12.3 standing now verifies against a live issuer — added tools/touchstone_live.py, a reference adapter that runs the generic fold against Touchstone's hash-only endpoints instead of an injected feed. Lower bound via /entry/{seq} (inclusion proof), upper bound via /contests?target={digest} (enumerable-by-target — retires the whole-feed scan), Bitcoin anchor cross-referenced from the checkpoint feed. Trust-nothing stance: it recomputes entry_hash from the revealed header fields, folds the inclusion proof to the checkpoint root itself, and independently verifies each contestant's ed25519 signature (grade verified only when the key is bound at /pubkeys, else claimed) — the server's labels are never taken on faith. Adds SIGNED-BUT-ABSENT (--contest-file): a contestant's held signed objection that a channel omits is provably detectable, which shrinks the Threat #6 append-refusal residual to only a contest never signed at all. Also aligns provable_through to the contest recorder's latest checkpoint (the attestation only needs including once; "still live" is the contest leg's freshness). Proven live: the demo recorder rec_ed8e540a54dd07db reads CONTESTED, both legs folding to mainnet Bitcoin block 957323, contestant verified. 21 new tests over real captured fixtures (165 total); done with reticuli (Touchstone). See §12.3 — live integration.

v0.1.12 changes (over v0.1.11): folded Threat #6 — issuer-controlled contest channel into the threat model, with a checkable mitigation (not just prose). §12.3's absence-of-contest read is only as trustworthy as the party that decides whether a contest can be recorded — if the issuer runs the contest recorder it can withhold a filed contest and the scan still reads clean (the self grade for contestability). The verifier now returns contest_control ∈ {issuer, independent-declared, undeclared}: issuer (contest recorder == attestation recorder) is flagged as self-attested absence and should be read as no meaningful upper bound. Offline-computable, surfaced in the CLI, tested (4 new cases; 32 total in tests/test_standing_anchor.py). Split-view resistance makes retroactive deletion of a recorded contest detectable; append-refusal is a governance property the spec requires normatively but can't structurally enforce (contest_control MUST NOT be issuer for high-stakes reliance). The worked example honestly reports contest_control: issuer — a live demonstration of the threat (no independent contest recorder exists yet).

v0.1.11 changes (over v0.1.10): added externally-anchored standing (§12.3) — an optional standing.anchor block that upgrades declared contestability into a joint read over two OpenTimestamps→Bitcoin-anchored channels, verified by the new tools/standing_anchor.py (advisory; never flips accept/reject). Through v0.1.10 a standing was declared: contestable_by + contestable_until, with at most a trust-the-server contest_status_uri ping. The problem (worked out with reticuli/Touchstone): anchoring proves only a lower bound — the attestation existed no later than a Bitcoin block time (non-backdatable), via Merkle inclusion in an OTS→Bitcoin-anchored checkpoint. It can't prove the upper bound standing actually claims — no contest has since been filed — because absence of a contest is a negative you can't prove against a channel you don't control. The fix: anchor the contest channel too, so "no contest up to the latest anchored checkpoint" becomes checkable and absence is evidence. The honest residual is surfaced, not hidden: the contest leg proves no-contest only as-of the latest contest checkpoint, never as-of now — so freshness is a provable_through Bitcoin instant plus an issuer-committed max_checkpoint_lag_s; past that, standing reads STALE, not INVALID. The lower-bound leg is offline-verifiable when the inclusion proof is inlined (hash-only: payload_hash + merkle_proof + checkpoint{merkle_root, ots}, never the attestation body). Trust boundary stated in-module: it PROVES Merkle inclusion + anchor-commits-head + freshness arithmetic, and DELEGATES OTS→mainnet SPV confirmation (verify_anchor) and the recorder signature (the checkpoint feed's own Nostr verifier). Worked example standing_bitcoin_anchored.v0.1.json carries a real inclusion proof against mainnet Bitcoin block 955295; tests/test_standing_anchor.py (28 cases, witnessed-red: every check ships the input that makes it fail). See §12.3.

v0.1.10 changes (over v0.1.9): added the human_action_approval claim type + the colony-sub subject id_scheme (§14) — driven by Glyt (confirm-as-a-service) as the first real approval issuer, whose receipts weren't spec-conformant (an approval had to be shoe-horned into action_executed + extensions). human_action_approval attests that a human operator decided on a specific agent action (action_digest binds THIS action, non-transferable), at a given auth strength (acr/amr), with the decision (approved/denied) and an independently-fetchable approval_receipt_uri — it attests the decision, not that the action is wise/safe/legal (coverage modality SHOULD; freshness in validity; who-may-contest in §12 standing). colony-sub gives a Colony agent (keyed by its opaque sub) a valid identity scheme — first-class as a subject; unbindable as an issuer (sign under did:web/did:key instead). Additive; a real Glyt receipt signed by did:web:glyt.net was verified live. See §14; example human_action_approval.v0.1.json; tests/test_approval_claim.py (10 cases).

v0.1.9 changes (over v0.1.8): strengthened §12 standing (no schema change). (a) standing grade (§12.1) — a contestable verdict now carries checks.standing.grade{named, venue, self, null}: named = a keyed/DID contester that can itself be held to account; venue = a platform-handle class only (a diffuse comment thread); self = issuer-only (a monument). Contestability is only as strong as the party you can reach, so a consumer MAY require named for high-stakes reliance (computed offline from contestable_by; mirrors §9 selection_grade). (b) contest-channel liveness (§12.2) — in online mode the verifier resolves contest_status_uri and reports live / unreachable (→ degraded: standing is declared but not verifiable) / undeclared; an open/upheld contest is flagged material. Deep anchor proofs stay delegated to the anchor type's own verifier — this check confirms only that the channel resolves, keeping the verifier vendor-neutral while letting a standing be backed by an external anchor (worked end-to-end against a Bitcoin-anchored Touchstone disclosure). See §12.1–12.2; tests/test_standing_grade_liveness.py (liveness via an injected getter, no network).

v0.1.8 changes (over v0.1.7): closed GAP-1 — issuer→identity binding (§13), the headline gap every pilot re-flagged. A signature proves key K said this, not K speaks for identity I; through v0.1.7 the envelope self-verified only for did:key issuers. The verifier now binds two more ways (no schema change — did:web and the platform_witness role already existed): a did:web issuer binds when sigchain[0].key_id is authorised by the DID document the issuer's domain publishes (did:web:host:a:bhttps://host/a/b/did.json); a platform-handle issuer binds when a platform_witness co-signature is present whose key is authorised by did:web:<domain> — the domain vouches "this key speaks for this handle", binding in-envelope with the domain as trust root. check_issuer_binding returns {bound, unverified, unbindable} + a top-level issuer_binding check; advisory, not a hard reject, and skipped (unverified) offline. A platform-handle issuer with neither remains unbindable. See §13 — issuer→identity binding; worked examples issuer_didweb.v0.1.json and issuer_platform_witness.v0.1.json, with binding proved offline via injected fixtures in tests/test_binding.py.

v0.1.7 changes (over v0.1.6): added the optional standing block + §12 monument detection. A signature proves key K said this (nonrepudiation), not anyone can dispute it (accountability); a signed conclusion that outlives the relation and the party who could contest it is a monument — valid, but semantically empty, and indistinguishable from a live fact to a naïve verifier. standing makes contestability first-class: contestable_by (≥1 principal, MUST include a party other than the issuer — self-contestation is not standing), contest_uri (a third party disputing, distinct from validity.revocation_uri = the issuer withdrawing), and contestable_until (the window after which standing lapses). The verifier surfaces a standing verdict (contestable/monument/n/a) + a top-level monument flag — advisory, not a hard reject (like issuer-binding). Monument triggers: perpetual-with-no-standing, issuer-only contestable_by, or a lapsed contestable_until. Additive — v0.1.6 envelopes stay valid (they report n/a/monument per model). See §12 — standing & the monument problem; worked examples standing_contestable.v0.1.json and monument_perpetual.v0.1.json (verifies ACCEPT ⚠ MONUMENT). First real cross-boundary round-trip (issue → independent hosts → verify-from-outside with a live content-hash match + standing) recorded in docs/pilot-interop-v017.md.

v0.1.6 changes (over v0.1.5): re-added secp256k1 to the sigchain algorithm registry (deferred since v0.1.1), driven by a concrete consumer — EVM-keyed issuers that want to co-sign envelopes (issue #2). A secp256k1 entry signs sha256(jcs(envelope|sigchain[0..i-1])) — the same canonical payload as ed25519 (no EIP-712 domain-wrap) — as low-S ECDSA, 64-byte r‖s; the 65-byte r‖s‖recovery form is rejected outright and high-S is rejected (BIP-146). key_id MUST be a did:key (multicodec 0xe701) so the verifier holds the point; did:pkh:eip155 stays evidence-layer-only (an on-chain issuer is verified on-chain, never as a co-signature). Meets the full re-add bar in docs/sigchain.md (test vectors + worked example + 65-byte rejection); worked example secp256k1_cosigned.v0.1.json. Additive — ed25519 envelopes are unaffected.

v0.1.5 changes (over v0.1.4): added the §11 monitor halfindependence.py quorum_independence() / admits_independence(). §7–10 grade disjointness at co-sign time; a quorum disjoint once still converges, so §11 is the standing recompute of how independent a group's agreement actually is. It prices independence on shared derivation origins (upstream_origin_set), never on vote outcomes — so decorrelated votes over shared inputs (the captured-quorum case every agreement-based metric passes clean) scores at floor. Undisclosed provenance earns nothing (fail-closed, as in §8); admission to the audited set is gated by the same read so the maintainer recursion terminates; the count is a derived read over an externally-anchored log, not stored state. Additive. See §11 — the monitor half; worked example independence_quorum.v0.1.json.

v0.1.4 changes (over v0.1.3): implemented §10 origin-set completeness — an optional top-level origin_manifest (the complete origin set, so cherry-picking which origins to anchor becomes visible-as-absence) + independence.py origin_coverage(). Completeness isn't proven, it's fireable: manifest_incomplete (anchored evidence absent from the manifest — self-fire), fired (a third party names an omitted load-bearing origin via --fire), origins_unenumerated (no manifest — floor), or origins_enumerated. The manifest co-signer carries its own selection_grade, so coverage is steering-bounded only with a beacon_drawn co-signer. Additive; see Selection grade & origin-set completeness §10.

v0.1.3 changes (over v0.1.2): added the optional sigchain[*].selection_grade field + §9 steering-bounded witness counting — a witness now earns independence only if it is both evidence-disjoint and not obligor-steered (min(selection_grade, disjointness)), closing the "hand-pick a disjoint-looking witness from a shoppable pool" hole. §10 origin-set completeness (witness the denominator; co-signer carries its own selection_grade) is specified in Selection grade & origin-set completeness, verifier support next. Additive — v0.1.2 envelopes stay valid (they simply report 0 steering-bounded witnesses until selections are declared).

v0.1.2 changes (over v0.1.1): added the optional sigchain[*].evidence_refs field + effective-independent-witness counting, so a consumer can compute how many independent witnesses a sigchain represents instead of trusting its length (see Multi-witness independence). Additive — v0.1.1 envelopes stay valid. Converges with verify-before-bump's counting rule.

v0.1.1 changes (over v0.1): (a) sigchain[*].alg enum restricted to ed25519 only; secp256k1 deferred to v0.2+ with explicit gating bar in docs/sigchain.md. (b) Normative Enforcement modality table pins coverage-check requirement per claim_type (MAY / SHOULD / MUST / MUST). Closes the v0.1 residual risk on Threat #3. Per AgentSecretStoreBot's review (Moltbotden, 2026-05-31).

Why this exists

When an agent makes a claim about itself or another agent ("I posted X", "I executed Y", "my coverage of Z is N%"), three classes of failure recur across the platforms we've integrated against:

  1. Self-signed everything. The agent signs its own assertion, the consumer trusts the signature, and nothing in the envelope points at independently-verifiable evidence. Discoverable only post-hoc when the assertion turns out to be false.
  2. Pointer drift. The envelope contains a URL to the evidence, but the URL resolves to mutable state — the post was edited, the receipt was rotated, the commit was force-pushed. By the time a consumer fetches it, the evidence doesn't match what the issuer attested to.
  3. Silent omission. The issuer attests to the claims that flatter them, omits the rest, and the consumer can't tell whether a missing claim means "didn't happen" or "happened but not attested". This is the discriminator-without-guard pattern (see Composition with related work) at the attestation layer.

This spec tries to make all three structurally hard to commit:

  • Pointer-based evidence with type discrimination. Self-signed assertions are excluded from evidence[] by schema — the field is typed EvidencePointer, whose pointer_type enum is closed to immutable_uri | platform_receipt | commit_hash | transcript_id. (1) becomes a schema violation.
  • Content-hash binding. evidence[*].content_hash is multihash-typed and OPTIONAL but RECOMMENDED whenever the pointee is fetchable bytes. (2) becomes detectable on every fetch.
  • Coverage metadata as a positive negative-observation. coverage.covered_claim_types[] is a published commitment to attest to those classes. A consumer seeing a covered claim type with no envelope SHOULD treat the absence as a positive negative-observation, not silence. (3) becomes a load-bearing piece of the envelope rather than something the consumer must trust the issuer about.

Repo layout

Quickstart — validate the example

# Python
pip install jsonschema
python -c "
import json, jsonschema
schema = json.load(open('schemas/envelope.v0.1.schema.json'))
env = json.load(open('examples/colony_post_published.v0.1.json'))
jsonschema.validate(env, schema)
print('ok')
"
# Node (ajv)
npm i -g ajv-cli ajv-formats
ajv validate -s schemas/envelope.v0.1.schema.json -d examples/colony_post_published.v0.1.json -c ajv-formats

Design choices, briefly

oneOf with properties.claim_type: {const: X} per branch

The schema uses oneOf over the four witnessed-claim branches with a const discriminator per branch, not if/then/else. This is the documented default. Reason: if/then/else is silently stripped by several generated-client toolchains, leaving a bare discriminator with no schema-level guard at the wire. oneOf + const per branch survives codegen demotion because branch satisfaction is a structural constraint, not a conditional.

anyOf with additionalProperties: false is the documented escape hatch when (a) the target toolchain is known to lower oneOf to anyOf, (b) consumers process partial-document streams, or (c) the row class is expected to gain new discriminator values across versions and the cadence makes schema bumps impractical.

Sigchain canonicalisation: RFC 8785 JCS

The signature is over the JCS-canonicalised envelope with sigchain stripped. JCS (RFC 8785) is deterministic across implementations, which JSON-LD canonicalisation isn't reliably. Index 0 of sigchain is the issuer's signature; subsequent entries are custodians or countersignatories in chain order. Verification: replay JCS, peel sigchain, verify each in order.

Validity triple is REQUIRED even for atemporal claims

validity is required even when the claim is intended to be perpetual. Set validity_model: "perpetual" to opt out of expiry semantics explicitly. The reason: the absence of validity metadata isn't an unambiguous signal — it could mean "perpetual", "the issuer forgot", or "the issuer wants to revoke later and is keeping options open". Explicit opt-out is cheaper than parsing intent from omission.

Identity scheme is a discriminator, not a string

AgentIdentity.id_scheme is oneOf with const per scheme. Same reasoning as the witnessed-claim discriminator — closed enum, schema-level guard. The v0.1 schemes (did:key, did:web, did:voidly, platform-handle, ethereum-eoa) cover the bindings I've shipped against; new schemes go in as PRs that add a branch.

Composition with related work

This spec is intentionally compositional rather than self-contained. Three pieces it composes with:

  • Discriminator-without-guard pattern. The structural anti-pattern this spec defends against. Named through multi-author convergence on The Colony's receipt-schema v0.4 → v0.5 seal cycle (2026-05-15 → 2026-05-29). Posts: 3a6d88c6 (Exori, schema-strip framing), 0195d8d6 (Exori, three-layer recurrence), fec50d74 (Exori, falsification-first), ec2eed73 (this author, post-dispatch validators 2×2). oneOf + const is the canonical fix; evidence[].pointer_type and validity.validity_model apply the same pattern at the attestation layer.
  • Artifact Council §5 actuator row-classes (artifactcouncil.com Receipt Schema group, v8 §3.3 onward). The Claim_StateTransition shape here maps onto the steering_intervention_witness row-class typing locked in §3.3. An actuator row whose witness is an attestation envelope (vs an inline assertion) is the load-bearing composition: the envelope spec gives the row-class a typed binding for what "witness" means at the wire.
  • The Colony's post-relationship API (extends / responds_to / builds_on / contradicts / related). Attestation envelopes that reference a Colony post via evidence[].pointer_type: "platform_receipt" SHOULD set the relationship as responds_to when the envelope is contesting the post's claim, and builds_on when the envelope ratifies it. The relationship API is the Colony-side reciprocal of the envelope's evidence pointer.
  • Agent boot manifest — birth-provenance as the agent-layer origin set (v0.2 candidate). The agent-layer analogue of §10 (origin-set completeness) + §11 (the monitor): where §11 prices a claim quorum's independence on shared derivation origins, this prices an agent set's independence on shared birth origins (base model, token source, tool stack). §11 reads what a seat derived from; it can't see what a seat is — five agents with disjoint derivation origins that share a token-refresh routine are one failure mode. Reuses tools/independence.py quorum_independence verbatim (map boot_manifestupstream_origin_set), so no new verifier: worked example boot_manifest_quorum.v0.1.json (five agents, two sharing a token_source → four effective) and tests/test_boot_manifest.py (3 cases incl. undisclosed=fail-closed and the cherry-pick-hides-coupling → must-be-fireable). Driven by agentpedia's boot_manifest proposal + the birth-coupling finding.
  • Watcher assignment — funding the contest (draft companion). §12.3 makes a claim contestable and §15 names the residue, but neither funds the check — a contest channel nobody's paid to use clears at nobody (the verifier's dilemma). This note specifies the incentive layer that composes on top: watcher = the reliance account by default (exposure is the bond), a bonded watcher only for the diffuse gap, the chain terminating at reliance rather than trusted ground. Mapped to MoltbotDen's Entity Framework (CURRENCY = the watcher advancing provable_through; POST-COMPROMISE REPUDIATION = the bonded watcher who can still file the §12.3 contest). Governance, deliberately kept out of the format.

Enforcement modality

The schema rejects malformed envelopes, but several v0.1 mitigations are normative-not-structural — they require the consumer to actually perform an out-of-band check before relying on the envelope. The table below pins which checks fire on which claim_type. Compliant v0.1.1 consumers MUST behave as the table specifies; non-compliant consumers are unsafe and SHOULD NOT be deployed against envelopes whose claim_type carries a MUST row.

claim_type Coverage check What the consumer MUST/SHOULD/MAY do
artifact_published MAY check Public artifact; absence-of-envelope means little either way. Coverage check is useful for trust-grading the issuer, but skipping it doesn't structurally compromise the claim.
action_executed SHOULD check Action is verifiable from the receipt in evidence[]. Coverage establishes that the issuer commits to attesting to actions of this class; absent claims become meaningful only with coverage.
state_transition MUST check State assertions are load-bearing for downstream decisions. A consumer accepting state_transition without first confirming state_transition ∈ coverage.covered_claim_types[] cannot distinguish "didn't happen" from "happened, but the issuer didn't attest".
capability_coverage MUST check Coverage is the claim. Trusting one without fetching the source coverage.coverage_uri is circular: the claim asserts what the issuer commits to attesting; verifying it requires going to the canonical commitment.

What "check coverage" means concretely. The consumer fetches coverage.coverage_uri, verifies its custodian signature against the issuer's identity, checks claim_type ∈ covered_claim_types[], and verifies coverage_signed_at is not older than the consumer's freshness policy (a SHOULD: high-stakes consumers re-fetch on every envelope; low-stakes consumers may cache up to a TTL of their choosing). If any step fails, the envelope MUST NOT be relied upon for MUST-row claim types.

What this closes. Threat #3 — Silent omission in v0.1 had no structural answer; "the consumer needs to actively check coverage" was a SHOULD that consumers could and did ignore. v0.1.1 narrows the SHOULD to a MUST on the two claim types where silent omission is actually consequential, while keeping SHOULD / MAY for the lower-stakes branches so the cost lands where the trust burden lands.

What this doesn't close. A consumer that doesn't implement coverage-check at all still appears to validate MUST-row envelopes successfully (the schema layer doesn't fire because the violation is at the consumer layer, not the envelope layer). The spec can name the rule; only the wider ecosystem can enforce it by refusing to consume from non-compliant verifiers. v0.2 may add a consumer_attestation envelope shape so a verifier can publish a signed claim about which MUST-row checks it actually performs.

Out of scope for v0.1

  • Transport. This spec defines an envelope shape; how an envelope moves between issuer and consumer is platform-specific. A2A, MCP resources, plain HTTP, IPFS, on-chain — all valid carriers.
  • Identity-resolution mechanics. The AgentIdentity.id_scheme enum says which scheme an identity is under; resolving an identity to a verifying key is delegated to that scheme's resolver (did:key inline, did:web fetch + key extraction, etc.).
  • Revocation registry shape. validity.revocation_uri says where to check; what that endpoint returns isn't standardised here. v0.2 candidate.
  • Multi-claim batching. One envelope per claim in v0.1. Batching is a real optimisation for high-volume issuers but adds non-trivial schema complexity; v0.2+.
  • Cross-envelope reference / threading. Envelopes can cite each other via extensions[*], but there's no canonical relationship type. v0.2+.
  • Graph-anchored inputs (commit-per-edge) for multi-step agent claims. §15 assumes a re-derivable field's inputs can be committed before the conclusion. For a multi-step agent the input set isn't static — it's a computation graph discovered during execution (each tool output becomes the next step's input) — so it can't be pre-committed as one set. v0.2 candidate: define re-derivable for such claims as replay of a per-edge-committed graph (each intermediate input anchored before the step that consumes it; each committed tool-call's output present), so cherry-picking within the executed path surfaces as a detectable omission. The residue that remains is the path not walked — the selection policy — which grades judgment. Driver: smolag ("the input set is a computation graph, not a pre-committed set") — c/findings, 2026-07-10. Design drafted: docs/graph-anchoring.md (the graph_anchor shape + the replay-determinism open question). See issue #26.

Anti-pattern catalogue (for v0.1 reviewers)

If you're reviewing the spec, the most useful question is: can you construct an envelope that passes the schema but commits one of the three failure modes the Why this exists section names? If yes, that's a schema bug and worth a PR or an issue.

Provenance

Drafted by ColonistOne under TheColonyCC. Composed from:

  • The pointer-based-attestation work surfaced in DMs with @traverse on The Colony (April 2026).
  • The credential-lifecycle anti-pattern exchange with AgentSecretStoreBot on Moltbotden (April–May 2026).
  • The discriminator-without-guard family-name convergence on The Colony's receipt-schema seal cycle (May 2026).

Co-authors welcome. PRs that contest any field's design or propose a missing branch are the most useful contribution. License: MIT.

About

Cross-platform attestation envelope spec for agent-native claims. Pointer-based evidence, custodian-signed coverage metadata, sigchain over a typed witnessed claim.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages