Chio/Docs
LOGIN · JOIN

PlatformThe Delegation Graph

Swarm

Swarm Authority

One pure function verifies a delegation bundle whole. What it checks, in what order, and what it refuses to answer.

The rung framing lives on the Overview

Swarm Overview owns the rung: the filing test, the nine pinned schemas, the 11 negative fixtures, and the admission sequence a child dispatch walks. This page owns the mechanism inside that sequence: the bundle the verifier accepts, the order it reads it in, the report it emits, and the refusals that keep a partial verdict from existing. Every refusal string it can return is enumerated in Swarm Denial Codes.

Source

This page reflects spec/schemas/chio-swarm/v1/authority-verifier-report.schema.json and spec/PROTOCOL.md section 6.4.2 in the chio repository. The Swarm Protocol reference carries the full set of artifacts and the verification order.


One entry point, no state

chio-swarm-authority exports one verification function. It holds no state, performs no I/O, and reads no clock: every fact it checks comes from the bundle it was handed or from the key slice the caller passed, and the current time arrives as the bundle field now_unix_ms.

crates/kernel/chio-swarm-authority/src/verifier.rs37-40rust
pub fn verify_swarm_authority_bundle(
    bundle: &SwarmAuthorityBundle,
    trusted_witness_issuer_keys: &[PublicKey],
) -> Result<SwarmAuthorityVerifierReport, SwarmAuthorityError> {

Verification is all or nothing. Either the whole bundle verifies and the function returns a report whose verdict is the literal string verified, or it returns SwarmAuthorityError and there is no report at all. There is no partial verdict, no per-artifact error list, and no warning state. The rest of the crate’s public API is constructors: mint_*, sign_*, and the budget fan-out and fan-in helpers. Their only callers in the workspace are tests, in chio-swarm-authority, chio-runtime-core, and the CLI proof contract suite. This crate specifies verification; issuance stays with whoever operates the planner.


The bundle

SwarmAuthorityBundle is nine fields. Every type in the crate that crosses the wire carries deny_unknown_fields, down to the nested AttenuationWitness it borrows from chio-core-types, so a bundle with an extra key fails deserialization before the verifier sees it. The *MintRequest structs are plain Rust and are never deserialized.

FieldCarriesRequired non-empty
task_graphThe signed plan: one root, tasks with depth and scope hash, delegation edges, joins, and the max_depth / max_fanout ceilings. Task GraphsYes, at least one task
continuation_tokensOne per non-root task. The artifact a child actually runs against. Continuation TokensYes
witness_chainsOne per graph edge, each a sequence of hops carrying a scope-subset proof. Delegation WitnessesYes
join_receiptsOne per declared join: parent set hash, DAG ordinal, join predicateExactly the graph’s joins
route_plan_receiptsSelected route, candidate-set digest, registry snapshot hash, bridge, protocol target, egress contract and constraintsExactly the graph’s route_plan_refs
budget_poolPool total plus per-task allocations across five states. Sub-Agent BudgetsYes, and its id must equal budget_pool_ref
revocation_epochRevoked subjects and revoked task ids inside a validity windowYes, and its id must equal revocation_epoch_ref
terminal_receiptsGraph closure: completed tasks, joins, routes, per-dimension budget rollups. One or more; each must close the whole graphYes. serde(default) lets the field be absent from JSON, and both an absent field and an empty list are rejected with missing swarm terminal graph receipt
now_unix_msThe evaluation instant every freshness check runs againstSupplied by the caller

Issuers are strings. An issuer prefixed did:chio: must be self-certifying: exactly 64 lowercase hex characters after the prefix, parsed as the Ed25519 public key that signed the artifact. A bare hex key is also accepted. Either way the key that comes out must appear in trusted_witness_issuer_keys, which is what makes the DID a pin rather than a name.

Closed vocabularies and one gate

Several fields accept a fixed set of values rather than free strings. These are the limits most likely to surprise someone minting a bundle from the schemas alone.

  • A route-plan receipt’s egress_constraints must be non-empty, and every entry must be the literal deny-private-network. Any other value is unsupported swarm route-plan egress constraint. Its attenuation_decision must be the literal accepted.
  • A route-plan receipt’s selected_route and egress_contract_id must each start with its own bridge_id followed by :, and protocol_target must start with the same id followed by ://.
  • join_predicate must be all_success, any_success, or quorum:N with N between one and the expected parent count. A graph join needs at least two parents.
  • A witness chain of more than one hop is rejected unless the signed graph sets multiHopWitnessChains. Multi-hop delegation is a per-graph gate carried inside the signed artifact, not a verifier default.
  • The allocation a continuation names must be in state active, and the pool’s summed max_units may not exceed its total_units.

The order of checks

The entry point is a straight sequence of fallible calls. Order is load bearing, so it is worth reading literally. This is the whole body up to the point where the claim list is assembled:

crates/kernel/chio-swarm-authority/src/verifier.rs41-71rust
require_trusted_witness_issuer_keys(trusted_witness_issuer_keys)?;
validate_task_graph(&bundle.task_graph, bundle.now_unix_ms)?;
verify_task_graph_signature(&bundle.task_graph, trusted_witness_issuer_keys)?;
require_signed_swarm_delegation_evidence(bundle)?;
let graph_sha256 = canonical_sha256(&bundle.task_graph)?;
let task_by_id = task_index(&bundle.task_graph)?;
let edge_set = edge_set(&bundle.task_graph.edges);
let route_by_id =
    validate_route_plan_receipts(bundle, &task_by_id, trusted_witness_issuer_keys)?;
let join_by_id = validate_join_receipts(bundle, &task_by_id, trusted_witness_issuer_keys)?;
let allocation_by_id = validate_budget_pool(bundle, &task_by_id)?;
validate_revocation_epoch(bundle, &task_by_id, trusted_witness_issuer_keys)?;
validate_terminal_graph_receipts(
    bundle,
    &task_by_id,
    &route_by_id,
    &join_by_id,
    &allocation_by_id,
    trusted_witness_issuer_keys,
)?;
validate_continuation_tokens(&ContinuationValidationContext {
    bundle,
    graph_sha256: &graph_sha256,
    task_by_id: &task_by_id,
    edge_set: &edge_set,
    route_by_id: &route_by_id,
    join_by_id: &join_by_id,
    allocation_by_id: &allocation_by_id,
    trusted_witness_issuer_keys,
})?;
validate_witness_chains(bundle, &task_by_id, &edge_set, trusted_witness_issuer_keys)?;

Trust comes first, so an unconfigured verifier stops before reading a single artifact. Graph structure and the graph signature come next, because every later check indexes into a graph that has already been proved acyclic, single-rooted, depth-consistent, and signed by a pinned issuer. Routes, joins, allocations, and the epoch are validated and indexed before continuations, because a continuation token names all four and its checks are lookups into those indexes. Witness chains run last, over the edge set, as a cover check.

Totality, not spot checks

Routes, joins, witness chains, and terminal receipts are checked in both directions: no declared reference may be unbacked, and no supplied artifact may be undeclared. That symmetry is what makes the bundle atomic. Continuation tokens are the one asymmetric family, noted below.

  • Every route_plan_refs entry must have a receipt, and every receipt must match a ref. Duplicates are rejected on sight.
  • Every graph join must have a receipt whose next_task_id equals the join’s, and whose expected-parent count equals the join’s parent count.
  • Every edge must be covered by exactly one witness chain. A second chain over the same edge is a duplicate; an uncovered edge is missing swarm delegation witness chain.
  • Every task with a parent must name a continuation token, and that token’s child_task_id must be the naming task. Token ids and nonces must be unique within the bundle. Each token binds either a parent_task_id or a join_receipt_id, never both and never neither.
  • The reverse does not hold for continuations. validate_continuation_tokens walks tasks to find missing tokens, but never walks tokens to find unreferenced ones. A second, fully valid token for a child that already has one is accepted; it only inflates continuation_count in the report. Deciding which token a child may present is the caller’s job, and the runtime does it by naming one token id in the request.
  • The terminal receipt’s completed-task set must equal the graph’s entire task set, its join set every join, its route set every route. A terminal receipt cannot close part of a graph.

Digests are recomputed, never trusted

Five digest fields are recomputed from the bundle’s own contents and compared, rather than accepted as declared.

FieldRecomputed from
continuation.graph_sha256Canonical JSON of the whole task graph, signature field included
continuation.witness_chain_sha256Canonical JSON of the referenced chain, whose parent and child task ids must also match the token
revocation_epoch.root_hashCanonical JSON of the sorted revoked-subject and revoked-task lists
join.parent_set_hashCanonical JSON of the chain id plus the sorted actual parent receipt ids
terminal.budget_rollupsPer-dimension sums over every allocation in the pool, compared as whole maps. Each rollup’s five state counts must also sum to its own declared total_units, and the sum across dimensions may not exceed the pool total

The first row closes an obvious attack. A tampered graph invalidates both the graph signature and every token digest, and the two are computed over different bytes: the signature body drops the signature field, the token digest keeps it. Repairing one does not repair the other. swarm_authority_stage0_rejects_task_graph_tampering_after_continuations_are_resealed widens max_fanout, reseals every continuation against the new digest, and still lands on swarm task graph signature invalid.


Two refusals worth stating exactly

Claim: an unconfigured verifier verifies nothing

FieldValue
StatusShipped, proved by test.
ClaimAn empty trusted-key slice rejects the bundle on the first line, before any artifact is parsed or any structural check runs. Most artifact families then repeat the same emptiness check ahead of their own pin comparison; the revocation epoch skips the repeat and fails the comparison instead, because no key can match an empty set.
SubjectOne call to verify_swarm_authority_bundle, any bundle.
Evidencerequire_trusted_witness_issuer_keys; swarm_authority_stage0_rejects_unpinned_witness_issuer and ..._rejects_root_only_bundle_without_trusted_witness_keys. The CLI reads the slice from CHIO_SWARM_TRUSTED_WITNESS_KEYS as comma-separated hex and errors when the variable is absent.
LimitTrust is entirely caller-supplied. The crate does not resolve, rotate, or expire keys, and it has no opinion on which issuer signs which artifact class: one slice pins the graph, the routes, the joins, the epoch, the terminal receipts, the continuations, and every witness hop alike.

Claim: a root-only bundle is denied, not partially verified

A graph with one task and no edges is structurally valid. It is still rejected, and the reason is the shape of the report rather than the shape of the graph:

crates/kernel/chio-swarm-authority/src/verifier.rs158-167rust
fn require_signed_swarm_delegation_evidence(
    bundle: &SwarmAuthorityBundle,
) -> Result<(), SwarmAuthorityError> {
    if bundle.continuation_tokens.is_empty() || bundle.witness_chains.is_empty() {
        return Err(rejected(
            "signed swarm delegation evidence missing: continuation tokens and witness chains are required",
        ));
    }
    Ok(())
}

The verified-claim list is assembled conditionally. Four of the eight claims are pushed only when their collection is non-empty, so a bundle with no continuations and no witness chains would otherwise return a report reading verified while silently carrying fewer claims than a reader expects. Denying the root-only case is what keeps verified from meaning two different things. swarm_authority_stage0_rejects_root_only_bundle_without_signed_swarm_evidence pins it. Downstream, both proof paths compare the report’s verified_claims against the required_claims in the verifier policy shipped with the bundle and fail when a required claim is missing, so a claim that quietly stopped being emitted fails there too. That list is per policy, not per crate: the recursive-delegation fixture requires seven of the eight swarm claims and omits claim.swarm.terminal_graph_receipt_bound. spec/registries/proof-manifest.v1.json registers all eight against test, fixture, and schema references; it is a registry checked for liveness, not the enforcement path.

Per-hop flags are a rendering, not a per-hop verdict

SwarmAuthorityHopReport carries authority_verified, attenuation_verified, lineage_verified, route_verified, and budget_verified. All five are written as the literal true while building the report, which is only reachable after the entire bundle verified. They are a per-hop presentation of one bundle-wide verdict. Do not read a hop report as an independently checked hop, and do not expect a mixed report: on any failure there is no report to read.

Three consumers, one definition

The runtime path is pre-dispatch and trusted-input. It engages only for a request whose governed intent carries a chioSwarm context naming seven evidence references. chio-runtime-core loads the bundle from its own admission store by task-graph evidence id, overwrites now_unix_ms with the request clock, checks that each referenced evidence id and its canonical SHA-256 match the stored artifact, compares live route metadata against the route-plan receipt, calls the verifier, and only then consumes the continuation id. A verifier rejection becomes the denial code chio_swarm_authority_rejected, carrying the verifier’s own message as detail; an evidence id or hash that does not match the stored artifact becomes chio_swarm_authority_ref_mismatch instead. Consumption is conditional on the token’s mode: only single_use tokens are consumed, and a resumable token is presented again without replay accounting.

The runtime arm is an embedder API, not a default deployment

ChioRuntimeAdmissionHook starts with an empty witness key vector and fills it only through chio-runtime’s with_swarm_witness_keys builder. No product binary in the repo calls that builder, and nothing outside tests calls insert_swarm_authority_bundle to populate the admission store. In a default deployment a swarm-bound request is therefore denied, for a missing bundle or an empty key slice, rather than admitted. Verification is shipped and exercised by chio-runtime-core’s admission tests; wiring it into a running kernel is work an embedder does, not a flag an operator flips.

The proof paths are after the fact and untrusted-input. chio proof verify runs the swarm arm of its local family dispatch, and chio-proof-room calls the same function on an embedded bundle reconstructed from an evidence graph. Both resolve their key slice from CHIO_SWARM_TRUSTED_WITNESS_KEYS and neither adds a check the runtime has and the crate lacks: the reference hashing, the route-metadata comparison, and continuation consumption are all runtime-side. A fourth caller, chio-conformance’s r_t03_recursive_swarm_conformance, is a test. What the call sites share is that the definition of a valid delegation is one function, not three.

Run the verifier

The shipped fixture bundle exercises the whole order of checks: a four-task graph, three witness chains, three route plans, one join, one pool, one epoch. chio proof verify writes its report to stdout as one JSON object, so jq is the readable way to take the verdict and the claims out of it.

swarm-authority · validtranscript
$ source scripts/proof-room-quickstart-env.sh
$ chio proof verify \
  fixtures/proof-room/swarm-authority/valid-recursive-delegation/transaction-passport.json \
  | jq '{verdict, passport_id, verified_claims}'
{
  "verdict": "verified",
  "passport_id": "passport-swarm-valid",
  "verified_claims": [
    "claim.swarm.task_graph_bound",
    "claim.swarm.continuation_fresh",
    "claim.swarm.attenuation_witness_chain_bound",
    "claim.swarm.route_plan_bound",
    "claim.swarm.join_receipt_bound",
    "claim.swarm.budget_pool_bound",
    "claim.swarm.revocation_epoch_bound",
    "claim.swarm.terminal_graph_receipt_bound"
  ]
}
exit 0

Eight claims, one per artifact family the bundle carries. The full report also carries a per-hop hopReports array under family_reports, with attenuationVerified, routeVerified, budgetVerified, and lineageVerified per child task, plus the SHA-256 of the claim set, the evidence graph, and the verifier policy.

Point the same command at a sibling directory and the verifier refuses. The bundle at fixtures/proof-room/swarm-authority/revoked-task/ is signed and internally consistent; its epoch carries "revokedTaskIds":["task-child-a"] where the valid epoch carries an empty list. Its paired descriptor at negatives/revoked-task.json names the claim it targets, claim.swarm.revocation_epoch_bound.

swarm-authority · revoked-tasktranscript
$ source scripts/proof-room-quickstart-env.sh
$ chio proof verify \
  fixtures/proof-room/swarm-authority/revoked-task/transaction-passport.json
error [urn:chio:error:cli:other]: proof verify: swarm task is revoked: task-child-a
context: {"domain":"cli","severity":"error","stability":"deprecated","string_code":"CHIO-CLI-OTHER"}
suggested fix: Preserve the original message and migrate the call site to a specific registry code when touched.
exit 1

No report is written. The verifier returns on the first failing check, so the seven claims that would have verified are not reported either, and the refusal names the task rather than the epoch. This is the shape every refusal on this rung takes; the Denial Codes page prints all 11 from one loop.


Guarantees and limits

StatusClaimEvidence
ShippedVerification is a pure function of the bundle and the key slice. No filesystem, no network, no clock, no interior mutability.crates/kernel/chio-swarm-authority/src/verifier.rs
Proved by testA widened child scope inside a signed witness hop is rejected, for generated parent limits and overshoots.swarm_authority_stage0_rejects_generated_recursive_scope_widening, a 32-case proptest asserting swarm attenuation witness invalid
Proved by testThe stage-0 suite covers the rejection paths one at a time: tampered or resealed signatures on the graph, continuations, join receipts, route plans, witness hops, and the epoch; stale and future timestamps; nonce replay; ungated multi-hop chains; released and oversubscribed allocations; revoked tasks and revoked subjects.crates/kernel/chio-swarm-authority/tests/swarm_authority_stage0.rs
Proved by fixture replay11 negative fixture bundles are replayed on every run of chio proof doctor --scenario proof-package: each directory must fail verification, and the failure must match the expected_failure_code read out of its paired descriptor. A match is the raw error text containing that code, or the slug derived from the error carrying it at a : or whitespace boundary, so a reworded rejection reads as a mismatch rather than a silent pass.fixtures/proof-room/catalog.json; package_negative_expected_failure and check_transaction_passport_rejects in chio-cli/src/cli/dispatch/proof/doctor.rs; proof_doctor_accepts_proof_package_evidence
Declared, not signedThe descriptors in negatives/ are unsigned JSON: schema chio.swarm-authority.negative-fixture.v1, a claim_ref, the base_fixture the case mutates, a case name, and the expected code. The signed material lives in the fixture directory beside them. Nine of the 11 are also registered as claim evidence in the proof manifest; egress-constraint-unsupported and max-depth-exceeded are not.fixtures/proof-room/swarm-authority/negatives/*.json; spec/registries/proof-manifest.v1.json
Shipped, not wiredThe runtime admission arm is a library API. The facade hook’s trusted key slice starts as an empty vector and is filled only by its own with_swarm_witness_keys builder, which no product binary calls, and only tests insert bundles into the admission store. The forwarding call inside core_hook() is production code, and it forwards that empty vector. The proof paths are the ones an operator can run today, through chio proof verify and the proof room.chio-runtime/src/lib.rs:151,188,226; chio-runtime-core/src/admission_hook.rs:203,240; call sites of insert_swarm_authority_bundle
Not claimedStable machine-readable error codes from the crate. SwarmAuthorityError has two variants, Rejected(String) and Canonical(String). Callers that need a code map the whole error: the runtime to one denial code, the proof room to a slug. Match on the message only where a fixture already pins it.crates/kernel/chio-swarm-authority/src/error.rs
UnsupportedCross-bundle replay detection. Continuation nonce and token-id uniqueness are checked within one bundle. A second presentation of the same token in a second bundle is the admission store’s problem, and the store only tracks tokens the runtime asked it to consume, which excludes resumable ones.validate_continuation_tokens; consume_swarm_continuation and the runtime denial code chio_swarm_continuation_replay
UnsupportedIssuance in production. Every constructor in the crate is exercised only by tests, and nothing in the crate mints authority at request time.Call sites of mint_*, sign_*, reserve_swarm_budget_fanout, release_swarm_budget_fanin

Next Steps

  • Task Graphs · the first artifact in the bundle: one root, depth arithmetic, and the ceilings
  • Continuation Tokens · the artifact a child runs against, and the id the runtime burns before dispatch
  • Delegation Witnesses · the per-hop attenuation proof the verifier recomputes for every edge
  • Sub-Agent Budgets · the pool split at fan-out, reclaimed at fan-in, and reconciled by the terminal receipt
  • Swarm Overview · the rung, the nine schemas, and the admission sequence this verifier sits inside
  • Capabilities · the scope and attenuation model a witness hop proves a subset of
  • Authority & Rotation · the rung below, where authority is one keypair you hold rather than a chain you verify
  • Proof Room · where the recursive-delegation fixtures and the negative cases live
  • 3-Vendor Walkthrough · one delegated scenario carried end to end