Agent Action Capsule · explain
08 / 11

Bundles and selective disclosure

A bundle packages a record with what makes it checkable and says exactly what it left out; selective disclosure withholds single fields without breaking the signature over the record.

Withholding a field without breaking the signature Left: a record. Step 1: the plain record shows action_id act-001, action_type decide, a timestamp, operator acme-corp, and a constraints array holding one constraint record, spend_limit, result pass. Step 2: in the middle, the producer builds two disclosures, the array ["rIdm2xVvGT-yKjLWOXXJfg", "operator", "acme-corp"] and ["aBcDeFgHiJkLmNoPqRsTuV", the constraint record], and hashes the JCS form of each with SHA-256, giving digests beginning nqN82i0T and NLyJfzzG. Step 3: the record now carries _sd_alg sha-256, an _sd array holding nqN82i0T and a decoy digest, and a constraints array holding the placeholder {"...": NLyJfzzG}; the operator member and the constraint record are gone. A badge shows capsule_id computed over this form and the Producer Envelope signature over capsule_id. Step 4: the holder sends the signed record and the operator disclosure only; the constraint disclosure is kept back. Step 5: on the right, the verifier rehashes the operator disclosure, gets nqN82i0T, finds it in the signed _sd array, and re-inserts operator: acme-corp; capsule_id is recomputed over the form as received and the signature verifies. Step 6: the decoy digest is ignored, and the constraint placeholder stays concealed and is reported as sd_undisclosed_constraints: 1. The final picture shows the concealed record with the intact signature badge and the verifier's results. record (payload) "action_id": "act-001" "action_type": "decide" "timestamp": "2026-06-18T00:00:00Z" "operator": "acme-corp" "constraints": [{"id": "spend_limit", "result": "pass", …}] "_sd_alg": "sha-256" "_sd": ["nqN82i0T…", "<decoy>"] "constraints": [{"...": "NLyJfzzG…"}] capsule_id = SHA-256(JCS(this form)) Producer Envelope (COSE_Sign1) signs capsule_id ✓ producer: salt, then hash ["rIdm2xVvGT-yKjLWOXXJfg", "operator","acme-corp"] SHA-256 → nqN82i0TEZF3… ["aBcDeFgHiJkLmNoPqRsTuV", {…"spend_limit"…}] SHA-256 → NLyJfzzGc-kl… holder sends to verifier signed record + operator disclosure constraint disclosure: kept back verifier rehash revealed disclosure: SHA-256 → nqN82i0TEZF3… ✓ found in signed _sd → "operator": "acme-corp" ✓ capsule_id over form received ✓ signature over capsule_id <decoy>: no disclosure, ignored constraint: still concealed sd_undisclosed_constraints: 1 reported, never filled in Values from the selective-disclosure draft examples (§7.1, §7.2); the digests are SHA-256 of the JCS formof each array, computed for this page. The salts are the draft's own example salts, so these digests are illustrative, not from a production record.

1What it is

An Evidence Bundle is one JSON object that carries a root record, the records it cites, any revealed preimages, and log evidence, without changing any record inside it.

It states its completeness as three separate claims, graph closure, interval coverage and per-record membership, and a verifier reports each one on its own.

Selective disclosure lets a producer replace single eligible fields with salted digests before signing, so a holder can later reveal some fields to a verifier and keep others back while the signature still verifies.

2Design choices

Chosen: citation closure is declared: every citation reached within closure_depth is either supplied or listed once in completeness.missing.
Rejected: assuming closure from what is present; a short chain would look complete. A verifier reports the depth it applied. (disclosure-bundle §4)
Chosen: three completeness claims, each verified and reported independently.
Rejected: one "complete" flag; an endpoint-only range proof still passes when an interior record is deleted or replaced, so per-record membership needs its own proof. (disclosure-bundle §6)
Chosen: without a verified checkpoint signature or witness receipt, coverage and membership are labelled checkpoint_unverified.
Rejected: reporting them as independently verified; they are then only relative to a checkpoint the producer asserted. (disclosure-bundle §6)
Chosen: a bundle-level disclosure overlay, keyed by capsule_id; a member not revealed is WITHHELD, never blank and never failed.
Rejected: wrapping each record with its disclosures; the overlay leaves each record and its capsule_id untouched. (disclosure-bundle §5)
Chosen: salted per-field digests with decoys, and capsule_id computed over the concealed form, so one signature serves every view.
Rejected: recomputing capsule_id over the revealed form; identity would then depend on which fields a verifier was shown, not on the event. (sel-disc §5.2, §8.4)
Chosen: the JSON form of the selective-disclosure model (SD-JWT, RFC 9901), hashing the JCS form, since the record payload is JSON.
Rejected: using SD-CWT directly; it is the CBOR/dCBOR sibling, cited for alignment, and hashes dCBOR rather than JCS. (sel-disc §3, Appendix)
Chosen: a closed list of eligible fields (operator, developer, some disposition and effect members, constraint records, framework_tags).
Rejected: concealing anything; hiding effect.status or a response digest would make base checks impossible, so that is a structural failure. (sel-disc §4.4, §8.6)

3What it shows / what it does not

Shows

  • That every citation reached within the stated depth is supplied with a matching identity, or openly declared missing.
  • That each supplied record sits at its claimed log position, by its own inclusion proof to the range root.
  • That a revealed preimage hashes to the digest the record committed to (REVEALED-match), or does not (REVEALED-mismatch).
  • That a revealed selective disclosure matches a digest under the signature; a mismatch is reported as sd_commitment_mismatch.
  • How many constraint records are still concealed, so a reader knows the visible list may be incomplete.

Does not show

  • Anything about records beyond closure_depth.
  • Interval coverage or membership as independent results while the checkpoint is unverified.
  • What a withheld field contains, or how many fields are concealed (decoys blur the count).
  • A full verification when a REQUIRED field such as operator stays concealed: ok is false, with sd_required_field_not_disclosed (sel-disc §8.5).
  • Anything from the verification member, which is producer self-report.
  • A valid countersignature: the slot is reserved and every entry is surfaced as unverified in this revision.

Two digest checks appear on this page. The bundle overlay compares the plain JSON digest of a revealed preimage (for example agent_input) with the digest the record carries. Selective disclosure compares the digest of a salted [salt, name, value] array with an entry in a signed _sd array.

4Example

A single-record bundle from the conformance vectors: one root record, depth 0, nothing missing, the agent_input preimage revealed in the overlay, and a one-leaf log certificate with an unsigned checkpoint. Recomputing the JSON digest of the full revealed agent_input gives the record's agent_input_digest. One member of that preimage is elided below.

"name": "pos-single-record-report",
…
"bundle": {
  "bundle_version": "2",
  "bundle_kind": "evidence-bundle/v2",
  "root": "01011c5da0a88805492b97c4ddd8f50f63839757de8cbf4608bfb51098bfd6fe",
  "records": [
    {
      …
      "action_id": "report-single-record-1",
      "action_type": "fyi",
      …
      "model_attestation": {
        "compute_attestation": {
          "agent_input_digest": "1fadc146114450fe2e22def4f2a57832d68e8ecfd3dca792d93fd68e505e2798"
        }
      },
      "countersignatures": [],
      "capsule_id": "01011c5da0a88805492b97c4ddd8f50f63839757de8cbf4608bfb51098bfd6fe"
    }
  ],
  "completeness": {
    "closure_depth": 0,
    "records_mode": "complete",
    "payloads_mode": "selected",
    "suppressed_fields": [],
    "missing": []
  },
  "disclosures": {
    "01011c5da0a88805492b97c4ddd8f50f63839757de8cbf4608bfb51098bfd6fe": {
      "agent_input": {
        …
        "rows": [
          {
            "id": "r1",
            "result": "pass"
          },
          {
            "id": "r2",
            "result": "withheld"
          }
        ]
      }
    }
  },
  "completeness_certificate": {
    "log_id": "vector-log",
    "range_root": "3d8f59c9a33a55f63256b75bf2710e696d8f397fbc10766b95b0e803d0a4800e",
    …
    "memberships": {
      "01011c5da0a88805492b97c4ddd8f50f63839757de8cbf4608bfb51098bfd6fe": {
        "log_coordinates": {
          "log_id": "vector-log",
          "seq": 1,
          "leaf_index": 0
        },
        …
  "checkpoint": {
    "root": "3d8f59c9a33a55f63256b75bf2710e696d8f397fbc10766b95b0e803d0a4800e",
    "mmr_size": 1
  }
},
"expected": {
  "graph_closure": "pass",
  "interval_coverage": "pass",
  "interval_findings": [
    "checkpoint_unverified"
  ],
  "per_record_membership": "pass",
  "membership_findings": [
    "checkpoint_unverified"
  ]
}
FieldTypeMeaning
roothexcapsule_id of the record the bundle's claim starts from.
recordsarrayUnmodified records; here only the root.
completeness.closure_depthintegerHow many citation edges were followed; 0 means the root alone.
completeness.missingarrayCited digests declared absent; empty, so records_mode is complete.
disclosuresobjectOverlay of revealed preimages keyed by capsule_id; checked against the record's committed digest.
completeness_certificateobjectLog id, range root, and a per-record membership proof at leaf_index = seq - 1.
checkpointobjectRoot and MMR size; no signature here, hence checkpoint_unverified.
expectedobjectThe three completeness results, each reported on its own.

Source: action-state-group/agent-action-capsule:vectors/bundle/report-single-record.json @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c

5Specs