Chio/Docs
LOGIN · JOIN

Swarm authority

Every child task's authority traces back to a signed plan, and the kernel admits or refuses the child on that trace.

Concept chio@5f63e6e

On this page

A coordinator can split work across many agents, and any kernel that serves a child task can check that the child’s authority descends, hop by hop, from a plan its issuer signed.

Chio checks the delegation as one signed bundle: the task graph, a witness for every edge, the selected routes, the budget pool, the revocation epoch, and the receipts that close the graph. The running example is the bundle chio proof fixture generate recursive-runtime-swarm writes: one root task delegating to three children, the shape the fixer builds when it splits issue #417 across three helpers. The fixture names its tasks generically, so the outputs below read task-root and task-child-a.

A delegation is checked as one bundle, accepted whole or refused

Every record in the bundle points at the graph, and the graph names every record. A child is admitted only when all of them agree.

Signed task graphChoose a view.
depth 0depth 13 of maxFanout 3maxDepth 2joinpool · 10,000 usd_minornext task: task-rootσσσreserve_budget 3 → 1κtask-root07bedb…a1task-child-a582bb5…2,500a2task-child-b582bb5…2,500a3task-child-c582bb5…2,500ρjoin-child-resultsmaxDepth 2 · maxFanout 3pool 10,000σσσ3 → 1κtask-roota12,500a22,500a32,500ρjoin-child-resultsnext task: task-root

a1task-child-aa2task-child-ba3task-child-c

Every edge carries a signed narrowing

Each delegation edge has exactly one witness chain. The verifier anchors the first parent scope to task-root’s scope hash 07bedb… and the last child scope to the child’s 582bb5…, then recomputes the subset relation from the scope text. The task-child-b witness keeps commerce/reserve_budget and cuts max_invocations from 3 to 1. Delegation Witnesses.

The signed task graph and budget pool from the valid-recursive-delegation fixture, with widths drawn to the pool’s units; the overdrawn pool is the budget-allocations-exceed-pool fixture and the two-hop chain is the verifier’s source test. Route plans, continuations, and the revocation epoch appear by name only.Delegation Witnesses

In the fixture, task-root delegates to task-child-a, task-child-b, and task-child-c under a graph that signs maxDepth 2 and maxFanout 3. Their results meet at join-child-results, whose next task is task-root. Each edge carries one witness chain anchored at the root’s scope hash 07bedb… and the child’s 582bb5…; the task-child-b witness keeps commerce/reserve_budget and cuts max_invocations from 3 to 1.

The graph indexes everything else. task-child-a names continuation-child-a, route-child-a, and budget-child-a, and witness-child-a covers its edge. The graph itself names budget-pool-swarm-valid and revocation-epoch-swarm-valid. Delegated scope is evidence about authority; the runtime process tree separately decides which descendants a cancellation reaches, as the process authority tree shows.

Read more in Swarm Protocol, which defines every artifact in the bundle.

One pure function decides whether a delegation is valid

verify_swarm_authority_bundle takes the bundle and the trusted issuer keys and returns a complete report or an error. The caller supplies the evaluation time as 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> {

The function reads only its arguments, with no filesystem, network, clock, or interior mutability. A successful report carries verdict: verified; a failed check returns SwarmAuthorityError with no partial report.

Four callers share it. Runtime admission in chio-runtime-core calls it before dispatch. chio proof verify calls it from the swarm arm of its family dispatch, chio-proof-room calls it on a bundle reconstructed from an evidence graph, and chio-conformance calls it in r_t03_recursive_swarm_conformance. Every path applies one definition of a valid delegation.

Trust comes first, and each later check reads an index already verified

The entry point is a straight sequence of fallible calls, 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)?;

An unconfigured verifier stops before it reads a single artifact. Graph structure and the graph signature come next, so every later check indexes into a graph already known to be 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. Witness chains run last, over the edge set, as a cover check.

Routes, joins, witness chains, and terminal receipts are checked in both directions: every declared reference is backed, and every supplied artifact is declared. That symmetry makes the bundle atomic. A tampered graph also breaks every continuation’s graph_sha256, and the two digests cover different bytes, so repairing one leaves the other broken.

Read more in Totality rules and recomputed digests.

The kernel admits a swarm-bound call only after its bundle verifies

Runtime admission runs before dispatch, for a request whose governed intent carries a chioSwarm context naming seven evidence references. The host installs ChioRuntimeAdmissionHook with the operator’s trusted keys through with_swarm_witness_keys and stores signed bundles with insert_swarm_authority_bundle.

  1. Load. chio-runtime-core loads the bundle from its admission store by task-graph evidence id.
  2. Clock. It overwrites now_unix_ms with the request clock.
  3. Match. Each referenced evidence id and its canonical SHA-256 must match the stored artifact, or the call is refused with chio_swarm_authority_ref_mismatch.
  4. Route. Live route metadata is compared against the route-plan receipt.
  5. Verify. The verifier runs; a rejection becomes chio_swarm_authority_rejected, carrying the verifier’s message as detail.
  6. Consume. A single_use continuation id is consumed, and a second presentation is refused with chio_swarm_continuation_replay. A resumable token is presented again without replay accounting.

The proof paths run after the fact, on untrusted input. chio proof verify and the Proof Room read the keys pinned for the swarm.witness role, from --trust or the default trust file. Reference hashing, route comparison, and continuation consumption belong to the runtime path alone.

Replay protection has two layers. Token ids and nonces are unique within one bundle, and the admission store refuses a second presentation of a consumed single_use token across bundles.

Worked Rust example: Install swarm admission on a working kernel

Install swarm admission on a working kernel

Register a local tool handler, issue its capability, populate the evidence store, and install ChioRuntimeAdmissionHook with trusted witness keys. Calls go through ChioKernel::evaluate_tool_call_with_metadata.

Use the Rust example setup, or download the complete source project. The files below belong to that project; shared constructors and configuration are included in the download.

Run it

bash
cargo run --locked --features runtime --bin runtime-host

Implementation

runtime-host.rsrust
#[path = "../admission_support.rs"]
mod admission_support;
use admission_support::*;
use chio_core_types::{capability::scope::ChioScope, crypto::Keypair};
use chio_kernel::*;
use chio_runtime_core::*;
use serde_json::{json, Value};
use std::sync::{
    atomic::{AtomicUsize, Ordering},
    Arc,
};
use swarm_authority_examples::*;

struct Analysis {
    calls: Arc<AtomicUsize>,
}
#[async_trait::async_trait]
impl ToolServerConnection for Analysis {
    fn server_id(&self) -> &str {
        "analysis"
    }
    fn tool_names(&self) -> Vec<String> {
        vec!["record_result".into()]
    }
    fn tool_is_read_only(&self, _tool: &str) -> bool {
        false
    }
    async fn invoke(
        &self,
        tool: &str,
        args: Value,
        _bridge: Option<&mut dyn NestedFlowBridge>,
    ) -> Result<Value, KernelError> {
        if tool != "record_result" {
            return Err(KernelError::ToolServerError("unknown analysis tool".into()));
        }
        self.calls.fetch_add(1, Ordering::SeqCst);
        Ok(json!({"recorded": args}))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let kernel_key = Keypair::generate();
    let mut kernel = ChioKernel::new(KernelConfig {
        keypair: kernel_key.clone(),
        ca_public_keys: Vec::new(),
        max_delegation_depth: 3,
        policy_hash: chio_core_types::sha256_hex(include_bytes!("runtime-host.rs")),
        allow_sampling: false,
        allow_sampling_tool_use: false,
        allow_elicitation: false,
        max_stream_duration_secs: DEFAULT_MAX_STREAM_DURATION_SECS,
        max_stream_total_bytes: DEFAULT_MAX_STREAM_TOTAL_BYTES,
        require_web3_evidence: false,
        // Keys and budget counters live for one process. A SQLite store
        // retains parent capabilities and receipts for this run.
        allow_ephemeral_receipt_log: true,
        allow_ephemeral_revocation_store: true,
        checkpoint_batch_size: DEFAULT_CHECKPOINT_BATCH_SIZE,
        retention_config: None,
        memory_budget: chio_kernel::MemoryBudgetConfig::defaults(),
        deadlines: chio_kernel::HotPathDeadlineConfig::default(),
    });

    kernel.set_federation_local_kernel_id("kernel.vendor-b");
    let calls = Arc::new(AtomicUsize::new(0));
    kernel.register_tool_server(Box::new(Analysis {
        calls: calls.clone(),
    }));
    let worker = Keypair::generate();
    let capability = kernel.issue_capability(
        &worker.public_key(),
        ChioScope {
            grants: scope_for("analysis", "record_result", 10).grants,
            ..ChioScope::default()
        },
        300,
    )?;
    kernel.set_capability_trust_root(
        kernel_key.public_key(),
        chio_core_types::capability::attenuation::scope_hash(&capability.scope)?,
    );
    let store = InMemoryRuntimeAdmissionStore::new();
    let mut admission = bundle();
    admission.schema = "chio.runtime.admission-bundle.v1".into();
    admission.destructive = false;
    admission.lease_id = None;
    admission.governance_receipt_id = None;
    let args = json!({"record": "analysis-7", "value": "complete"});
    admission.binding.capability_id = capability.id.clone();
    admission.binding.tool_args_sha256 = tool_args_sha256(&args)?;
    admission.binding.origin_kernel_id = None;
    let admission_hash = runtime_admission_bundle_sha256(&admission)?;
    store.insert_bundle(admission)?;
    let swarm = build_bundle()?;
    let route = &swarm.route_plan_receipts[0];
    let metadata = json!({"route": {"bridge":route.bridge_id, "protocolTarget":route.protocol_target, "selectedRoute":route.selected_route}});
    store.insert_swarm_authority_bundle(swarm.clone())?;
    let mut request =
        chio_swarm_runtime_request(args, admission_hash, swarm_runtime_context(&swarm)?)?;
    request.capability = capability;
    request.agent_id = worker.public_key().to_hex();
    let hook = allowing_chio_policy_hook(store)?.with_swarm_witness_keys(trusted_witness_keys());
    kernel.set_runtime_admission_hook(Arc::new(hook));
    let wrong =
        json!({"route":{"bridge":"mcp", "protocolTarget":"mcp://other", "selectedRoute":"other"}});
    let denied = kernel
        .evaluate_tool_call_with_metadata(&request, Some(wrong))
        .await?;
    assert!(denied.verdict != Verdict::Allow);
    assert_eq!(calls.load(Ordering::SeqCst), 0);
    println!("wrong route: DENY; handler calls 0");
    let allowed = kernel
        .evaluate_tool_call_with_metadata(&request, Some(metadata.clone()))
        .await?;
    assert!(allowed.verdict == Verdict::Allow, "{allowed:#?}");
    assert_eq!(calls.load(Ordering::SeqCst), 1);
    println!("valid continuation: ALLOW; handler calls 1");
    let replay = kernel
        .evaluate_tool_call_with_metadata(&request, Some(metadata))
        .await?;
    assert!(replay.verdict != Verdict::Allow);
    assert_eq!(calls.load(Ordering::SeqCst), 1);
    assert_eq!(
        replay
            .receipt
            .metadata
            .as_ref()
            .and_then(|m| m.get("chio_runtime"))
            .and_then(|m| m.get("failure_code"))
            .and_then(Value::as_str),
        Some("chio_swarm_continuation_replay")
    );
    for (name, response) in [
        ("wrong-route", denied),
        ("allowed", allowed),
        ("replay", replay),
    ] {
        assert!(response.receipt.verify_signature()?);
        std::fs::write(
            format!("{name}-receipt.json"),
            serde_json::to_vec_pretty(&response.receipt)?,
        )?;
    }
    println!("same continuation: DENY; handler calls 1; three receipt signatures verified");
    Ok(())
}

Open src/bin/runtime-host.rs

admission_support.rs · allowing_chio_policy_hookrust
pub fn allowing_chio_policy_hook<S>(
    store: S,
) -> Result<ChioRuntimeAdmissionHook<S>, Box<dyn std::error::Error>> {
    let mut profile = profile();
    profile.schema = "chio.runtime.admission-profile.v1".to_string();
    let (signed_trust, trusted, advisory, signed_policy, signed_weights) =
        signed_policy_inputs(0.10)?;
    Ok(ChioRuntimeAdmissionHook::new(profile, store)
        .with_runtime_trust_input(signed_trust, trusted)
        .with_pheromone_query_report(advisory)
        .with_runtime_pheromone_policy(signed_policy, signed_weights))
}

Open src/admission_support.rs

admission_support.rs · swarm_runtime_contextrust
pub fn swarm_runtime_context(
    bundle: &SwarmAuthorityBundle,
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
    Ok(serde_json::json!({
        "taskGraph": {
            "id": &bundle.task_graph.graph_id,
            "sha256": canonical_hash(&bundle.task_graph)?
        },
        "continuationToken": {
            "id": &bundle.continuation_tokens[0].token_id,
            "sha256": canonical_hash(&bundle.continuation_tokens[0])?
        },
        "routePlanReceipt": {
            "id": &bundle.route_plan_receipts[0].route_plan_id,
            "sha256": canonical_hash(&bundle.route_plan_receipts[0])?
        },
        "delegationWitness": {
            "id": &bundle.witness_chains[0].chain_id,
            "sha256": canonical_hash(&bundle.witness_chains[0])?
        },
        "joinReceipt": {
            "id": &bundle.join_receipts[0].join_id,
            "sha256": canonical_hash(&bundle.join_receipts[0])?
        },
        "revocationEpoch": {
            "id": &bundle.revocation_epoch.epoch_id,
            "sha256": canonical_hash(&bundle.revocation_epoch)?
        },
        "budgetPool": {
            "id": &bundle.budget_pool.pool_id,
            "sha256": canonical_hash(&bundle.budget_pool)?
        }
    }))
}

Open src/admission_support.rs

Captured result and refusal

Captured executable outputtext
wrong route: DENY; handler calls 0
valid continuation: ALLOW; handler calls 1
same continuation: DENY; handler calls 1; three receipt signatures verified

The wrong route and replay leave the handler count unchanged. All three returned receipt signatures are verified and saved. This is one local kernel with process-local admission state; a persistent distributed host needs durable consumption and its own evidence acquisition.

Exit 0 indicates a successful example run. Expected refusals shown in the output are checked by the program; invalid command inputs exit nonzero. Keys, digests, timestamps and run-directory names vary.

The prepared fixture verifies with eight claims, and a revoked task refuses it

The fixture bundle exercises the whole order of checks: a four-task graph, three witness chains, three route plans, one join, one pool, and one epoch. Generate it with its eleven negative variants. The generator also writes trust.toml, the fixture’s signing keys marked as a fixture profile.

swarm-authority · setuptranscript
$ chio proof fixture generate recursive-runtime-swarm --with-negatives --out ./swarm
generated  recursive-runtime-swarm  ./swarm
trust      ./swarm/trust.toml  (fixture profile, 4 roles)
negatives  11 under ./swarm/negatives
exit 0

chio proof verify prints the verdict and every claim it established. Add --json for the full report.

swarm-authority · validtranscript
$ chio proof verify ./swarm/proof-room-bundle --trust ./swarm/trust.toml
VERIFIED  passport-swarm-valid  8 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
trust     ./swarm/trust.toml (fixture keys)
exit 0

You should see eight claims, one per artifact family. The full report also carries a 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.

The revoked-task negative that chio proof fixture generate recursive-runtime-swarm --with-negatives writes to ./swarm/negatives/revoked-task/ is signed and internally consistent, but its epoch carries "revokedTaskIds":["task-child-a"]. It targets claim.swarm.revocation_epoch_bound: the coordinator revoked one helper after delegating to it, and every kernel that reads the epoch must now refuse that helper’s work.

swarm-authority · revoked-tasktranscript
$ chio proof verify ./swarm/negatives/revoked-task --trust ./swarm/trust.toml
REFUSED   revoked-task  swarm task is revoked: task-child-a
trust     ./swarm/trust.toml (fixture keys)
exit 1

You should see the refusal name the task, with exit status 1 and no report. The seven claims that would have verified are absent too, because the verifier returns on the first failing check. The worked example below builds and verifies the same kind of bundle from Rust.

Worked Rust example: Build and verify an authority bundle

Build and verify an authority bundle

Construct a signed graph and every referenced artifact, then choose the verification key and evaluation time independently. The program writes bundle.json for inspection or a second verifier.

Use the Rust example setup, or download the complete source project. The files below belong to that project; shared constructors and configuration are included in the download.

Run it

bash
cargo run --locked --bin authority

Implementation

authority.rsrust
use chio_core_types::crypto::PublicKey;
use chio_swarm_authority::{verify_swarm_authority_bundle, SwarmAuthorityBundle};
use swarm_authority_examples::{build_bundle, trusted_witness_keys};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<_> = std::env::args().collect();
    if args.get(1).map(String::as_str) == Some("verify") {
        if args.len() != 5 {
            return Err("usage: authority verify bundle.json trusted-key-hex now-unix-ms".into());
        }
        let mut bundle: SwarmAuthorityBundle = serde_json::from_slice(&std::fs::read(&args[2])?)?;
        // The verifier chooses time and trust; neither comes from the submitted bundle.
        bundle.now_unix_ms = args[4].parse()?;
        let trusted = vec![PublicKey::from_hex(&args[3])?];
        let report = verify_swarm_authority_bundle(&bundle, &trusted)?;
        println!("{}", serde_json::to_string_pretty(&report)?);
        return Ok(());
    }
    let bundle = build_bundle()?;
    let trusted = trusted_witness_keys();
    let report = verify_swarm_authority_bundle(&bundle, &trusted)?;
    assert_eq!(
        (
            report.task_count,
            report.continuation_count,
            report.join_count
        ),
        (3, 2, 1)
    );
    std::fs::write("bundle.json", serde_json::to_vec_pretty(&bundle)?)?;
    println!(
        "verified: {} tasks, {} continuations, {} join",
        report.task_count, report.continuation_count, report.join_count
    );
    println!("trusted key: {}", trusted[0].to_hex());
    println!("evaluation time: {}", bundle.now_unix_ms);
    let error =
        verify_swarm_authority_bundle(&bundle, &[]).expect_err("an unpinned signer must refuse");
    println!("untrusted signer: {error}");
    Ok(())
}

Open src/bin/authority.rs

lib.rs · build_bundlerust
pub fn build_bundle() -> Result<SwarmAuthorityBundle, Box<dyn Error>> {
    let parent_scope = scope_for("analysis", "record_result", 3);
    let child_scope = scope_for("analysis", "record_result", 1);
    let parent_scope_hash = scope_hash(&parent_scope)?;
    let child_scope_hash = scope_hash(&child_scope)?;
    let witness = compute_attenuation_witness(&parent_scope, &child_scope)?;

    let mut task_graph = SwarmTaskGraph {
        schema: CHIO_SWARM_TASK_GRAPH_SCHEMA.to_string(),
        graph_id: "analysis-graph".to_string(),
        root_transaction_ref: "passport-analysis".to_string(),
        planner_subject: "did:chio:planner".to_string(),
        issuer: witness_issuer(),
        signature: String::new(),
        created_at_unix_ms: now_ms() - 1_000,
        expires_at_unix_ms: now_ms() + 60_000,
        max_depth: 2,
        max_fanout: 2,
        multi_hop_witness_chains: false,
        nodes: vec![
            SwarmGraphNode {
                task_id: "task-root".to_string(),
                parent_task_id: None,
                route_plan_ref: None,
                continuation_token_ref: None,
                budget_allocation_ref: None,
                scope_hash: parent_scope_hash.clone(),
                depth: 0,
            },
            SwarmGraphNode {
                task_id: "task-child-a".to_string(),
                parent_task_id: Some("task-root".to_string()),
                route_plan_ref: Some("route-child-a".to_string()),
                continuation_token_ref: Some("continuation-child-a".to_string()),
                budget_allocation_ref: Some("budget-child-a".to_string()),
                scope_hash: child_scope_hash.clone(),
                depth: 1,
            },
            SwarmGraphNode {
                task_id: "task-child-b".to_string(),
                parent_task_id: Some("task-root".to_string()),
                route_plan_ref: Some("route-child-b".to_string()),
                continuation_token_ref: Some("continuation-child-b".to_string()),
                budget_allocation_ref: Some("budget-child-b".to_string()),
                scope_hash: child_scope_hash.clone(),
                depth: 1,
            },
        ],
        edges: vec![
            SwarmGraphEdge {
                from_task_id: "task-root".to_string(),
                to_task_id: "task-child-a".to_string(),
                edge_type: "delegates".to_string(),
            },
            SwarmGraphEdge {
                from_task_id: "task-root".to_string(),
                to_task_id: "task-child-b".to_string(),
                edge_type: "delegates".to_string(),
            },
        ],
        joins: vec![SwarmGraphJoin {
            join_id: "join-child-results".to_string(),
            parent_task_ids: vec!["task-child-a".to_string(), "task-child-b".to_string()],
            next_task_id: "task-root".to_string(),
        }],
        budget_pool_ref: "budget-pool-analysis".to_string(),
        revocation_epoch_ref: "revocation-epoch-analysis".to_string(),
        route_plan_refs: vec!["route-child-a".to_string(), "route-child-b".to_string()],
    };
    sign_task_graph(&mut task_graph)?;
    let graph_sha256 = canonical_hash(&task_graph)?;
    let empty_revoked_subjects = Vec::<String>::new();
    let empty_revoked_task_ids = Vec::<String>::new();
    let revocation_epoch_root_hash =
        revocation_epoch_root_hash(&empty_revoked_subjects, &empty_revoked_task_ids)?;

    let witness_chains = vec![
        witness_chain(
            "witness-child-a",
            "task-root",
            "task-child-a",
            &parent_scope_hash,
            &child_scope_hash,
            witness.clone(),
        )?,
        witness_chain(
            "witness-child-b",
            "task-root",
            "task-child-b",
            &parent_scope_hash,
            &child_scope_hash,
            witness,
        )?,
    ];
    let continuation_tokens = vec![
        continuation_token(
            "continuation-child-a",
            "task-child-a",
            "route-child-a",
            &graph_sha256,
            &revocation_epoch_root_hash,
            Some(&witness_chains[0]),
        )?,
        continuation_token(
            "continuation-child-b",
            "task-child-b",
            "route-child-b",
            &graph_sha256,
            &revocation_epoch_root_hash,
            Some(&witness_chains[1]),
        )?,
    ];
    task_graph.nodes[1].continuation_token_ref = Some(continuation_tokens[0].token_id.clone());
    task_graph.nodes[2].continuation_token_ref = Some(continuation_tokens[1].token_id.clone());

    let join_request = SwarmJoinReceiptMintRequest {
        join_id: "join-child-results".to_string(),
        graph_id: "analysis-graph".to_string(),
        chain_id: "analysis-chain".to_string(),
        dag_ordinal: 2,
        hlc_unix_ms: now_ms() - 500,
        parent_task_receipts: vec![
            SwarmJoinParentReceipt {
                task_id: "task-child-a".to_string(),
                receipt_id: "receipt-child-a".to_string(),
            },
            SwarmJoinParentReceipt {
                task_id: "task-child-b".to_string(),
                receipt_id: "receipt-child-b".to_string(),
            },
        ],
        expected_parent_receipt_ids: vec![
            "receipt-child-a".to_string(),
            "receipt-child-b".to_string(),
        ],
        actual_parent_receipt_ids: vec![
            "receipt-child-a".to_string(),
            "receipt-child-b".to_string(),
        ],
        join_predicate: "all_success".to_string(),
        result_digest: sha256_hex(b"joined-child-results"),
        next_task_id: "task-root".to_string(),
    };
    let join_receipts = vec![mint_swarm_join_receipt(join_request, &witness_keypair())?];

    let mut bundle = SwarmAuthorityBundle {
        task_graph,
        continuation_tokens,
        witness_chains,
        join_receipts,
        route_plan_receipts: vec![
            route_plan_receipt("route-child-a", "task-child-a", "mcp", "mcp://provider-a")?,
            route_plan_receipt("route-child-b", "task-child-b", "a2a", "a2a://provider-b")?,
        ],
        budget_pool: SwarmBudgetPool {
            schema: CHIO_SWARM_BUDGET_POOL_SCHEMA.to_string(),
            pool_id: "budget-pool-analysis".to_string(),
            graph_id: "analysis-graph".to_string(),
            currency: "USD".to_string(),
            total_units: 10_000,
            allocations: vec![
                SwarmBudgetAllocation {
                    allocation_id: "budget-child-a".to_string(),
                    task_id: "task-child-a".to_string(),
                    dimension_id: "usd_minor".to_string(),
                    state: SwarmBudgetAllocationState::Active,
                    max_units: 2_500,
                    reserved_units: 0,
                    active_units: 2_500,
                    consumed_units: 0,
                    released_units: 0,
                    reversed_units: 0,
                },
                SwarmBudgetAllocation {
                    allocation_id: "budget-child-b".to_string(),
                    task_id: "task-child-b".to_string(),
                    dimension_id: "usd_minor".to_string(),
                    state: SwarmBudgetAllocationState::Active,
                    max_units: 2_500,
                    reserved_units: 0,
                    active_units: 2_500,
                    consumed_units: 0,
                    released_units: 0,
                    reversed_units: 0,
                },
            ],
        },
        revocation_epoch: SwarmRevocationEpoch {
            schema: CHIO_SWARM_REVOCATION_EPOCH_SCHEMA.to_string(),
            epoch_id: "revocation-epoch-analysis".to_string(),
            root_hash: revocation_epoch_root_hash,
            issued_at_unix_ms: now_ms() - 1_000,
            valid_until_unix_ms: now_ms() + 60_000,
            revoked_subjects: empty_revoked_subjects,
            revoked_task_ids: empty_revoked_task_ids,
            issuer: witness_issuer(),
            signature: String::new(),
        },
        terminal_receipts: vec![terminal_graph_receipt()?],
        now_unix_ms: now_ms(),
    };
    sign_revocation_epoch(&mut bundle.revocation_epoch)?;
    Ok(bundle)
}

Open src/lib.rs

Captured result and refusal

Captured executable outputtext
verified: 3 tasks, 2 continuations, 1 join
trusted key: e5a17d66cccd612fc7cd09d2db256b67fccf9ac0082fffef16eff9e6cdb0b930
evaluation time: 1789230049403
untrusted signer: trusted swarm witness keys missing: CHIO_SWARM_TRUSTED_WITNESS_KEYS must pin trusted swarm witness keys

This verifies signed declarations. It does not dispatch the graph's tasks. The configured kernel example below covers admission before a tool handler.

Exit 0 indicates a successful example run. Expected refusals shown in the output are checked by the program; invalid command inputs exit nonzero. Keys, digests, timestamps and run-directory names vary.

A bundle that fails any check is refused before a report exists

An empty trusted-key slice refuses the bundle on the first line, before any artifact validation or structural check. One slice pins the graph, the routes, the joins, the epoch, the terminal receipts, the continuations, and every witness hop alike; the caller decides which keys it holds.

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(())
}

A graph with one task and no edges is structurally valid and still refused. Four of the eight claims are pushed only when their collection is non-empty, so this check keeps a report from reading verified without signed delegation evidence. Both proof paths also compare verified_claims against the required_claims in the verifier policy shipped with the bundle.

SwarmAuthorityHopReport flags are a per-hop rendering of the one bundle-wide verdict, written only after the whole bundle verified. On any failure there is no report to read. The Denial Codes page verifies all 11 negative fixtures in one command.

Verification establishes consistency with the supplied keys and time. Join and terminal receipts are signed declarations: the verifier checks their references, parent sets, and accounting, and it never observes the child tasks running or reads parent execution receipt bodies. What a receipt proves about an external effect follows the mediation boundary.

Bundle fields and verifier rules

Supply all nine fields, with matching graph references and an explicit evaluation time. Unknown JSON fields are rejected, including inside nested witnesses.

task_graph

objectAt least one task

The signed plan: one root, tasks with depth and scope hash, delegation edges, joins, and the max_depth and max_fanout ceilings.

PageTask graphs

continuation_tokens

arrayRequired

Every parented task references a token. Additional valid tokens for a child are permitted.

PageContinuation Tokens

witness_chains

arrayRequired

One per graph edge, each a sequence of hops carrying a scope-subset proof.

PageDelegation Witnesses

join_receipts

arrayOne per join

One per declared join: parent set hash, DAG ordinal, and join predicate.

route_plan_receipts

arrayOne per route reference

Each receipt matches a route_plan_refs entry and carries the selected route, candidate-set digest, registry snapshot hash, bridge, protocol target, egress contract, and constraints.

budget_pool

objectRequired

Its id matches budget_pool_ref. Carries the pool total plus per-task allocations across five states.

PageSub-Agent Budgets

revocation_epoch

objectRequired

Its id matches revocation_epoch_ref. Carries revoked subjects and revoked task ids inside a validity window.

terminal_receipts

arrayAt least one

Graph closure: completed tasks, joins, routes, and per-dimension budget rollups. Each receipt closes the whole graph.

now_unix_ms

integerSupplied by the caller

The evaluation instant every freshness check runs against. Runtime admission overwrites it with the request clock.

Issuer keys

Issuers are strings. An issuer prefixed did:chio: is 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 resulting key must appear in trusted_witness_issuer_keys, which makes the DID a pin.

require_trusted_witness_issuer_keys refuses an empty slice before any check; the caller has already deserialized the bundle. Most artifact families repeat the emptiness check ahead of their own pin comparison, and the revocation epoch fails the comparison instead, because no key matches an empty set. The tests are swarm_authority_stage0_rejects_unpinned_witness_issuer and swarm_authority_stage0_rejects_root_only_bundle_without_trusted_witness_keys. The CLI reads the slice from the keys pinned for swarm.witness and stops with trust:role-unpinned when none is pinned. The crate resolves, rotates, and expires no keys and assigns no issuer to an artifact class.

Closed vocabularies and one gate

  • A route-plan receipt’s egress_constraints is non-empty, and every entry is the literal deny-private-network; any other value is unsupported swarm route-plan egress constraint. Its attenuation_decision is the literal accepted.
  • A route-plan receipt’s selected_route and egress_contract_id each start with its own bridge_id followed by :, and protocol_target starts with the same id followed by ://.
  • join_predicate is 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 refused unless the signed graph sets multiHopWitnessChains, a per-graph gate carried inside the signed artifact.
  • The allocation a continuation names is in state active, and the pool’s summed max_units stays within its total_units.

Totality rules

  • Every route_plan_refs entry has a receipt, and every receipt matches a ref. Duplicates are refused on sight.
  • Every graph join has a receipt whose next_task_id equals the join’s and whose expected-parent count equals the join’s parent count.
  • Every edge is 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 names a continuation token whose child_task_id is the naming task. Token ids and nonces are unique within the bundle. Each token binds exactly one of parent_task_id and join_receipt_id.
  • Continuations are the one asymmetric family. validate_continuation_tokens walks tasks to find missing tokens and accepts a second valid token for a child, which only raises continuation_count in the report. The runtime selects the token by naming one token id in the request.
  • The terminal receipt’s completed-task set equals the graph’s entire task set, its join set every join, and its route set every route. A terminal receipt closes the whole graph or fails.

Recomputed digests and rollups

Four digest fields and the terminal budget rollups are recomputed from the bundle’s own contents and compared.

  • continuation.graph_sha256: canonical JSON of the whole task graph, signature field included.
  • continuation.witness_chain_sha256: canonical JSON of the referenced chain, whose parent and child task ids also match the token.
  • revocation_epoch.root_hash: canonical JSON of the sorted revoked-subject and revoked-task lists.
  • join.parent_set_hash: canonical JSON of the chain id plus the sorted actual parent receipt ids.
  • terminal.budget_rollups: per-dimension sums over every allocation in the pool, compared as whole maps. Each rollup’s five state counts sum to its declared total_units, and the sum across dimensions stays within the pool total.

The graph signature body drops the signature field and the token digest keeps it. 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.

Claims and per-hop flags

swarm_authority_stage0_rejects_root_only_bundle_without_signed_swarm_evidence pins the root-only refusal. The required_claims list is set per verifier policy: 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, as a registry checked for liveness.

SwarmAuthorityHopReport carries authority_verified, attenuation_verified, lineage_verified, route_verified, and budget_verified, each written as the literal true while the report is built.

Error type

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.

Tests and negative fixtures

swarm_authority_stage0_rejects_generated_recursive_scope_widening is a 32-case proptest asserting swarm attenuation witness invalid for a widened child scope inside a signed witness hop. The stage-0 suite in crates/kernel/chio-swarm-authority/tests/swarm_authority_stage0.rs 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.

11 negative fixture bundles are replayed on every run of chio proof doctor --scenario proof-package. Each directory fails verification, and the failure matches the expected_failure_code in its paired descriptor: the raw error text contains the code, or the slug derived from the error carries it at a : or whitespace boundary. A reworded rejection reads as a mismatch. The replay lives in package_negative_expected_failure and check_transaction_passport_rejects, with the fixture catalog built into the binary and proof_doctor_accepts_proof_package_evidence.

The 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 the two outside it.

Sources and wire contract

The report schema is spec/schemas/chio-swarm/v1/authority-verifier-report.schema.json, and the verification order is spec/PROTOCOL.md section 6.4.2 in the public Chio repository. The verifier is crates/kernel/chio-swarm-authority/src/verifier.rs and the error type crates/kernel/chio-swarm-authority/src/error.rs.

Next: read the plan the bundle indexes