Chio/Docs

PlatformContracts

Mandates & Capabilities

Capability tokens authorize tool invocations through signed scopes, time bounds, revocation checks, and optional DPoP binding.

Source

Verified against the crates/core/chio-core-types/src/capability/ module (a directory of submodules: token.rs, scope.rs, attenuation.rs, caveat.rs, and others) and crates/kernel/chio-kernel-core/src/capability_verify.rs. Field names, enum variants, and defaults are taken from those files. If this page disagrees with source, source wins.

CapabilityToken

The on-the-wire token shape:

crates/core/chio-core-types/src/capability/token.rs
pub struct CapabilityToken {
    pub schema: String,                      // Wire schema ID; defaults to "chio.capability.v1"
    pub id: String,                          // UUIDv7 recommended; used for revocation
    pub issuer: PublicKey,                   // Capability authority or delegating agent
    pub subject: PublicKey,                  // Agent this token is bound to (DPoP sender constraint)
    pub scope: ChioScope,                    // What the token authorizes
    pub issued_at: u64,                      // Unix seconds
    pub expires_at: u64,                     // Unix seconds
    pub delegation_chain: Vec<DelegationLink>,
    pub algorithm: Option<SigningAlgorithm>, // Absent means Ed25519 (legacy default)
    pub caveats: Vec<Caveat>,                // Typed caveats; empty tokens omit this on the wire
    pub scope_attenuations: Option<Vec<Attenuation>>, // Attenuation request on attenuated tokens
    pub attenuation_proof: Option<AttenuationProof>,  // Wire witness for child-scope attenuation
    pub budget_share_bps: Option<u16>,       // Sub-agent budget share, basis points (<= 10000)
    pub signature: Signature,                // Over canonical JSON of the signing body
}

The schema field carries the constant CHIO_CAPABILITY_SCHEMA ("chio.capability.v1"); tokens that omit it on the wire default to that value. The caveats, scope_attenuations, attenuation_proof, and budget_share_bps fields are the support caveats, scope attenuation, attenuation proofs, and shared budgets. A tool token that uses none of them round-trips byte-identical to a pre-subsystem token because each is skipped from canonical JSON when empty.

Three token properties:

  • Signature covers everything except itself. Newly issued tokens sign the schema-aware CapabilityTokenSigningBody (schema, core body, caveats, scope attenuations, attenuation proof, and budget share). Legacy caveat-free, attenuation-free tokens on the default schema fall back to signing the plain CapabilityTokenBody; the selection is made by permits_plain_body_signature(). Verification tries the schema-aware body first and only then the plain body, so tampering with any field invalidates the signature.
  • Subject binding is enforced via DPoP. The token names the agent it is intended for. The kernel checks DPoP proof against this subject when dpop_required is set on a grant.
  • Time bounds are inclusive of issued_at and exclusive of expires_at. A token is valid when issued_at <= now < expires_at.

ChioScope and Grant Variants

ChioScope is a flat container holding three optional grant lists. Empty lists are skipped from canonical JSON, so a tool-only token round-trips byte-identical to its pre-resource-grant ancestors.

crates/core/chio-core-types/src/capability/scope.rs
pub struct ChioScope {
    pub grants: Vec<ToolGrant>,
    pub resource_grants: Vec<ResourceGrant>,
    pub prompt_grants: Vec<PromptGrant>,
}

ToolGrant

The most heavily used grant. Each ToolGrant authorizes a single tool on a single server, with optional invocation, cost, and constraint attenuation:

crates/core/chio-core-types/src/capability/scope.rs
pub struct ToolGrant {
    pub server_id: String,
    pub tool_name: String,
    pub operations: Vec<Operation>,
    pub constraints: Vec<Constraint>,
    pub max_invocations: Option<u32>,
    pub max_cost_per_invocation: Option<MonetaryAmount>,
    pub max_total_cost: Option<MonetaryAmount>,
    pub dpop_required: Option<bool>,
}

server_id and tool_name support a literal "*" as a parent wildcard during delegation, but a leaf token must name a specific tool.

ResourceGrant

crates/core/chio-core-types/src/capability/scope.rs
pub struct ResourceGrant {
    pub uri_pattern: String,
    pub operations: Vec<Operation>,
}

URI pattern matching is literal-equal, full-match *, or trailing-prefix prefix*. There is no glob engine; if you need richer patterns, narrow at issuance time.

PromptGrant

crates/core/chio-core-types/src/capability/scope.rs
pub struct PromptGrant {
    pub prompt_name: String,
    pub operations: Vec<Operation>,
}

Operations

The Operation enum is shared across all three grant kinds:

crates/core/chio-core-types/src/capability/scope.rs
pub enum Operation {
    Invoke,       // Execute the tool
    ReadResult,   // Read a prior invocation result
    Read,         // Read a resource
    Subscribe,    // Subscribe to resource updates
    Get,          // Retrieve a prompt
    Delegate,     // Delegate this grant to another agent
}

A grant with Delegate in its operations list is the precondition for further delegation. Without it, the holder cannot extend the chain.


Constraint Variants

Constraints narrow tool parameters or impose runtime requirements. They serialize via #[serde(tag = "type", content = "value", rename_all = "snake_case")], so the wire form is {"type": "path_prefix", "value": "/var/log"}. The full enum, taken from source:

VariantPayloadSemantics
PathPrefixStringFile path parameter must start with this prefix
DomainExactStringNetwork domain must match exactly
DomainGlobStringNetwork domain matches a glob
RegexMatchStringParameter must match regex
MaxLengthusizeString parameter length cap
MaxArgsSizeusizeSerialized arg payload byte cap
GovernedIntentRequiredunitRequest must carry a governed transaction intent
RequireApprovalAbove{ threshold_units: u64 }Approval token required above threshold
SellerExactStringCommerce approval bound to this seller
MinimumRuntimeAssuranceRuntimeAssuranceTierFloor on runtime attestation tier
MinimumAutonomyTierGovernedAutonomyTierFloor on bonded autonomy tier
TableAllowlistVec<String>SQL tables this grant may reference
ColumnDenylistVec<String>Forbidden table.column identifiers
MaxRowsReturnedu64Row cap, enforced post-invocation
OperationClassSqlOperationClassread_only / read_write / admin
AudienceAllowlistVec<String>Allowed recipient channels or IDs
ContentReviewTierContentReviewTiernone / basic / strict review demand
MaxTransactionAmountUsdString (decimal)Per-transaction USD cap
RequireDualApprovalboolTwo-party approval required
ModelConstraint{ allowed_model_ids, min_safety_tier }Constrain executing model identity / safety tier
MemoryStoreAllowlistVec<String>Memory stores this grant may write to
MemoryWriteDenyPatternsVec<String>Regex patterns that block writes
Custom(String, String)Extensibility key / value pair

Constraint subset semantics during delegation

A child grant must contain every constraint of its parent. The check is membership equality on the constraint list, not semantic comparison. A child cannot "narrow" a parent PathPrefix("/") by replacing it with PathPrefix("/etc"); the parent constraint must appear unchanged in the child, alongside any further restrictions.

Signing Algorithms

Four algorithms ship today. The enum serializes as a short lowercase identifier ("ed25519", "p256", "p384", "hybrid"). Backward-compatibility is preserved via the optional algorithm envelope field: when absent, consumers MUST treat the algorithm as Ed25519 so legacy tokens deserialize unchanged.

crates/core/chio-core-types/src/crypto.rs
pub enum SigningAlgorithm {
    Ed25519,   // Default, non-FIPS. No feature flag required.
    P256,      // ECDSA P-256 / SHA-256. Requires the `fips` feature.
    P384,      // ECDSA P-384 / SHA-384. Requires the `fips` feature.
    Hybrid,    // Classical signature plus ML-DSA-65. Requires the `pq` feature.
}

Verification dispatches off the self-describing prefix on PublicKey and Signature, not the envelope field. The algorithm field is informational; when present it MUST agree with the signature's prefix, and a mismatch is rejected fail-closed as a downgrade signal. Without the fips feature, P-256 and P-384 verification returns Ok(false).

Hybrid pairs a classical signature with ML-DSA-65 (FIPS 204) post-quantum material and is the algorithm the kernel-wide CryptoFloor::AllowHybrid and CryptoFloor::PqRequired postures require. Hybrid public keys and signatures carry a hybrid: hex prefix, parallel to p256: and p384:. verify_signature_with_floor enforces the floor before the cryptographic check.


Delegation Chains

Delegation lets one agent narrow its own capability and hand the attenuated form to another agent. The chain is recorded inline on the leaf token:

crates/core/chio-core-types/src/capability/attenuation.rs
pub struct DelegationLink {
    pub capability_id: String,    // ID of the ancestor token at this step
    pub delegator: PublicKey,
    pub delegatee: PublicKey,
    pub attenuations: Vec<Attenuation>,  // How the scope was narrowed
    pub timestamp: u64,
    pub scope_hash: Option<ScopeHash>,   // SHA-256 of the canonical scope authorized here
    pub signature: Signature,     // Delegator signs canonical(body)
}

scope_hash is the delegation chain-binding mechanism. It records the SHA-256 of the canonical scope authorized at that hop and ties AttenuationProof.parent_scope_hash on the next hop back to it, closing a parent-scope-inflation soundness hole. It is absent on older links and enforced when the delegation_chain_binding feature is active.

Verification reads the chain root-to-leaf:

  1. The first link's delegator must equal the issuer of the original capability (the root of trust).
  2. Each subsequent link's delegator must equal the previous link's delegatee.
  3. Each link's signature is verified against its delegator key.
  4. The leaf token's subject must equal the final link's delegatee.
  5. The leaf scope must be a subset of every ancestor scope. The subset check uses ChioScope::is_subset_of, which composes per-grant checks over tools, resources, and prompts.
  6. When chain binding is active, each link's scope_hash must match the next hop's attenuation_proof.parent_scope_hash, and the first hop's scope_hash must match the trust root's scope hash. A link that omits scope_hash is rejected.

Verification

The portable, IO-free check lives in chio-kernel-core::capability_verify:

crates/kernel/chio-kernel-core/src/capability_verify.rs
pub fn verify_capability(
    token: &CapabilityToken,
    trusted_issuers: &[PublicKey],
    clock: &dyn Clock,
) -> Result<VerifiedCapability, CapabilityError>

It performs three checks and nothing else:

  • Issuer trust. token.issuer must appear in the supplied trusted-issuer set.
  • Signature. token.verify_signature() re-canonicalizes the body and dispatches verification off the signature's self-describing encoding.
  • Time bounds. classify_time_window(now, issued_at, expires_at) returns one of Valid, NotYetValid, Expired.

Revocation, delegation lineage, scope match against a request, and DPoP subject binding are deliberately out of scope here. They live in chio-kernel because they require IO or transport state. The full kernel orchestrates all of them in ChioKernel::evaluate_tool_call_sync.


Revocation

Revocation is a separate signal layered on top of valid-signature tokens. The kernel queries a RevocationStore:

crates/kernel/chio-kernel/src/revocation_runtime.rs
pub trait RevocationStore: Send + Sync {
    fn is_revoked(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
    fn revoke(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;

    /// Whether this store loses its revocation set on process restart.
    /// Default-provided: the safe, loud assumption is that an unknown
    /// store is ephemeral. Durable and remote stores override to false.
    fn is_ephemeral(&self) -> bool {
        true
    }
}

Two implementations ship: an InMemoryRevocationStore suitable for tests and short-lived processes, and a SQLite-backed store in chio-store-sqlite for production. The lookup happens once per evaluation, before the guard pipeline runs. A revoked token denies with a fixed reason regardless of scope.

The third method, is_ephemeral, is default-provided and matters to anyone implementing a custom store. The default returns true — the safe, loud assumption that an unknown store forgets its revocation set on restart. InMemoryRevocationStore keeps that default; the SQLite-backed store overrides it to false. A kernel built from chio.yaml sets allow_ephemeral_revocation_store = false, so a custom store that has to survive a restart must override is_ephemeral() to return false to be accepted.

Distributed revocation

A NATS-subscribed feed implementation lives downstream of the trait. Operators with multiple kernel replicas should propagate revocations through that channel. The trait keeps the kernel independent of the feed implementation.

DPoP Subject Binding

DPoP (Demonstration of Proof-of-Possession) binds a single tool invocation to the agent named in token.subject. The agent signs a per-request proof; the kernel verifies that signature against the subject key.

crates/kernel/chio-kernel/src/dpop.rs
pub struct DpopProof {
    pub body: DpopProofBody,
    pub signature: Signature,
}

DPoP is opt-in per grant via ToolGrant::dpop_required. The semantics:

  • Some(true): the kernel requires a valid DPoP proof on every invocation.
  • Some(false) or None: DPoP is not enforced.
  • During delegation, a parent that requires DPoP forces every child grant to also require DPoP. A child cannot relax this requirement.

Schema: chio.dpop_proof.v1. The proof is namespaced and replay-protected via a nonce store owned by the kernel; consult crates/kernel/chio-kernel/src/dpop.rs for the verification logic.


Worked Example: Sign and Verify

Build a body, sign it with an Ed25519 keypair, then verify it against a trusted-issuer set.

rust
use chio_core_types::capability::scope::{
    ChioScope, Constraint, Operation, ToolGrant,
};
use chio_core_types::capability::token::{CapabilityToken, CapabilityTokenBody};
use chio_core_types::crypto::Keypair;
use chio_kernel_core::capability_verify::verify_capability;
use chio_kernel_core::clock::FixedClock;

let issuer_kp = Keypair::generate();
let agent_kp = Keypair::generate();

let body = CapabilityTokenBody {
    id: "01HXJ...".into(),
    issuer: issuer_kp.public_key(),
    subject: agent_kp.public_key(),
    scope: ChioScope {
        grants: vec![ToolGrant {
            server_id: "fs".into(),
            tool_name: "read_file".into(),
            operations: vec![Operation::Invoke],
            constraints: vec![Constraint::PathPrefix("/var/log".into())],
            max_invocations: Some(1000),
            max_cost_per_invocation: None,
            max_total_cost: None,
            dpop_required: Some(true),
        }],
        resource_grants: vec![],
        prompt_grants: vec![],
    },
    issued_at: 1_700_000_000,
    expires_at: 1_700_086_400, // +24h
    delegation_chain: vec![],
};

let token = CapabilityToken::sign(body, &issuer_kp).expect("sign");

let trusted = [issuer_kp.public_key()];
// FixedClock is the only concrete Clock chio-kernel-core ships. Production
// callers supply their own Clock impl wrapping std / browser / embedded time.
let clock = FixedClock::new(1_700_000_100); // inside [issued_at, expires_at)
let verified = verify_capability(&token, &trusted, &clock)
    .expect("token verifies");

assert_eq!(verified.id, token.id);

Verification returns a VerifiedCapability with the state required for downstream scope and revocation checks. Adapters that drop the original token after verification retain the captured scope. chio-kernel-core is deliberately clock-source-agnostic: it never calls SystemTime::now(), so every verifier threads time in through a &dyn Clock.


Next Steps