Chio/Docs
LOGIN · JOIN

PlatformCross-Agent Disclosure

Swarm

Disclosure Lineage

Five artifacts must agree before a disclosure counts as evidence: the capsule, the profile, the signed subgraph, the leakage ledger, the crypto report.

The proof is next door

Selective Disclosure owns the cryptography: the BBS+ projection over a receipt body, the message slots, the range predicates, and the proof itself. This page owns what a verifier requires around that proof before it will accept a disclosure as evidence. The split is a crate boundary, not a narrative one: chio-selective-disclosure produces proofs and evaluates cryptographic policy; chio-disclosure-lineage cross-validates the five artifacts that carry a proof to a counterparty and refuses when any binding fails. Neither crate produces the other half.

Five artifacts or nothing

A disclosure capsule is what one party hands another when it wants a verifier to learn a bounded fact and nothing more. The verifier does not run the kernel that produced the receipts, so it cannot query the provenance graph. It has to be handed a subgraph, and it has to be able to check that the subgraph is the one the capsule claims. This site files that under Swarm because the reader is a counterparty running none of the producing kernel; nothing in the Chio repo assigns the crate to a rung.

Verification is shipped. crates/trust/chio-disclosure-lineage is a pure verification library: three source files, no I/O, no runtime state, and exactly three dependencies (chio-core-types, serde, thiserror). It holds no keys. Trusted signer keys are passed in per call. It is reached from chio proof verify and chio proof doctor --scenario disclosure-lineage, both through the chio-selective-disclosure re-export, and from the proof room directly. There is no runtime admission caller: the three in-tree call sites of verify_disclosure_lineage_bundle_with_trust are the CLI proof dispatch and two proof-room modules.

Two claims in the registry are answered here, both status: enforced under introduced_in: disclosure-lineage-v1: claim.disclosure.lineage_subgraph_bound and claim.disclosure.leakage_ledger_complete. Two more, claim.disclosure.crypto_context_bound and claim.disclosure.profile_context_policy_enforced, are registered under crypto-context-v1, belong to the crypto context report, and are copied into this verifier’s output verbatim. That list is constrained to exactly those two values with both required, so a well-formed bundle yields four ids. The copy is not deduplicated: a report repeating an allowed id repeats it in the output (spec/registries/claim-registry.v1.json).


What is in the bundle

DisclosureLineageBundle is five fields. Every artifact type in src/types.rs carries #[serde(deny_unknown_fields)], so an unrecognized key is a deserialization failure before verification starts.

ArtifactSchema tagSignedStates
DisclosureCapsulechio.disclosure.capsule.v1NoWhat was disclosed and what was proved without disclosure. Six refs plus a disclosed-field list and a hidden-predicate list.
DisclosureVerifierPrivacyProfilechio.disclosure.verifier-privacy-profile.v1NoWhat this verifier permits: sensitivity classes, allow and forbid lists for fields and predicates, a count budget, and a block of cryptographic policy this crate never evaluates.
SignedLineageSubgraphchio.lineage.signed-subgraph.v1Ed25519The receipt evidence behind the capsule, as nodes with depth and disclosure state, edges, redactions, and six digests.
DisclosureLeakageLedgerchio.disclosure.leakage-ledger.v1NoOne scored entry per unit of leaked information, with its sensitivity class, its derived inferences, and a cross-tenant risk flag.
DisclosureCryptoContextReportchio.disclosure.crypto-context-report.v1Ed25519An attestation that the underlying proof and its key, revocation, nonce, audience, holder-binding, algorithm, and transparency state were checked.

The report is Option on the wire and therefore omittable in JSON. It is not optional in practice: validate_bundle_bindings returns missing crypto context report on None. The optionality is a loading convenience, and the CLI uses it that way: it pulls the report from the evidence graph with the optional-artifact loader and lets the verifier be the thing that refuses an absent one.

The subgraph

Each node names one artifact and its position in the provenance graph. Every field below is required and non-empty, including on a node the capsule declares redacted.

Node fieldConstraint
kindOne of thirteen: receipt, capability_snapshot, session_anchor, request_lineage_record, receipt_lineage_statement, continuation_token, checkpoint, bbs_projection, bbs_proof, passport_presentation, governed_intent, approval_token, runtime_assurance.
artifact_sha256, source_id_hashBoth must equal sha256(receipt_ref), recomputed by the verifier.
evidence_classOne of observed, verified, asserted, derived, and its rank must meet the subgraph’s declared floor.
depth, parent_idsA non-root node needs at least one parent; every parent must exist, must carry a strictly smaller depth, and must have an edge pointing at this node. The subgraph’s declared max_depth must equal the largest depth observed.
disclosure_statedisclosed or redacted; a redacted node needs a matching entry in redactions.
tenant_hashShape-checked as a sha256 hex digest. Never resolved, never compared across nodes.

The evidence-class floor is a rank comparison, and the ranks are not a total order over the four names: asserted is 0, observed is 1, and verified and derived tie at 2. A subgraph declaring required_evidence_class: verified therefore admits derived nodes and refuses observed ones. The floor applies to nodes only. Edge evidence classes are checked for membership in the same four names and are not held to the floor.

Edges carry relation from continued, derived, delegated, and kind from a list of ten (parent_of, issued_under, observed_in_session, continued_by, signed_lineage_statement, included_in_checkpoint, proves_projection, governs_transaction, approves_intent, presented_by). Two digests per edge are recomputed: source_artifact_sha256 must equal sha256(edge_id), and statement_sha256 must equal sha256("from|to|kind"). edge_id must be unique, and so must the (from, to, relation) triple.

The edge and parent relations are checked in one direction. validate_lineage_closure requires an edge for every entry in a node’s parent_ids; nothing requires a parent_ids entry for every edge, so an extra edge is accepted. It is not free, since it drops its source node out of the frontier and changes that digest, but the graph verifies around it. Edge disclosure_state is checked for membership in disclosed | redacted and nothing else: redactions are a node-keyed list, so a redacted edge needs no reason and gets no entry.

Redaction is a label, not an erasure. A node marked redacted still carries its receipt_ref in the clear, because the artifact-digest check cannot pass without it, and it still carries its source_table and tenant hash. What a redaction adds is a required reason, drawn from privacy_profile, data_minimization, or legal_hold. A redaction that points at a disclosed node is refused, and a redacted node with no reason is refused. Node redaction is not concealment from the verifier; it is a declared policy state the verifier can hold the producer to.

The ledger

The leakage ledger has no analogue in ordinary presentation formats. Every unit of information the capsule releases gets one entry, and an entry is not just a name: a sensitivity class, a value class, a policy rule, derived inferences, a cross-tenant risk flag, an optional mitigation, an optional residual-inference note, and an integer score. Three kinds of leakage are recognized, and disclosure_kind must repeat leakage_kind exactly.

leakage_kindKeyed byMust the field be in the named sensitivity class?
disclosed_fieldThe disclosed field name.Yes.
hidden_predicateThe predicate id, in the same namespace as field names.Yes.
derived_factAny name. Only the three REQUIRED_DISCLOSURE_DERIVED_FACTS ids are demanded by coverage.No. The class must exist; membership is not required.

The derived facts are the point most producers miss. Proving anything at all leaks that the issuer was checked, that revocation state was fresh, and that a presentation happened at a particular time, so a capsule that discloses anything must also account for all three:

crates/trust/chio-disclosure-lineage/src/verifier.rsrust
const REQUIRED_DISCLOSURE_DERIVED_FACTS: &[&str] = &[
    "derived.crypto.issuer_status",
    "derived.crypto.revocation_freshness",
    "derived.crypto.presentation_timing",
];

A capsule that discloses nothing and proves no predicate needs none of them, and an empty ledger verifies. That asymmetry is deliberate and pinned by disclosure_lineage_accepts_empty_leakage_ledger_without_disclosed_facts.

Two ledger fields look like verifier judgements and are not. accepted and the per-entry allowed_by_profile are written by the producer; the verifier requires both to be true and derives neither. Step 6 re-checks the profile allow and forbid lists for disclosed fields and predicate ids, so the flag is redundant there. For a derived_fact entry there is no profile list at all and the flag is the whole policy claim. The ledger’s subject_artifact_sha256 is the same shape of thing: checked as a sha256 digest, never compared to the capsule.


The order of the checks

verify_disclosure_lineage_bundle_with_trust runs eight steps and returns on the first error. There is no partial report and no accumulated-findings mode: a DisclosureLineageVerifierReport is constructed only on success, and its verdict is the literal "verified". A refusal is a single DisclosureLineageError::InvalidArtifact carrying one sentence.

#StepWhat it decides
1validate_capsuleSchema tag, a non-empty id and six non-empty refs, unique disclosed fields, unique predicate ids.
2validate_privacy_profileSchema tag, uniqueness across every allow and forbid list, at least one sensitivity class.
3validate_lineageNode and edge shape, evidence-class floor, root shape, graph closure, redaction consistency, three recomputed digests, and the Ed25519 signature against the pinned lineage signer set.
4validate_leakage_ledgerLedger-to-profile identity, accepted == true, per-entry classification and note rules, recomputed total against the declared cap.
5validate_bundle_bindingsRef identity across all five artifacts, and the crypto report’s schema tag, verdict == verified, cryptographic_proof_verified == true, claim allowlist, and disclosed-field-set equality. The report’s own signature goes unchecked at this step.
6validate_privacy_profile_policyThe count budget, then per disclosed field: membership in some sensitivity class, absence from the forbid list, presence in the allow list. Then the hidden-predicate catalog and the same two lists for predicate ids.
7validate_leakage_coverageEvery disclosed field, every predicate, and the three derived facts have a policy-allowed ledger entry.
8verify_crypto_context_report_signature_with_trustThe report’s Ed25519 signature against the pinned report signer set. Its claims are then appended to the output.

Step 5 is where the artifacts stop being five documents and become one. Every ref is an identity check, not a lookup: the verifier is handed the objects and asserts that their declared names agree.

rendering
Ref identity in validate_bundle_bindings and validate_leakage_ledger. Beyond these edges, transaction_passport_ref must be byte-identical across the capsule, the subgraph, the ledger, and the profile.

Read the diagram for what is missing as much as for what is there. The subgraph is bound to the bundle by two things only: its id, and its transaction passport ref. Its own policy_profile_id and audience are required non-empty and are never compared against the profile, even though the ledger’s equivalents are. A subgraph generated for one audience can ride in a bundle addressed to another and this verifier will not notice.

Run the pipeline

Fifteen negative bundles sit beside the valid one under fixtures/proof-room/disclosure-lineage/, one per rule the pipeline enforces. The doctor scenario runs three of them; pointing chio proof verify at a directory runs any one.

disclosure-lineage · doctortranscript
$ source scripts/proof-room-quickstart-env.sh
$ chio proof doctor --scenario disclosure-lineage
chio proof doctor disclosure-lineage
  [passed] disclosure_lineage_valid_ledger: transaction passport verified
  [passed] disclosure_lineage_missing_ledger_entry: transaction passport rejected
  [passed] disclosure_lineage_unknown_lineage_root: transaction passport rejected
verdict: passed
exit 0

Four of the fifteen, read directly, show how far into the pipeline each rule sits. Every message carries the artifact or field at fault:

disclosure-lineage · negativestranscript
$ source scripts/proof-room-quickstart-env.sh
$ for n in missing-ledger-entry unknown-lineage-root \
         forbidden-disclosed-field lineage-depth-regression; do
$   chio proof verify \
    "fixtures/proof-room/disclosure-lineage/$n/transaction-passport.json" 2>&1 | head -1
$ done
error [urn:chio:error:cli:other]: proof verify: invalid disclosure lineage artifact: disclosed field absent from leakage ledger: tool_name
error [urn:chio:error:cli:other]: proof verify: invalid disclosure lineage artifact: unknown lineage root receipt: receipt-missing
error [urn:chio:error:cli:other]: proof verify: invalid disclosure lineage artifact: disclosed field forbidden by privacy profile: tool_name
error [urn:chio:error:cli:other]: proof verify: invalid disclosure lineage artifact: lineage node depth not greater than parent: receipt-child
exit 1

The first and the third are the same field, tool_name, refused by two different rules: one for being disclosed without a ledger entry, the other for being disclosed at all under the profile. The verifier does not merge them, and an operator reading only the first would fix the ledger and hit the second.

Three digests recomputed, three taken on faith

The subgraph declares six digests. The verifier recomputes three of them and checks only the hex shape of the rest. The frontier is the interesting one: it is the set of nodes that are never an edge’s from, which is to say the leaves of the subgraph. The filter does not read disclosure_state, so redacted leaves are in the frontier alongside disclosed ones.

crates/trust/chio-disclosure-lineage/src/verifier.rsrust
let parents = lineage
    .edges
    .iter()
    .map(|edge| edge.from.as_str())
    .collect::<BTreeSet<_>>();
let mut frontier = lineage
    .nodes
    .iter()
    .filter(|node| !parents.contains(node.id.as_str()))
    .map(|node| format!("{}:{}:{}", node.id, node.artifact_sha256, node.depth))
    .collect::<Vec<_>>();
if frontier.is_empty() {
    return Err(invalid("lineage frontier is empty"));
}
frontier.sort();
Ok(chio_core_types::sha256_hex(frontier.join("|").as_bytes()))

Above that sits checkpoint_inclusion_sha256, which must equal sha256("checkpoint_ref|frontier_sha256"), and above that subgraph_sha256, the canonical JSON hash of every subgraph field except schema, subgraph_sha256, and signature. That last digest is what the signature covers, so a mutation anywhere in the material, including in the two lower digests, invalidates it.

What the digest chain does not prove

checkpoint_inclusion_sha256 is a hash of two strings, not a Merkle inclusion proof. It binds the frontier to a checkpoint name; it says nothing about whether that checkpoint exists or contains those receipts. The real inclusion check in this family lives next door in verify_transparency_inclusion_proof, which walks a path to a root. Similarly, leakage_ledger_sha256, projection_manifest_sha256, and redaction_map_sha256 are checked for sha256 shape and nothing else. Nothing on the verify path recomputes them against the ledger, the manifest, or the redaction set they name, so the value a producer writes is the value a verifier accepts. The proof-room generator does hash the file (sha256_file over leakage-ledger.json), while the crate’s own in-memory test bundle sets the same field to the hash of the ledger’s id string; both pass, because the check that would separate them does not exist. The ledger is bound to the bundle by its id and its refs, not by its digest. lineage_anchor_ref is in the same position: required non-empty, folded into the signed material, and never resolved.

The node digests have the same character. artifact_sha256 is checked against sha256(receipt_ref), which is a hash of the identifier, not of the artifact. It proves the node is internally consistent. It does not commit the verifier to any particular receipt body, and a producer that never held the receipt can still emit a node that passes.


Two budgets, and only one of them is the verifier’s

A capsule can be refused for leaking too much in two different ways, and the two budgets have different owners.

The first is a count budget, and it belongs to the verifier. DisclosureProfileLeakageBudget holds max_disclosed_fields and max_hidden_predicates, both read off the privacy profile, and a capsule exceeding either is refused with disclosure leakage budget exceeded before any allow or forbid list is consulted.

The second is a score budget, and it travels with the ledger. Entry scores are summed and compared against max_allowed_leakage_score, a field on the ledger itself. The profile has no score cap. The crate’s own ARCHITECTURE.md describes this step as a total "within the profile’s maximum"; read the code instead. There is no such field on DisclosureVerifierPrivacyProfile, and the only writer of max_allowed_leakage_score outside the crate is the fixture generator, which sets it equal to the total it just computed. A producer that prices its own leakage also sets its own ceiling. The excerpt below is the tail of the per-entry loop and the two comparisons that follow it, so the bare closing brace in the middle is the loop’s.

crates/trust/chio-disclosure-lineage/src/verifier.rsrust
    if (entry.leakage_kind == "hidden_predicate" || entry.score > 1 || entry.cross_tenant_risk)
        && entry
            .residual_inference_note
            .as_ref()
            .is_none_or(|note| note.is_empty())
    {
        return Err(invalid(
            "sensitive leakage entry requires residual inference note",
        ));
    }
    if !entries.insert((entry.field.as_str(), entry.leakage_kind.as_str())) {
        return Err(invalid("duplicate leakage ledger entry"));
    }
    total_score = total_score.saturating_add(entry.score);
}
if total_score != ledger.total_leakage_score {
    return Err(invalid("leakage ledger total score mismatch"));
}
if ledger.total_leakage_score > ledger.max_allowed_leakage_score {
    return Err(invalid("leakage ledger score exceeds policy maximum"));
}

Scores are declared, not derived. Nothing computes a score from a sensitivity class, and cross_tenant_risk and derived_inferences add nothing to the total. What they do instead is force disclosure of the reasoning: any entry that is a hidden predicate, or scores above 1, or carries cross-tenant risk must ship a non-empty residual_inference_note. A producer can price its own leakage, but it cannot price a sensitive entry silently.

The shipped positive fixture sits exactly at its own cap, which is the clearest way to read the mechanism. Six entries, total 7, maximum 7:

FieldKindSensitivity classScore
capability_iddisclosed_fieldcapability_identifier1
tool_namedisclosed_fieldtool_identity1
amount_lte_100hidden_predicateamount_or_budget2
derived.crypto.issuer_statusderived_factruntime_assurance1
derived.crypto.revocation_freshnessderived_factruntime_assurance1
derived.crypto.presentation_timingderived_facttiming1

Only the hidden predicate carries a residual-inference note, because it is the only entry that trips the rule: predicate reveals capped amount band. Deleting that note refuses the bundle (disclosure_lineage_rejects_sensitive_leakage_without_residual_note). So does disclosing one more field: the profile’s max_disclosed_fields is 2, and the ledger has no headroom either.

The table is the shipped JSON at fixtures/proof-room/disclosure-lineage/valid-lineage-ledger/, which is what chio proof verify and chio proof doctor run. That is a different object from the one the unit tests use. disclosure_lineage_records_exact_leakage_budget_and_derived_facts pins the same six fields, kinds, classes and scores and the same 7 against 7, but on a bundle it builds in memory, with different entry ids and a different passport ref. The crate’s suite never reads the fixture directory.

One hidden predicate, spelled out

The set of predicates a capsule may hide is not a grammar. It is a hard-coded catalog with one entry, and a capsule predicate must match it on every field, including the projection slot:

crates/trust/chio-disclosure-lineage/src/verifier.rsrust
const SUPPORTED_HIDDEN_PREDICATES: &[SupportedHiddenPredicate] = &[SupportedHiddenPredicate {
    predicate_id: "amount_lte_100",
    kind: "amount_cap",
    field: "amount",
    operator: "<=",
    operand: "100",
    unit: "USD",
    proof_ref: "selective-disclosure-proof",
    projection_slot: 2,
}];

The predicate language on Selective Disclosure is broader; what a capsule may carry today is one cap at one operand in one currency. A predicate whose result is false is refused outright, so a capsule cannot prove a bound was breached, only that it held. Only an unrecognized predicate_id produces unsupported hidden predicate; a known id with any other field off catalog gets a per-field message instead, such as hidden predicate projection slot mismatch. Extending the catalog is a code change in this crate, not policy. The check runs over the capsule inside validate_privacy_profile_policy, so it constrains what a capsule declares, not what the BBS+ proof proved. Binding the declaration to the projection is the CLI’s job, below.


Trust is an argument, not a store

DisclosureLineageVerifierTrust holds two independent key sets: one for lineage signers, one for crypto context report signers. A key in the first is not accepted for the second unless the caller puts it in both. Both default to empty, and an empty set trusts nobody, which makes the convenience wrapper verify_disclosure_lineage_bundle a function that cannot accept a signed bundle at all. That is the intended shape: disclosure_lineage_rejects_without_pinned_signer_keys asserts the refusal.

Both signatures use the wire format sig-ed25519:<pubkey-hex>:<signature-hex> and different signing schemes. A lineage subgraph signs the ASCII bytes of its own hex digest string; a crypto context report signs itself canonically with the signature field cleared. A self-certifying sig-sha256:<digest> form is rejected by the prefix check, which disclosure_lineage_rejects_recomputed_digest_only_signature pins.

Callers supply the keys from the environment. chio-cli and chio-proof-room each carry a copy of the same loader, reading CHIO_DISCLOSURE_TRUSTED_LINEAGE_SIGNER_KEYS and CHIO_DISCLOSURE_TRUSTED_CRYPTO_CONTEXT_REPORT_SIGNER_KEYS. A missing variable fails before the trust object exists: CHIO_DISCLOSURE_TRUSTED_LINEAGE_SIGNER_KEYS must pin trusted disclosure lineage signer keys, and the parallel message reading disclosure crypto context report signer for the other. The requirement is scoped to the route, not the command: chio proof verify loads this trust only when the evidence graph routes to the disclosure lineage family, so a passport without one verifies with neither variable set.

The CLI does more than this crate does. chio proof verify loads the BBS proof, the verification context, the projection manifest, and the transparency inclusion proof from the same evidence graph, re-derives the crypto context report from them, and requires the derived claims and disclosed fields to match the report in the bundle set for set. It also requires the projection manifest to declare every hidden predicate the capsule carries. That work is nominally gated on the report claiming claim.disclosure.crypto_context_bound, which is not a real gate: this crate refuses any report missing that claim, so nothing reaches an accept without the re-derivation. This crate, on its own, trusts a validly signed report and never opens a manifest.


Guarantees and limits

StatusClaimEvidence
ShippedA bundle is accepted only if all five artifacts agree on refs, the subgraph signature verifies against a pinned key, and every disclosed field and predicate has a policy-allowed ledger entry. The first failure returns and no partial report is built.verify_disclosure_lineage_bundle_with_trust, crates/trust/chio-disclosure-lineage/src/verifier.rs
Registered claimclaim.disclosure.lineage_subgraph_bound and claim.disclosure.leakage_ledger_complete, both enforced, each with a proof manifest naming rust tests, a positive fixture, a negative fixture, and registered schemas.spec/registries/claim-registry.v1.json; manifest.disclosure.lineage_subgraph_bound, manifest.disclosure.leakage_ledger_complete
LimitBoth manifests cite the mirrored suite, not the owning one. Every disclosure-lineage test they name lives in the re-exporting crate’s five-case file, and all five also exist in the owning crate’s thirty. Neither manifest points at chio-disclosure-lineage, so a claim audit that follows the manifest reads the smaller file.manifest.disclosure.lineage_subgraph_bound and manifest.disclosure.leakage_ledger_complete, both citing crates/trust/chio-selective-disclosure/tests/disclosure_lineage.rs
Proved by testThirty cases in the owning crate cover budget breach, unclassified fields, score mismatch, depth regression, frontier mismatch, evidence below floor, missing residual note, unsupported predicate kind and slot, unsupported node and edge kinds, forbidden fields, missing ledger and derived-fact entries, six crypto-report binding failures, unsigned and untrusted signers, and unknown roots.crates/trust/chio-disclosure-lineage/tests/disclosure_lineage.rs
Proved end to endchio proof verify on the shipped fixture emits chio.disclosure.lineage-verifier-report.v1 with verdict: verified and both lineage claims; removing one trusted-key variable fails the command. The doctor scenario runs three fixtures: the valid one, plus missing-ledger-entry and unknown-lineage-root with their expected error substrings.crates/products/chio-cli/tests/proof_verify.rs; DISCLOSURE_LINEAGE_CHECKS in chio-cli/src/cli/dispatch/proof/doctor.rs
LimitThe score budget is declared by the artifact it constrains. max_allowed_leakage_score lives on the ledger, the profile has no score cap, and nothing reconciles them. Only the count budget is verifier-owned.DisclosureProfileLeakageBudget versus DisclosureLeakageLedger in src/types.rs
LimitThree of the subgraph’s six digests are shape-checked only. The leakage ledger, projection manifest, and redaction map digests are never recomputed against the artifacts they name.validate_lineage; no leakage_ledger_sha256 comparison exists in crates/
LimitA node’s artifact digest hashes its reference string, not the artifact. Node identity is internally consistent, not content-bound.sha256_hex(node.receipt_ref.as_bytes()) != node.artifact_sha256
LimitClosure is one-directional. Every declared parent needs an edge; no edge needs a declared parent. An edge outside parent_ids is accepted, and it only shows up indirectly by moving its source out of the frontier.validate_lineage_closure versus validate_lineage_edges
LimitRedaction accounting covers nodes only. An edge may carry disclosure_state: redacted with no entry in redactions and no reason.validate_lineage_redactions, which iterates the redacted node set
LimitThe ledger’s accepted flag and each entry’s allowed_by_profile are producer assertions the verifier requires to be true and never derives. For derived_fact entries there is no profile list behind them at all. subject_artifact_sha256 is shape-checked and never compared.validate_leakage_ledger
Not checkedThe subgraph’s own audience, policy_profile_id, challenge_nonce, and generated_at against the profile or the clock. The ledger’s audience is checked; the subgraph’s is only required non-empty.validate_lineage versus validate_leakage_ledger
Not checkedThe crypto report’s context_id, evidence_class, and rejected_checks. A report carrying rejected checks passes as long as its verdict is verified and its signature holds.validate_bundle_bindings
By design elsewhereThe profile’s cryptographic policy is carried here and never evaluated here. nonce_policy, allowed_proof_mechanisms, allowed_issuer_keys and the algorithm lists get a non-empty and uniqueness check; required_key_epoch_min, forbidden_key_epochs, required_status_freshness_seconds, required_transparency_state, max_presentation_age_seconds and required_holder_binding are never read at all.chio-selective-disclosure::crypto_context::policy, which produces the signed report this crate verifies
UnsupportedAny hidden predicate other than amount_lte_100, and any predicate whose result is false.SUPPORTED_HIDDEN_PREDICATES, validate_hidden_predicate
Not shippedProduction issuance. No workspace code outside tests and the fixture regenerator builds a capsule or signs a subgraph, and the regenerator signs with a constant seed. Treat capsule production as an integration you write.The only non-test sign_lineage_subgraph call site is crates/products/chio-cli/src/cli/dispatch/proof/fixture.rs

Next Steps

  • Selective Disclosure · the BBS+ projection, the predicate language, and the proof this page’s report attests to
  • Delegation Witnesses · the other signed graph a swarm verifier recomputes rather than trusts
  • Swarm Authority · the bundle verifier this one sits beside, and what an unpinned verifier verifies
  • Evidence and Verification · claim ids, proof manifests, and how a registered claim is meant to be read
  • Evidence Export · what one authority hands out when the counterparty is not a disclosure verifier
Disclosure Lineage · Chio Docs