Treaties
Let independently governed kernels cooperate under one explicit action contract, with each participant’s requirements preserved in the shared decision.
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.
One LadderIntersection over 3 action classes.
One LadderIntersection over 3 action classes.
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
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.
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());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
from chio import Computer
project = Computer.open("quickstart")
partner = Computer.connect("partner")
treaty = project.treaty(
partner=partner,
actions=["workspace.read", "workspace.write", "model.invoke", "merge"],
expires_in="7d",
)
print(treaty.describe())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
use chio::{Computer, Treaty};
let project = Computer::open("quickstart").await?;
let partner = Computer::connect("partner").await?;
let treaty = project
.treaty(
Treaty::with(&partner)
.actions(["workspace.read", "workspace.write", "model.invoke", "merge"])
.expires_in("7d"),
)
.await?;
println!("{}", treaty.describe());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:
- Structure. The scope and the intersection both validate, seeding
checkswithchio_federation.treaty.scope_validandchio_federation.treaty.intersection_valid. - Freshness.
--now-unix-mssits inside the scope window and the intersection window, at or aftergeneratedAtUnixMs. - Identity. The intersection's
treatyId,ladderManifestSha256s, andparticipantKernelIdsequal the scope's, element for element. - Binding.
--expected-ladder-intersection-sha256is supplied and equals the digest computed from the intersection handed in. - Scope membership. The action class appears in the scope's
allowedActionClassesand in the intersection'sactionClasses. A miss in either raises the same code. - Evidence presence, then evidence verification. Every required label is present, and each carries a
verified: truereference.
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.
cargo run --locked --bin treatyanalysis.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
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
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{
"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:
chio federation treaty revoke --treaty trt-01aa…4c19revoked 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.
Try treatyScopeSha256, coSignQuorum, or chio_federation_treaty_stale. Each entry has its own link.
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.
| Field | Constraint | What it pins |
|---|---|---|
schema | const chio. | Any other value rejects with unsupported_. |
treatyId | non-empty string | The name the intersection, the admission report, and the bilateral invocation all repeat back. |
participant | unique array, min 2 | Every kernel party to the treaty, in the order the intersection preserves. |
participant | unique array, min 2, each ^[0-9a-f]{64}$ | One Ed25519 public key per participant; the count equals the kernel-id count. |
ladder | unique array, min 2, each 64-hex | The exact governance ladder each side agreed to; a different manifest gives a different scope binding. |
allowed | 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. |
revocation | 64-hex | The revocation epoch the scope was cut against. |
trust | 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.
{
"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 field | Rule | Ordering |
|---|---|---|
mode | Highest declared rank across participants | observation 0, guarded 1, receipt_backed 2, partition_ 3, maintenance 4. Anything else is chio_. |
destructive | Logical OR: one side calling it destructive makes it destructive | false then true |
coSign | Strictest declared requirement | none 0, bilateral_ 1, bilateral_ 2, n_of_m 3. Anything else is chio_. |
coSignQuorum | Intersects: m and scope agree exactly, n takes the maximum | A disagreement on m or scope is chio_. The first participant to declare a quorum sets it; later ones intersect with it. |
consistency | Unanimity: every participant declares the same value | No ordering. Disagreement rejects. |
evidence | Set union, emitted in sorted order | Every label any participant demanded survives |
participant | 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.
| Code | Raised when | Stage |
|---|---|---|
chio_ | The evaluation time falls outside the scope window or the intersection window, or a validity window is empty. | intersect, admit |
chio_ | A supplied governance ladder manifest is outside its own window at intersection time. | intersect |
chio_ | 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_ | The recomputed manifest digests differ from ladder, or the scope carries a malformed or duplicate manifest hash. | intersect |
chio_ | 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_ | Participants declare different consistency models for the same class. | intersect |
chio_ | A destructive class sits below its manifest's destructive, or an intersected destructive class resolves below receipt_backed. | intersect |
chio_ | A destructive class declares or resolves to crdt- consistency. | intersect |
chio_ | An alias collides with another class id or another alias inside one manifest. | intersect |
chio_ | One manifest lists the same action class id twice. | intersect |
chio_ | 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_ | No expected intersection digest was supplied. Library callers only: the CLI declares --expected- as a required argument. | admit |
chio_ | The intersection disagrees with the scope on treaty id, participants, or manifest hashes, or its digest differs from the expected one. | admit |
chio_ | A required evidence label is absent from presentEvidence. | admit |
chio_ | 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.