Chio/Docs
LOGIN · JOIN

Treaties

Let independently governed kernels cooperate under one explicit action contract, with each participant’s requirements preserved in the shared decision.

Concept chio@5f63e6e

On this page

Operators can take on consequential work together and each keep its own governance terms. A treaty names the participating kernels, the action classes they share, and the governance ladder each contributes, and every kernel checks each shared action against the combination.

The running example is the Quickstart treaty trt-01aa…4c19 between your kernel and a partner's, covering workspace reads and writes, model calls, and a merge that needs both signatures.

Each kernel folds the ladders, and the stricter requirement wins

For every shared action class, the verifier resolves the class in each participant's ladder and folds the declarations into one set of terms.

Ladder intersection of two participants over three action classesper cellmodeco_signevidence_requiredkernel.buyerkernel.vendorintersection
max⁡k r(⋅)\htmlClass{role-seal}{\max_k\, r(\cdot)}
whisker.pheromone_depositobservation0crdt-commutativeguarded1crdt-commutativeguarded1= crdt-commutativeworkflow.cross_kernel.read_refund_caseguarded1totally-orderedreceipt_backed2totally-orderedreceipt_backed2= totally-orderedworkflow.destructive.vendor_calldestructiveguarded1totally-orderedreceipt_backed2destructivetotally-orderedreceipt_backed2≥ receipt_backed= totally-ordered
ma=max⁡k r(ma,k)κa,k=κa,1,da⇒r(ma)≥r(receipt_backed)\begin{aligned} &\htmlClass{role-seal}{m_a} = \max_k\, r(\htmlClass{role-call}{m_{a,k}}) \\ &\kappa_{a,k} = \kappa_{a,1}, \quad \htmlClass{role-grant}{d_a} \Rightarrow r(\htmlClass{role-seal}{m_a}) \ge r(\text{receipt\_backed}) \end{aligned}
max⁡(1, 2)=2ma=receipt_backed\begin{aligned} &\max(1,\, 2) = 2 \\ &\htmlClass{role-seal}{m_a = \text{receipt\_backed}} \end{aligned}
LadderIntersection3 action classes, each reduced to one combined declaration
Ladder intersection of two participants over three action classesmodeco_signevidence_requiredwhisker.pheromone_depositkernel.buyerobservation0crdt-commutativekernel.vendorguarded1crdt-commutativeintersectionguarded1workflow.cross_kernel.read_refund_casekernel.buyerguarded1totally-orderedkernel.vendorreceipt_backed2totally-orderedintersectionreceipt_backed2workflow.destructive.vendor_calldestructive, declared by kernel.vendorkernel.buyerguarded1totally-orderedkernel.vendorreceipt_backed2totally-orderedintersectionreceipt_backed2≥ receipt_backed

One LadderIntersection over 3 action classes.

One LadderIntersection over 3 action classes.

ma=max⁡k r(ma,k)κa,k=κa,1da⇒r(ma)≥2\begin{aligned} &\htmlClass{role-seal}{m_a} = \max_k\, r(\htmlClass{role-call}{m_{a,k}}) \\ &\kappa_{a,k} = \kappa_{a,1} \\ &\htmlClass{role-grant}{d_a} \Rightarrow r(\htmlClass{role-seal}{m_a}) \ge 2 \end{aligned}
In Rust · treaty.rs:27634 lines
for participant in &treaty_scope.participant_kernel_ids {
    // … find this participant's action class
    let rank = ladder_mode_rank(&action.mode)?;
    if rank > mode_rank {
        mode_rank = rank;
        mode.clone_from(&action.mode);
    }
    destructive |= action.destructive;
    if let Some(existing) = consistency_model.as_ref() {
        if existing != &action.consistency_model {
            return rejected(
                "chio_federation_ladder_consistency_mismatch",
                "governance ladder consistency models do not intersect",
            );
        }
    } else {
        consistency_model = Some(action.consistency_model.clone());
    }
    if co_sign_requirement_rank(&action.co_sign)? > co_sign_requirement_rank(&co_sign)? {
        co_sign.clone_from(&action.co_sign);
    }
    // … merge the co-sign quorum
    for item in &action.evidence_required {
        evidence_required.insert(item.clone());
    }
    // … record participant_modes
}
if destructive && mode_rank < ladder_mode_rank("receipt_backed")? {
    return rejected(
        "chio_federation_ladder_destructive_below_floor",
        "intersected destructive action resolves below receipt backed mode",
    );
}
// … then the destructive crdt-commutative check
Two participants from the treaty tests with illustrative declarations. Modes and co-signing combine by rank maximum and evidence by union; consistency models must be equal, and any rejection stops the whole intersection. Each manifest also enforces its own destructive floor, receipt_backed here. Co-sign quorums are merged but not drawn.Federation and swarms

Mode and co-signing take the highest declared rank, evidence requirements combine by union, and one participant calling an action destructive makes it destructive. Consistency models must be equal, and any rejection stops the whole intersection, so a participant's requirement survives into the shared decision or the treaty refuses the class.

The verifier supplies the scope and manifests from its own trusted inputs and computes the expected intersection itself. The treaty library validates those records; fetching peer documents and deciding whether to trust them stays with the verifier. A scope is a structured record, and its digest binds the exact content used in admission.

An application opens a treaty with one call

From the Computer facade, a treaty names the partner, the actions, and a lifetime. Both kernels sign it.

partners.tspartners.pysrc/main.rs · in main
import { Computer } from "@chio-protocol/sdk";

const project = await Computer.open("quickstart");
const partner = await Computer.connect("partner");
const treaty = await project.treaty({
  partner: partner,
  actions: ["workspace.read", "workspace.write", "model.invoke", "merge"],
  expiresIn: "7d",
});
console.log(treaty.describe());
Output
treaty           trt-01aa…4c19, signed by both kernels
participants     you · partner (did:chio:z6MkpA7…c2T)
action           you       them      admitted as
workspace.read   allow     allow     either kernel admits under its own policy
workspace.write  allow     allow     either kernel admits under its own policy
model.invoke     allow     allow     charged at your door
merge            co-sign   co-sign   both kernels sign
expires          in 7 days

You should see one row per action with what each side requires. Every action is admitted by the kernel that serves it, under that kernel's own policy, and merge resolves to co-sign on both sides.

Read more in Agree on what crosses.

Admission checks one action class against the bound contract

chio federation treaty admit emits an admission report for one class. A structurally invalid input returns an error before any report; a later failure produces a report with accepted: false and a failure code. The checks run in this order:

  1. Structure. The scope and the intersection both validate, seeding checks with chio_federation.treaty.scope_valid and chio_federation.treaty.intersection_valid.
  2. Freshness. --now-unix-ms sits inside the scope window and the intersection window, at or after generatedAtUnixMs.
  3. Identity. The intersection's treatyId, ladderManifestSha256s, and participantKernelIds equal the scope's, element for element.
  4. Binding. --expected-ladder-intersection-sha256 is supplied and equals the digest computed from the intersection handed in.
  5. Scope membership. The action class appears in the scope's allowedActionClasses and in the intersection's actionClasses. A miss in either raises the same code.
  6. Evidence presence, then evidence verification. Every required label is present, and each carries a verified: true reference.

The expected intersection digest comes from the verifier's own computation. Hashing an intersection you received and passing that hash back proves only that the intersection matches itself, never that it reflects your governance ladders.

At runtime the serving kernel applies the same contract at its own door. It reads the scope and intersection from its own store after checking the federated grant and its DPoP proof, and a stale treaty records chio_treaty_stale in chio_runtime.failure_code. Cross-org admission documents the same report and its failure codes from the boundary side.

The example treaty admits analysis.read and refuses a changed manifest

The federation guide project starts with a non-destructive analysis action, so the first workflow can verify every input it produced locally. The program writes both signed manifests, the pinned scope, the canonical intersection, and the clock and digest context the CLI reads.

Create complete treaty inputs

Generate fresh operator keys, signed ladder manifests, their pinned scope, and an intersection. Verify a permitted action and reject a changed manifest.

Complete the project setup, then run the program from federation-guide/. The download includes every constructor and input.

Terminalbash
cargo run --locked --bin treaty
Output on darwin/arm64 · exit 0
analysis.read: mode guarded, accepted true
changed pinned manifest: federation treaty rejected: chio_federation_ladder_manifest_hash_mismatch: computed ladder manifest hashes do not match treaty scope
wrote complete scope, signed manifests, intersection, and verification context

The manifests are locally signed and verified; scope revocation/trust digests identify example declarations. This action requires no external evidence. The separate billing reference demonstrates caller-asserted evidence classes; neither example executes a payment.

Read the complete program 103 lines · complete file
src/bin/treaty.rsrust
use chio_core_types::{
    crypto::{sha256_hex, Keypair},
    receipt::lineage::SignedExportEnvelope,
};
use chio_federation::treaty::*;
use federation_guide::now;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let now = now() * 1000;
    std::fs::create_dir("treaty-inputs")?;
    let keys = [Keypair::generate(), Keypair::generate()];
    let mut manifests = Vec::new();
    for (i, mode) in ["observation", "guarded"].into_iter().enumerate() {
        let manifest = GovernanceLadderManifest {
            schema: CHIO_FEDERATION_GOVERNANCE_LADDER_MANIFEST_SCHEMA.into(),
            manifest_id: format!("operator-{i}-ladder"),
            kernel_id: format!("operator-{i}"),
            issuer: keys[i].public_key().to_hex(),
            key_id: format!("operator-{i}-key"),
            issued_at_unix_ms: now - 1000,
            expires_at_unix_ms: now + 600_000,
            destructive_floor: "receipt_backed".into(),
            default_unknown_mode: "deny".into(),
            action_classes: vec![GovernanceLadderActionClass {
                action_class_id: "analysis.read".into(),
                mode: mode.into(),
                destructive: false,
                consistency_model: "single-kernel".into(),
                co_sign: "none".into(),
                co_sign_quorum: None,
                evidence_required: vec![],
                aliases: vec!["read".into()],
            }],
        };
        validate_governance_ladder_manifest(&manifest)?;
        // The manifest has no signature field. A signed export envelope can
        // authenticate distribution; the treaty separately pins the body hash.
        let signed = SignedExportEnvelope::sign(manifest.clone(), &keys[i])?;
        assert_eq!(signed.signer_key, keys[i].public_key());
        assert!(signed.verify_signature()?);
        std::fs::write(
            format!("treaty-inputs/operator-{i}-signed.json"),
            serde_json::to_vec_pretty(&signed)?,
        )?;
        std::fs::write(
            format!("treaty-inputs/operator-{i}.json"),
            serde_json::to_vec_pretty(&manifest)?,
        )?;
        manifests.push(manifest);
    }
    let scope = TreatyScope {
        schema: CHIO_FEDERATION_TREATY_SCOPE_SCHEMA.into(),
        treaty_id: "analysis-treaty".into(),
        participant_kernel_ids: manifests.iter().map(|m| m.kernel_id.clone()).collect(),
        participant_public_keys: keys.iter().map(Keypair::public_key).collect(),
        ladder_manifest_sha256s: manifests
            .iter()
            .map(governance_ladder_manifest_sha256)
            .collect::<Result<_, _>>()?,
        allowed_action_classes: vec!["analysis.read".into()],
        issued_at_unix_ms: now - 1000,
        expires_at_unix_ms: now + 600_000,
        revocation_epoch_sha256: sha256_hex(b"local revocation snapshot"),
        trust_bundle_sha256: sha256_hex(b"local two-operator trust configuration"),
    };
    let intersection = compute_ladder_intersection(&scope, &manifests, now)?;
    let report = evaluate_cross_boundary_admission(CrossBoundaryAdmissionInput {
        treaty_scope: &scope,
        ladder_intersection: &intersection,
        expected_ladder_intersection_sha256: Some(ladder_intersection_sha256(&intersection)?),
        action_class_id: "analysis.read",
        present_evidence: vec![],
        verified_evidence: vec![],
        now_unix_ms: now,
    })?;
    println!(
        "analysis.read: mode {}, accepted {}",
        report.mode, report.accepted
    );
    assert!(report.accepted);
    std::fs::write(
        "treaty-inputs/scope.json",
        serde_json::to_vec_pretty(&scope)?,
    )?;
    std::fs::write(
        "treaty-inputs/intersection.json",
        serde_json::to_vec_pretty(&intersection)?,
    )?;
    std::fs::write(
        "treaty-inputs/context.json",
        serde_json::to_vec_pretty(
            &serde_json::json!({"nowUnixMs":now,"intersectionSha256":ladder_intersection_sha256(&intersection)?}),
        )?,
    )?;
    manifests[0].action_classes[0].mode = "maintenance".into();
    println!(
        "changed pinned manifest: {}",
        compute_ladder_intersection(&scope, &manifests, now)
            .expect_err("changed manifest must not match treaty pin")
    );
    println!("wrote complete scope, signed manifests, intersection, and verification context");
    Ok(())
}

Open the source file. Each program asserts its expected refusals; keys, timestamps, and digests change between runs.

Recompute the intersection and admit the action

Terminalbash
TREATY_TIME=$(jq -r .nowUnixMs treaty-inputs/context.json)
TREATY_DIGEST=$(jq -r .intersectionSha256 treaty-inputs/context.json)
chio federation treaty intersect --treaty-scope treaty-inputs/scope.json \
  --manifest treaty-inputs/operator-0.json --manifest treaty-inputs/operator-1.json \
  --now-unix-ms "$TREATY_TIME" --report treaty-inputs/cli-intersection.json
chio federation treaty admit --treaty-scope treaty-inputs/scope.json \
  --ladder-intersection treaty-inputs/cli-intersection.json \
  --expected-ladder-intersection-sha256 "$TREATY_DIGEST" \
  --action-class-id analysis.read --now-unix-ms "$TREATY_TIME" \
  --report treaty-inputs/cli-admission.json
cat treaty-inputs/cli-admission.json
Output · exit 0
{
  "schema": "chio.federation.cross-boundary-admission-report.v1",
  "treatyId": "analysis-treaty",
  "actionClassId": "analysis.read",
  "accepted": true,
  "mode": "guarded",
  "consistencyModel": "single-kernel",
  "coSign": "none",
  "requiredEvidence": [],
  "presentEvidence": [],
  "verifiedEvidence": [],
  "treatyScopeSha256": "dd8e24469bd89e97820e8ae1196a394f94d60e11021bc69a3c805e380a7ec9ae",
  "ladderIntersectionSha256": "c0f39c8af4b8856c8d942ec3c1aa15bac131b24c358512d1903a1fe52c0cfc68",
  "expectedLadderIntersectionSha256": "c0f39c8af4b8856c8d942ec3c1aa15bac131b24c358512d1903a1fe52c0cfc68",
  "checks": [
    "chio_federation.treaty.scope_valid",
    "chio_federation.treaty.intersection_valid",
    "chio_federation.treaty.required_evidence_present",
    "chio_federation.treaty.required_evidence_verified"
  ]
}

You should see analysis.read: mode guarded, accepted true, then the changed manifest refused with chio_federation_ladder_manifest_hash_mismatch. The CLI's admission report repeats both digests and lists the four checks the action passed.

A changed pin makes a new scope, and revoking the treaty ends its grants

A scope is usable within issuedAtUnixMs <= now < expiresAtUnixMs. Intersection checks the scope and every manifest against the caller's time, admission checks the scope and intersection again, and the intersection expires at the earliest expiry among its inputs.

Changing revocationEpochSha256 or trustBundleSha256 changes treatyScopeSha256. The intersection omits those two pins, so unchanged ladders at the same generation time can yield the same ladderIntersectionSha256. An artifact bound to an old scope stays consistent with that scope, and a revocation epoch published elsewhere leaves it unchanged. It is rejected when compared against the current trusted inputs: refresh the verifier's scope, recompute the bindings, and apply current revocation and trust policy at the receiving door.

Revoking the treaty ends every grant issued under it, including the grants the partner's agents delegated to each other, and their next call is refused:

Terminalbash
chio federation treaty revoke --treaty trt-01aa…4c19
Output
revoked    trt-01aa…4c19, signed by your kernel 4f1c…a2e9
grants     cap-01a9…7e40 and 4 delegated descendants revoked
feed       revocation root sent to partner over iroh

Read more in Revoke, and the work stops.

Treaty scope fields

chio.federation.treaty-scope.v1 has ten required fields. The schema and Rust type reject unknown fields. Its canonical-JSON digest binds every field, including the revocation-epoch and trust-bundle pins.

FieldConstraintWhat it pins
schema
const chio.federation.treaty-scope.v1
Any other value rejects with unsupported_treaty_scope_schema.
treatyId
non-empty string
The name the intersection, the admission report, and the bilateral invocation all repeat back.
participantKernelIds
unique array, min 2
Every kernel party to the treaty, in the order the intersection preserves.
participantPublicKeys
unique array, min 2, each ^[0-9a-f]{64}$
One Ed25519 public key per participant; the count equals the kernel-id count.
ladderManifestSha256s
unique array, min 2, each 64-hex
The exact governance ladder each side agreed to; a different manifest gives a different scope binding.
allowedActionClasses
unique array, min 1
The candidate classes. Only a class listed here can be admitted, whatever the ladders say.
issuedAtUnixMs
integer, min 0
Start of the validity window, inclusive.
expiresAtUnixMs
integer, min 0
End of the validity window, exclusive; strictly greater than issuedAtUnixMs.
revocationEpochSha256
64-hex
The revocation epoch the scope was cut against.
trustBundleSha256
64-hex
The verifier trust bundle the scope was cut against.

Participant IDs, public keys, and manifest hashes have matching counts, with at least two participants. Duplicate IDs and keys reject, and manifest hashes are unique. The participant order is preserved in the intersection, while supplied manifests are matched to the pinned set.

Scope JSON

The treaty between you and your partner from Quickstart part 3 records its scope like this; digests are shortened. Use the digests of your own verified manifests and trust material when you construct a scope.

treaty-scope.jsonjson
{
  "schema": "chio.federation.treaty-scope.v1",
  "treatyId": "trt-01aa…4c19",
  "participantKernelIds": ["did:chio:z6Mkf3…Qp9", "did:chio:z6MkpA7…c2T"],
  "participantPublicKeys": [
    "4f1c…a2e9",
    "9b07…d31c"
  ],
  "ladderManifestSha256s": [
    "4f2c…b81a",
    "9c7b…c104"
  ],
  "allowedActionClasses": ["workspace.read", "workspace.write", "model.invoke", "merge"],
  "issuedAtUnixMs": 1800000000000,
  "expiresAtUnixMs": 1800003600000,
  "revocationEpochSha256": "1a4c…8039",
  "trustBundleSha256": "77e1…d972"
}

treatyScopeSha256 hashes RFC 8785 canonical JSON of the record, so reformatting a file preserves the digest and changing a field changes it. An internally consistent old artifact stays valid against itself; your verifier compares artifacts against its current trusted inputs.

Combine each participant’s requirements

For each allowed action class, intersection resolves the class or one of its aliases in every participant's ladder. The fold keeps the strongest ranked requirements, unions evidence, and requires compatible consistency and quorum declarations.

Intersected fieldRuleOrdering
mode
Highest declared rank across participants
observation 0, guarded 1, receipt_backed 2, partition_contingency 3, maintenance 4. Anything else is chio_federation_ladder_invalid_mode.
destructive
Logical OR: one side calling it destructive makes it destructive
false then true
coSign
Strictest declared requirement
none 0, bilateral_if_cross_org 1, bilateral_required 2, n_of_m 3. Anything else is chio_federation_ladder_invalid_cosign_mode.
coSignQuorum
Intersects: m and scope agree exactly, n takes the maximum
A disagreement on m or scope is chio_federation_ladder_quorum_misdeclared. The first participant to declare a quorum sets it; later ones intersect with it.
consistencyModel
Unanimity: every participant declares the same value
No ordering. Disagreement rejects.
evidenceRequired
Set union, emitted in sorted order
Every label any participant demanded survives
participantModes
Per-kernel record of what each side declared before the fold
Keyed by kernel id, so the climb stays auditable

A destructive action uses receipt_backed or a higher mode and a consistency model other than crdt-commutative. These floors are checked after the fold and again when the intersection is validated.

The intersection carries the participant IDs and ladder hashes, an ID based on the treaty and the supplied time, and the earliest expiry among the scope and manifests. It omits the full treaty-scope digest and the scope's revocation and trust-bundle hashes.

Admission report

Required references carry the expected evidence-class labels, valid artifact digests, and verified: true. The CLI records each well-formed --evidence evidence_class=artifact_sha256 pair as caller-verified, so supply only artifacts you have already authenticated and checked.

bilateral_required implies bilateral_invocation evidence and n_of_m implies quorum_signature; each is added if absent. quorum-required is a consistency model and adds no evidence label on its own.

Report fields on denial

Freshness, identity, binding, and scope failures happen before a class is resolved. Their mode, consistency, and co-sign fields carry the observation, totally-ordered, and none defaults with an empty required-evidence list; read failureCode for the cause. Accepted reports and evidence-stage denials carry the resolved class terms.

The output validator checks that acceptance agrees with the presence of required verified references and that failureCode agrees with accepted. Those checks establish the report's consistency; authenticating the artifacts behind caller assertions stays with the caller.

Treaty refusal codes

Unknown classes are refused. A mismatch in scope, manifests, consistency, freshness, expected intersection, or required evidence stops admission; the code names the input to inspect.

CodeRaised whenStage
chio_federation_treaty_stale
The evaluation time falls outside the scope window or the intersection window, or a validity window is empty.
intersect, admit
chio_federation_ladder_manifest_stale
A supplied governance ladder manifest is outside its own window at intersection time.
intersect
chio_federation_treaty_missing_participant
The manifest count differs from the participant count, a manifest names a kernel outside the scope, two manifests claim the same kernel, or a participant has no manifest.
intersect
chio_federation_ladder_manifest_hash_mismatch
The recomputed manifest digests differ from ladderManifestSha256s, or the scope carries a malformed or duplicate manifest hash.
intersect
chio_federation_treaty_action_class_not_allowed
A participant ladder lacks the class under its id or an alias, the intersection produced no classes, or the requested class is outside the scope or the intersection.
intersect, admit
chio_federation_ladder_consistency_mismatch
Participants declare different consistency models for the same class.
intersect
chio_federation_ladder_destructive_below_floor
A destructive class sits below its manifest's destructiveFloor, or an intersected destructive class resolves below receipt_backed.
intersect
chio_federation_ladder_destructive_crdt_not_allowed
A destructive class declares or resolves to crdt-commutative consistency.
intersect
chio_federation_ladder_alias_conflict
An alias collides with another class id or another alias inside one manifest.
intersect
chio_federation_ladder_duplicate_action_class
One manifest lists the same action class id twice.
intersect
chio_federation_ladder_quorum_misdeclared
A quorum is declared without n_of_m, or n_of_m without one; the quorum breaks 2 <= n <= m; its scope is outside treaty / kernel / operator; or two participants disagree on m or scope.
intersect, admit
chio_federation_treaty_missing_intersection_binding
No expected intersection digest was supplied. Library callers only: the CLI declares --expected-ladder-intersection-sha256 as a required argument.
admit
chio_federation_treaty_intersection_mismatch
The intersection disagrees with the scope on treaty id, participants, or manifest hashes, or its digest differs from the expected one.
admit
chio_federation_treaty_missing_required_evidence
A required evidence label is absent from presentEvidence.
admit
chio_federation_treaty_unverified_required_evidence
A required label is present with no reference, or its reference carries verified: false.
admit

Evidence kinds and verification grades

An admission reference's evidenceClass names an artifact kind, such as receipt_lineage or bilateral_invocation, and its verified flag is the caller's verification assertion. A lineage statement's evidenceClass instead grades evidence as verified, observed, asserted, unverifiable, or rejected.

Buyer-packet verification requires the lineage grade verified, an accepted admission report, and consistent artifact digests and identities. A scope or an accepted report replaces none of a capability grant, a trust root, or a payment settlement, and the buyer verifier rejects settlementClaimed: true.

Buyer-packet bindings and refusal codes

verify-packet recomputes the lineage, continuation, admission, and bilateral digests and compares each against the packet's claim. It requires the bilateral invocation's signerKernelIds to equal the continuation's source and target in that order, localReceiptSha256 and remoteReceiptSha256 to equal the lineage's parent and child receipt hashes, and the admission report to be accepted with exactly one verified reference each for receipt_lineage and bilateral_invocation at the packet's digests.

Identity failures raise chio_attest_buyer_packet_identity_mismatch and digest failures raise chio_attest_buyer_packet_hash_mismatch. A packet whose bilateral DSSE digest cannot be resolved lands on chio_attest_buyer_packet_dsse_unresolved; the hash alone is never enough.

Next: declare the ladder your kernel contributes

Establish federation · 3 of 7

PreviousPeer HandshakeNextDelegation