Chio/Docs

Bilateral Receipts

When an agent in Org A invokes a tool hosted by Org B, both kernels sign the same receipt. The artifact is symmetric: either side can verify the chain on its own without coordinating with the other at runtime. The shipped surface is DualSignedReceipt in chio-federation::bilateral, invoked through a kernel post-sign hook that runs after the tool-host receipt has been built and signed locally.

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 actual 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

The wire-level surface is small. Two schemas are involved:

SchemaPurpose
chio.federation-bilateral-cosigning.v1The body both kernels sign. Constant: BILATERAL_COSIGNING_SCHEMA.
chio.federation-dual-signed-receipt.v1The artifact persisted after both signatures land. 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 artifact 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, double-checks both signatures, and persists the artifact.
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 the federation drift rather than silently shipping 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 artifact 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 in chio-store-sqlite:

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.

chio-store-sqlite/migrations/federated_lineage_bridges.sql
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.

chio-store-sqlite/migrations/federated_evidence_shares.sql
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.

chio-store-sqlite/migrations/federated_share_tool_receipts.sql
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 hot path, the federation surface 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 surfaces; it does not silently widen runtime authority.

Visibility, not admission

Imported shares feed comparison surfaces 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 surface. 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 an artifact 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);

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. Every step is a signed artifact 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.