Chio/Docs

EconomyFederation & Congregates

Bilateral Receipts

For a cross-organization call, the tool-host and origin kernels sign one receipt body for independent verification.

Origin Kernel and Tool-Host Kernel

Cross-org calls have two named roles. The origin kernel is the kernel where the calling agent lives. The tool-host kernel is the kernel that owns the tool and dispatches the invocation. In the federation literature these are commonly tagged Org A and Org B, with Org A holding the agent and Org B holding the tool.

  • Origin (Org A): holds the agent, mints the original capability or presents the delegated child via the federation issuance endpoint, signs the dual receipt as the second co-signer.
  • Tool-host (Org B): receives the call, applies its bilateral policy and guard pipeline, dispatches the tool, builds the receipt, and signs it first.

Each receipt carries the kernel signing public key that produced it through the standard ChioReceipt.signature and kernel_key fields. The bilateral envelope adds a detached signature from the remote kernel without mutating the receipt body, so a verifier that only understands the base receipt can still check the local chain in isolation.


Co-Signing Body and Envelope

Two schemas are involved:

SchemaPurpose
chio.federation-bilateral-cosigning.v1The body both kernels sign. Constant: BILATERAL_COSIGNING_SCHEMA.
chio.federation-dual-signed-receipt.v1Record persisted after both signatures are present. Constant: BILATERAL_DUAL_RECEIPT_SCHEMA.

The signed body is CoSigningBody:

chio-federation/src/bilateral.rs
pub struct CoSigningBody {
    pub schema: String,
    /// Canonical JSON encoding of the underlying ChioReceipt, as a UTF-8
    /// string. Both kernels sign exactly the bytes they saw.
    pub receipt_canonical_json: String,
    pub org_a_kernel_id: String,
    pub org_b_kernel_id: String,
}

The receipt is embedded as canonical JSON in a string, not a nested object. This keeps signing stable even if the receipt schema later adds fields with skip_serializing_if: each kernel signs the exact bytes it saw, no re-encoding step between sign and verify.

The persisted record is DualSignedReceipt:

chio-federation/src/bilateral.rs
pub struct DualSignedReceipt {
    pub schema: String,
    pub body: ChioReceipt,
    pub org_a_kernel_id: String,
    pub org_b_kernel_id: String,
    pub org_a_signature: Signature,
    pub org_b_signature: Signature,
}

impl DualSignedReceipt {
    /// Verify both detached signatures against the provided pinned peer
    /// public keys. Returns Ok only when BOTH signatures validate.
    pub fn verify(
        &self,
        org_a_public_key: &PublicKey,
        org_b_public_key: &PublicKey,
    ) -> Result<(), BilateralCoSigningError>;
}

Either half alone is not sufficient

A federation-aware verifier MUST refuse a dual-signed receipt when only one detached signature validates. The bilateral contract is that both kernels signed off; either failure invalidates the cross-org assertion. The base ChioReceipt embedded in body can still be verified in isolation by a non-federation verifier, but that is a weaker assertion than the dual signature.

Protocol Flow

The tool-host kernel drives the protocol. After the local receipt has been built and signed, a apply_federation_cosign post-sign hook runs whenever the request carries a federated_origin_kernel_id (Phase 20.3 in ChioKernel).

  1. Tool-host kernel builds the canonical CoSigningBody from the receipt and the two kernel ids.
  2. Tool-host kernel signs the canonical bytes with its own keypair, producing org_b_signature.
  3. Tool-host kernel calls the installed BilateralCoSigningProtocol implementation with a CoSigningRequest.
  4. Origin kernel verifies the request: schema, body, kernel ids, and Org B's signature. On any failure it returns a typed error and refuses to co-sign.
  5. Origin kernel signs the same canonical bytes with its own keypair and returns CoSigningResponse carrying org_a_signature.
  6. Tool-host kernel verifies the origin signature against its pinned peer key, assembles the DualSignedReceipt, checks both signatures again and persists the record.
tool-host-cosign.rs
// chio_federation::co_sign_with_origin drives the full protocol.
// Tool-host (Org B) side:
let dual: DualSignedReceipt = co_sign_with_origin(
    "org-a-kernel",            // origin kernel id
    &org_a_public_key,         // pinned peer key from FederationPeer
    "org-b-kernel",            // local (tool-host) kernel id
    &tool_host_keypair,
    receipt,                   // the locally-signed ChioReceipt
    cosigner.as_ref(),         // BilateralCoSigningProtocol impl
)?;

The kernel post-sign hook fails closed: when the request carries a federated origin id but the cosigner is missing, or the peer is unpinned, or the cosigner returns an error, the kernel surfaces a KernelError::Internal so operators see federation drift instead of receiving a receipt without the remote signature.

rendering…
Tool-host kernel signs the receipt locally, asks the origin kernel to co-sign the canonical body, verifies the returned signature against the pinned peer key, and persists a DualSignedReceipt that both sides can verify independently.

Receipt Routing on Cross-Org Calls

The kernel state for the federation hook is set up via three builder methods on ChioKernel:

MethodPurpose
with_federation_peers(peers)Pin the trusted peer set after handshake.
set_federation_cosigner(cosigner)Install the implementation that contacts the origin kernel for a co-signature.
set_federation_local_kernel_id(id)Advertise this kernel's stable name. Defaults to the hex of the signing public key.

Production deployments wire the cosigner to an mTLS-attested RPC client that talks to the origin kernel; in-process tests can use InProcessCoSigner which holds the origin kernel's signing keypair directly.

After the hook completes, the dual-signed receipt is reachable from the kernel by receipt id:

lookup-dual.rs
if let Some(dual) = kernel.dual_signed_receipt(&receipt.id) {
    dual.verify(&org_a_public_key, &org_b_public_key)?;
    // both kernels signed off
}

Federated Lineage Bridges

Cross-org delegation produces capability lineage that crosses a federation boundary. The receipt store captures this with three related tables, created as inline CREATE TABLE statements in chio-store-sqlite/src/receipt_store/bootstrap/open.rs (there is no migrations/ directory):

federated_lineage_bridges

Maps a local capability id to its parent in another org and the share id that imported the parent. The table joins local capability_lineage rows to imported evidence, so an auditor walking child-to-parent traversal lands on a recognizable record even when the parent was issued at a different kernel.

crates/platform/chio-store-sqlite/src/receipt_store/bootstrap/open.rs
CREATE TABLE federated_lineage_bridges (
    local_capability_id TEXT PRIMARY KEY
        REFERENCES capability_lineage(capability_id) ON DELETE CASCADE,
    parent_capability_id TEXT NOT NULL,
    share_id TEXT REFERENCES federated_evidence_shares(share_id)
);

federated_evidence_shares

One row per signed evidence package imported from a partner. The manifest hash is the content identifier; issuer and partner name the two operators; signer public key identifies the partner's signing key at import time.

crates/platform/chio-store-sqlite/src/receipt_store/bootstrap/open.rs
CREATE TABLE federated_evidence_shares (
    share_id TEXT PRIMARY KEY,
    manifest_hash TEXT NOT NULL,
    imported_at INTEGER NOT NULL,
    exported_at INTEGER NOT NULL,
    issuer TEXT NOT NULL,
    partner TEXT NOT NULL,
    signer_public_key TEXT NOT NULL,
    require_proofs INTEGER NOT NULL DEFAULT 0,
    query_json TEXT NOT NULL
);

federated_share_tool_receipts

Receipt rows attached to an imported share. Each row carries the receipt id, capability id, subject and issuer keys, and the raw JSON of the receipt for verification. The composite primary key (share_id, seq) orders the receipts inside one share; a unique constraint on (share_id, receipt_id) prevents duplicate import.

crates/platform/chio-store-sqlite/src/receipt_store/bootstrap/open.rs
CREATE TABLE federated_share_tool_receipts (
    share_id TEXT NOT NULL
        REFERENCES federated_evidence_shares(share_id) ON DELETE CASCADE,
    seq INTEGER NOT NULL,
    receipt_id TEXT NOT NULL,
    timestamp INTEGER NOT NULL,
    capability_id TEXT NOT NULL,
    subject_key TEXT,
    issuer_key TEXT,
    raw_json TEXT NOT NULL,
    PRIMARY KEY (share_id, seq),
    UNIQUE (share_id, receipt_id)
);

A companion table federated_share_capability_lineage records imported capability lineage, including delegation depth and parent capability id, so a partner's chain can be walked offline against the share manifest.


Federated Evidence Shares

Beyond co-signed receipts on the request path, the federation API supports asynchronous evidence sharing. A partner can package a bundle of receipts and lineage rows into a signed share, send it across the boundary, and let the receiving org import it under its bilateral FederationImportControl:

  • Explicit local activation required by default. Imported shares stay visibility-only until an operator activates them.
  • Manual review required by default before activation.
  • Stale inputs rejected when the share's freshness exceeds the partner's max_evidence_age_secs.
  • Ambient runtime admission prohibited: imported evidence influences visibility and reputation displays; it does not silently widen runtime authority.

Visibility, not admission

Imported shares feed comparison views and reputation projections. They never substitute for a locally issued capability. Even after activation, runtime authority requires a capability that satisfies the local guard pipeline.

Revocation Feeds

Bilateral revocation is the most time-sensitive part of the federation mechanism. When Org A revokes a capability, Org B must stop honoring it on the next enforcement decision. Each side publishes a signed transparency feed of revocations; the counterparty subscribes through its trust control plane and merges new entries into its local revocation store.

  • The bilateral policy declares the partner's revocation_feed URL and a max_evidence_age_secs ceiling.
  • Subscriptions poll on a small interval. Merges are idempotent; replay is safe.
  • Every cross-org tool call re-checks revocation against the local store. Once the feed has landed, enforcement is instant on the next invocation.
  • A feed observed past the staleness ceiling fails closed: the kernel refuses to honor capabilities issued by that partner until the feed is re-synced.

Independent Verification

An auditor can verify a dual-signed receipt without contacting either kernel at runtime. They need:

  1. The persisted DualSignedReceipt.
  2. The Org A kernel signing public key (Ed25519).
  3. The Org B kernel signing public key (Ed25519).

Verification calls DualSignedReceipt::verify, which rebuilds the canonical CoSigningBody and checks both signatures against the provided keys. The same function runs at the tool-host kernel as a final check before persistence, so a record that was persisted in federation_dual_receipts always passes third-party verification given correct keys.

standalone-verify.rs
// Stand-alone verifier with no live kernel access.
let dual: DualSignedReceipt = read_persisted_artifact()?;
dual.verify(&org_a_public_key, &org_b_public_key)?;

// The embedded ChioReceipt is also verifiable in isolation:
let body_signature_ok = dual.body.verify_signature(&dual.body.kernel_key)?;
assert!(body_signature_ok);

DSSE Attestation Layer

DualSignedReceipt::verify checks two raw kernel keys the auditor already holds. For portable, third-party verification, Chio wraps that verification operation in a normative in-toto-style DSSE attestation predicate, chio.bilateral-cosign-invocation.v1 (spec CHIO_BILATERAL_COSIGN_INVOCATION.md, v1, 2026-05-04), implemented in chio-federation's bilateral_dsse.rs and bilateral_verifier.rs. The spec names bilateral.rs, the CoSigningBody and DualSignedReceipt pair above, as the existing Chio verification operation it wraps.

The DSSE envelope carries a named, ordered pair of signatures. Each keyid is the SHA-256 of the signing kernel's passport public key, and the envelope is invalid unless both declared keys appear and both verify. That is stricter than DSSE's permissive (t,n) threshold: it asserts a joint commit by two named organizational identities, not two signers who happen to sign the same statement. The Statement subject digest is the SHA-256 of the canonical-JSON (RFC 8785) ChioReceipt body, the same bytes the co-signing operation covers.

Trust inputs live outside the proof package. A verifier resolves peer pins, ladder references, and action-class policy from separate signed records ( chio.federation.verifier-trust-bundle.v1 plus chio.federation.verification-context.v1 ) and MUST reject a package whose embedded hints disagree with the trust bundle. The proof package itself embeds no peer pins, ladder refs, or action-class policy.

signature-slice is not conformance evidence

An older chio.bilateral-signature-slice.v1 profile still ships for local receipt binding only, but strict Chio verification rejects it as conformance evidence. Cross-org proofs emit chio.bilateral-cosign-invocation.v1; until the in-toto WG accepts the canonical URI https://in-toto.io/attestation/bilateral-cosign-invocation/v1, implementations emit the chio-namespaced fallback so verifiers do not collide with an unaccepted reservation.

Worked Example: Tracing a Dual Receipt

Suppose an agent at Org A invokes billing.read on Org B's billing tool server. The trace looks like this:

1. Tool-Host Receives the Call

Org B's kernel receives the ToolCallRequest with federated_origin_kernel_id = Some("org-a-kernel"). The kernel applies the bilateral policy stored at trust control, verifies the inbound capability against its trusted_issuers set, and runs the guard pipeline.

2. Local Receipt

On allow, Org B's kernel invokes the tool, builds a ChioReceipt, and signs it with the local kernel keypair. The receipt looks identical to a non-federation receipt at this point: it carries the standard signature and kernel_key fields.

3. Co-Signing Hook

The post-sign hook fires. It builds the canonical CoSigningBody, signs it as Org B, sends a CoSigningRequest to the origin via the installed cosigner, verifies the returned org_a_signature, and persists the DualSignedReceipt.

4. Audit Walk

Six months later an auditor pulls the receipt by id:

audit-walk.sh
$ chio receipts get \
    --receipt-id 01HV9C0K... \
    --include-dual

# Output includes:
#   body.kernel_key      : ed25519:7f3b9c... (Org B)
#   org_a_kernel_id      : org-a-kernel
#   org_b_kernel_id      : org-b-kernel
#   org_a_signature      : ed25519:...
#   org_b_signature      : ed25519:...
#   federated_lineage    : parent_capability_id=cap-anchor-...
#                          share_id=imp-2026-04-28-...

The auditor verifies both signatures with the published Org A and Org B kernel keys. They walk lineage through federated_lineage_bridges to the parent capability id at Org A, and from there into Org A's own capability lineage if available. Each step uses a signed record verified offline.


Failure Modes

  • Cosigner missing: a request with a federated origin lands on a kernel that has no cosigner installed. The post-sign hook returns KernelError::Internal and the receipt is rejected.
  • Peer unpinned or stale: the federation peer for the origin kernel id is not present or has aged past rotation_due. The hook fails closed and the operator must re-handshake.
  • Origin signature invalid: the response from the cosigner does not verify against the pinned peer key. co_sign_with_origin returns OrgASignatureInvalid.
  • Tool-host signature invalid: the origin kernel rejects the request with OrgBSignatureInvalid. Operators check tool-host key drift first.
  • Receipt body mismatch: the body in the request differs from the body the cosigner observed. Surfaced as ReceiptMismatch.