PlatformJoint Authorization
Swarm
Governance Ladder
A manifest, pinned at federation handshake, that maps each action class to a governance mode and the consistency model it requires.
Two schemas, one identifier
chio.federation.governance-ladder-manifest.v1 names two different shapes in the Chio tree. This page documents the one the code enforces: GovernanceLadderManifest (chio-federation/src/treaty.rs:61-72), mirrored key for key by the published JSON Schema at spec/schemas/chio-federation/v1/governance-ladder-manifest.schema.json. The other is the JSON Schema printed inside spec/CHIO_LADDER.md:49-305, which is snake_case and requires eleven keys (:55-67). Four concepts appear on both sides and only schema is spelled the same. The spec side then requires six keys the shipped struct has no field for (participant_id, domain, ladder_version, modes, default_unmapped_mode, ladder_refusal_policy) plus a signature object, while the shipped side requires six the spec never mentions (kernelId, issuer, keyId, issuedAtUnixMs, expiresAtUnixMs, defaultUnknownMode). A manifest written to the spec section is refused by deny_unknown_fields before any rule runs.Five modes, strictly ordered
A mode is a rank, and the rank is the whole of its machine-readable meaning. Everything the code does with a mode is a comparison: ladder_mode_rank maps the five names onto 0 through 4 and rejects anything else.
fn ladder_mode_rank(mode: &str) -> Result<u8, FederationTreatyError> {
match mode {
"observation" => Ok(0),
"guarded" => Ok(1),
"receipt_backed" => Ok(2),
"partition_contingency" => Ok(3),
"maintenance" => Ok(4),
_ => rejected(
"chio_federation_ladder_invalid_mode",
"governance ladder mode is not supported",
),
}
}Maintenance sits highest because it requires an authenticated operator present, not because it is the most destructive (spec/CHIO_LADDER.md:312-316). The coverage column below is the spec’s (:320-382); the rank column is the crate’s.
| Mode | Rank | Coverage | Required artefacts |
|---|---|---|---|
observation | 0 | Signal ingest, detection, investigation, correlation, durable memory, status publication, decoy operation, listing browse. | Signed deposits or listings. Receipts are emitted; no governance receipt. |
guarded | 1 | Non-destructive escalation, decoy deployment, advisory listing publication, low-amount quotes. | Policy validation evidence and the ordinary signed audit trail. |
receipt_backed | 2 | Destructive response, binding financial commitments, sanction enforcement, passport state changes. | Signed governance receipt, bilateral co-signature when co_sign is not none, workflow receipt at the boundary tick. |
partition_contingency | 3 | The destructive subset of receipt-backed actions while the federation is partitioned. | Staged contingency lease inside the declared cap and TTL, plus a reconciliation case at heal time. |
maintenance | 4 | Operator review, evidence export, replay, key rotation, manifest amendment. | Authenticated operator session and explicit operator presence in the receipt body. |
A verifier trust bundle does not carry the five-rank ladder. It collapses an action class to one of two kinds, routine or receipt_backed (ChioActionClassKind, chio-attest-buyer-core/src/trust_bundle.rs:31-36), because a downstream auditor needs to know whether a governance receipt was required, not how the two operators ranked the class between themselves.
The action class
Eight fields, camelCase on the wire, with deny_unknown_fields so an unrecognised key fails the parse rather than being ignored:
pub struct GovernanceLadderActionClass {
pub action_class_id: String,
pub mode: String,
pub destructive: bool,
pub consistency_model: String,
pub co_sign: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub co_sign_quorum: Option<GovernanceLadderQuorum>,
pub evidence_required: Vec<String>,
#[serde(default)]
pub aliases: Vec<String>,
}co_sign_quorum is the only optional one, and it is not optional in the way that phrasing suggests. validate_co_sign_quorum (treaty.rs:893-923) requires it when co_sign is n_of_m and forbids it otherwise, raising chio_federation_ladder_quorum_misdeclared either way. The JSON Schema encodes the same pairing as an if/then/else over coSign (schema :76-85).
pub struct GovernanceLadderQuorum {
pub n: u16,
pub m: u16,
pub scope: String,
}Inside a quorum, n and m must both be at least 2 with n <= m, and scope is one of treaty, kernel or operator. A 1-of-3 quorum is rejected, not accepted as a degenerate case.
Four co-sign values
fn validate_co_sign_mode(mode: &str) -> Result<(), FederationTreatyError> {
match mode {
"none" | "bilateral_if_cross_org" | "bilateral_required" | "n_of_m" => Ok(()),
_ => rejected(
"chio_federation_ladder_invalid_cosign_mode",
"governance ladder co-sign mode is not supported",
),
}
}They are ordered as well as closed, and the intersection uses the ordering:
fn co_sign_requirement_rank(mode: &str) -> Result<u8, FederationTreatyError> {
match mode {
"none" => Ok(0),
"bilateral_if_cross_org" => Ok(1),
"bilateral_required" => Ok(2),
"n_of_m" => Ok(3),
_ => rejected(
"chio_federation_ladder_invalid_cosign_mode",
"governance ladder co-sign mode is not supported",
),
}
}Two of the four pull required evidence with them. required_evidence_for_action (treaty.rs:840-857) appends bilateral_invocation to a bilateral_required class and quorum_signature to an n_of_m one, in each case only when the class did not already list it. A class author does not have to remember to declare them.
Four consistency models
Bilateral trees prove that N parties signed, not that they signed consistently. A and B can co-sign a credential’s revocation at t=10 while B and C co-sign continued use of it at t=11, both receipts independently valid. The consistency model is the per-class declaration that closes that window. The names are hyphenated, unlike the underscored modes and co-sign values in the same struct, and the federation validator accepts no other spelling:
fn validate_consistency_model(model: &str) -> Result<(), FederationTreatyError> {
match model {
"crdt-commutative" | "totally-ordered" | "single-kernel" | "quorum-required" => Ok(()),
_ => rejected(
"chio_federation_ladder_invalid_consistency_model",
"governance ladder consistency model is not supported",
),
}
}| Model | What it asserts about divergent co-signs |
|---|---|
crdt-commutative | Merge is commutative, so divergent co-signs converge on reconnect and a bilateral tree needs no anchor (spec/CHIO_LADDER.md:393-409). Refused on a destructive class. |
totally-ordered | A bilateral tree plus an ordering anchor, so a receipt whose parent or epoch does not match the receiver’s view is rejected instead of merged (:411-428). |
single-kernel | Accepted by both validators and by all three published schemas, and the only one the spec’s consistency chapter does not describe: spec/CHIO_LADDER.md:386-475 covers three models and says so in its opening line. |
quorum-required | A FROST-aggregated Ed25519 signature over a domain-separated canonical body, so no minority pair can produce an accepted decision (:430-445). |
Two of these bite at execution time rather than at manifest time, in the strict bilateral verifier. A predicate whose consistency_model is totally-ordered or quorum-required must carry a non-empty consistency_anchor, or verification fails with strict treaty DSSE ordered consistency requires consistency_anchor (chio-federation/src/bilateral_verifier/treaty.rs:130-138). The anchor value itself is compared against the runtime step evidence a line earlier, so it is a binding, not a label.
A destructive class declaring crdt-commutative is refused twice over: once when its own manifest is validated (treaty.rs:588-593) and again if the intersected class resolves that way (:320-325), both with chio_federation_ladder_destructive_crdt_not_allowed.
What n_of_m actually buys
The manifest declares; the verifier enforces. When a bilateral DSSE predicate arrives with co_sign: n_of_m, bind_frost_authorization_to_predicate (chio-federation/src/bilateral_verifier/cosign.rs:404-464) demands six things at once, and a missing one is a refusal rather than a downgrade:
- A
VerifiedFrostAuthorizationis present, from a verifiedchio.frost.authorization.v1record (frost/verify.rs:17). Without it: co_sign n_of_m requires VerifiedFrostAuthorization. - The predicate carries a
treaty_binding_ref. - Its
consistency_modelisquorum-requiredand itsconsistency_anchoris exactlyfrost-quorum. - The authorization’s ladder action class equals the treaty binding’s action class.
- Its scope equals the treaty id, and its resource equals the predicate’s
invocation_id. - It is current at the verifier’s pinned epoch, not at wall-clock now.
The same function runs the check in reverse. A bilateral_required or bilateral_if_cross_org predicate that arrives with a FROST authorization is refused for that reason alone: verified FROST authorization supplied for a non-n_of_m predicate. A quorum signature is not extra credit that a weaker class may attach.
Two validators, one wire shape
The ladder types are declared twice, with the same eight action-class fields and the same camelCase deny_unknown_fields serde, in chio-federation/src/treaty.rs:38-49 and chio-runtime-core/src/types.rs:173-184. The two agree on structure and on the rules that matter most: both require defaultUnknownMode to be exactly deny, under the same code governance_ladder_manifest_unknown_default_not_deny (chio-runtime-core/src/treaty.rs:133-138), and both refuse a consistency-model disagreement outright rather than folding it.
They disagree about spelling, and a manifest author needs to know which one is reading. The federation validator is exact. The runtime path normalises, and it does so through functions whose return type is the canonical spelling rather than a bool:
pub fn bilateral_dsse_consistency_model(model: &str) -> Result<&'static str, ChioRuntimeError> {
match model {
"crdt_commutative" | "crdt-commutative" => Ok("crdt-commutative"),
"totally_ordered" | "totally-ordered" => Ok("totally-ordered"),
"single_kernel" | "single-kernel" => Ok("single-kernel"),
"quorum_required" | "quorum-required" => Ok("quorum-required"),
_ => rejected(
"chio_ladder_invalid_consistency_model",
"governance ladder consistency model is not supported",
),
}
}It has six non-test callers on the admission path (admission_hook/dsse.rs:51,143, admission_hook/treaty_evidence.rs:381-382, chio-runtime-harness/src/proof_assembly.rs:574, chio-runtime-harness/src/treaty.rs:245), so an underscored consistencyModel on a DSSE predicate is admitted and rewritten, while the same string in a manifest handed to chio-federation is refused with chio_federation_ladder_invalid_consistency_model.
Two more aliases live only on the runtime side. ladder_co_sign_mode (chio-runtime-core/src/treaty.rs:780-791) maps quorum_required onto n_of_m, and ladder_mode_rank (:752-764) maps the same string onto rank 4 beside maintenance. One spelling therefore reads as a consistency model, a co-sign requirement and a mode depending on which field carries it. Write the canonical hyphenated consistency models and the four canonical co-sign values, and both validators agree.
The manifest
pub struct GovernanceLadderManifest {
pub schema: String,
pub manifest_id: String,
pub kernel_id: String,
pub issuer: String,
pub key_id: String,
pub issued_at_unix_ms: u64,
pub expires_at_unix_ms: u64,
pub destructive_floor: String,
pub default_unknown_mode: String,
pub action_classes: Vec<GovernanceLadderActionClass>,
}default_unknown_mode is the field most likely to surprise. It is not a fallback rung on the mode ladder, and it is not configurable: the validator requires the literal string deny, and deny is not a member of ladder_mode_rank at all, so a manifest that names any real mode here is rejected.
if manifest.default_unknown_mode != "deny" {
return rejected(
"governance_ladder_manifest_unknown_default_not_deny",
"governance ladder manifest must deny unknown action classes",
);
}The published schema says the same thing in one line, "defaultUnknownMode": { "const": "deny" } (schema :28). An unmapped action class has no reading. It is not admitted at a low mode.
A manifest built the way the crate’s own integration test builds one, with the values that pass every rule:
{
"schema": "chio.federation.governance-ladder-manifest.v1",
"manifestId": "ladder-kernel.vendor",
"kernelId": "kernel.vendor",
"issuer": "did:chio:kernel.vendor",
"keyId": "ladder-key-1",
"issuedAtUnixMs": 1800000000000,
"expiresAtUnixMs": 1800003600000,
"destructiveFloor": "receipt_backed",
"defaultUnknownMode": "deny",
"actionClasses": [
{
"actionClassId": "workflow.destructive.vendor_call",
"mode": "receipt_backed",
"destructive": false,
"consistencyModel": "totally-ordered",
"coSign": "bilateral_required",
"evidenceRequired": ["receipt_lineage"],
"aliases": []
}
]
}The field names and values match treaty_manifest and treaty_action in chio-federation/tests/treaty.rs:135-165, rendered through the camelCase serde the struct declares. Note what is absent: no signature block. The spec section requires one and defines a $defs/signature of {signer_key, alg, value} (spec/CHIO_LADDER.md:294-303), and the shipped schema has no signature property and no such $defs entry: grep -c signature over that file returns zero. What a peer signs on the shipped path is the reference, not the body.
What the validator rejects
validate_governance_ladder_manifest (treaty.rs:524-621) runs top to bottom and returns on the first failure. Every code below is the literal first argument to a rejected(...) call in that function.
| Code | Condition |
|---|---|
unsupported_governance_ladder_manifest_schema | schema is not chio.federation.governance-ladder-manifest.v1. |
governance_ladder_manifest_empty_id | manifestId is empty or whitespace. Sibling codes cover kernelId, issuer and keyId. |
governance_ladder_manifest_invalid_window | issuedAtUnixMs >= expiresAtUnixMs. |
governance_ladder_manifest_unknown_default_not_deny | defaultUnknownMode is anything but deny. |
governance_ladder_manifest_missing_action_classes | actionClasses is empty. |
chio_federation_ladder_duplicate_action_class | Two classes share an actionClassId. |
chio_federation_ladder_alias_conflict | An alias repeats, or collides with a class id in either direction. |
chio_federation_ladder_invalid_mode | A class mode, or the destructiveFloor, is outside the five. |
chio_federation_ladder_invalid_consistency_model | consistencyModel is outside the four. |
chio_federation_ladder_invalid_cosign_mode | coSign is outside the four. |
chio_federation_ladder_quorum_misdeclared | n_of_m without a quorum, a quorum on any other mode, n < 2, m < 2, n > m, or an unsupported scope. |
chio_federation_ladder_destructive_below_floor | A destructive class ranks below destructiveFloor. |
chio_federation_ladder_destructive_crdt_not_allowed | A destructive class declares crdt-commutative. |
governance_ladder_destructive_missing_evidence | A destructive class has an empty evidenceRequired. |
governance_ladder_invalid_evidence_label | An evidence label is outside the schema’s ^[a-z0-9_]+$. |
governance_ladder_duplicate_evidence | A class lists one evidence label twice. |
governance_ladder_empty_alias | An alias is empty or whitespace. |
Pinned at handshake, by reference
A peer does not send its ladder at handshake. It sends a digest of it, inside the signed challenge body:
/// Signed handshake reference to the ladder manifest a peer will enforce.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct LadderManifestRef {
pub manifest_id: String,
pub sha256: String,
pub issued_at_unix_ms: u64,
pub expires_at_unix_ms: u64,
}HandshakeChallenge carries this as an optional field (trust_establishment.rs:240), and canonical_bytes validates it before signing (:294-296), so a malformed reference cannot be signed rather than being caught later. The reference validator requires a non-empty manifestId, a 64-character hex sha256, and a window with expiresAtUnixMs strictly after issuedAtUnixMs (:85-102). The window matters because the strict verifier re-checks it much later: require_fresh_ladder_manifest (bilateral_verifier/cosign.rs:466-487) runs for both peers on every bilateral invocation (:118-119) and raises LadderManifestMissing or LadderManifestStale against the verifier’s pinned epoch, whose wire codes are ladder.manifest_missing and ladder.manifest_stale (bilateral_verifier/error.rs:102-103). An expired ladder stops the invocations, not just the next handshake.
The three-vendor fixture carries four of these, one per kernel, in peerLadderBindings:
"kernelId": "did:chio:vendor-c",
"publicKey": "31debe55d37c722768b137131caa6087080b2e0b60b94bd785d14575cfa498bc",
"ladderManifestRef": {
"manifestId": "ladder:vendor-c:refund:v1",
"sha256": "8ef4eaabdcbaeb55ab8b3c1cdfa52827e8c93d9870dc71dcabb09382dbaf9371",
"issuedAtUnixMs": 1765999940000,
"expiresAtUnixMs": 1766000060000
}A two-minute window, which is what makes the freshness check bite in a deterministic fixture. Both the public key and the digest are bare lowercase hex with no algorithm prefix, which is how an Ed25519 PublicKey renders (chio-core-types/src/crypto.rs:575-583).
The intersection
compute_ladder_intersection (treaty.rs:208-359) takes a treaty scope and one manifest per participant and folds them into a single record. The record is N-participant, not pairwise. There is no left side and no right side, and no co-signature field:
pub struct LadderIntersection {
pub schema: String,
pub intersection_id: String,
pub treaty_id: String,
pub participant_kernel_ids: Vec<String>,
pub ladder_manifest_sha256s: Vec<String>,
pub generated_at_unix_ms: u64,
pub expires_at_unix_ms: u64,
pub action_classes: Vec<LadderIntersectionActionClass>,
}pub struct LadderIntersectionActionClass {
pub action_class_id: String,
pub mode: String,
pub destructive: bool,
pub consistency_model: String,
pub co_sign: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub co_sign_quorum: Option<GovernanceLadderQuorum>,
pub evidence_required: Vec<String>,
pub participant_modes: BTreeMap<String, String>,
}participant_modes is the field that makes the fold auditable. The intersected mode is one value, and this map preserves what each participant declared before the climb, keyed by kernel id.
The fold runs once per action class in the treaty scope’s allowedActionClasses, and each field has its own rule:
| Field | Rule |
|---|---|
mode | Climbs. The highest rank any participant declared wins (treaty.rs:289-293). |
destructive | ORs. One participant calling the class destructive makes it destructive (:294). |
coSign | Climbs by co_sign_requirement_rank, so n_of_m beats bilateral_required (:305-307). |
coSignQuorum | merge_quorum: m and scope must agree exactly, n takes the maximum, disagreement is chio_federation_ladder_quorum_misdeclared (:925-945). |
evidenceRequired | Unions through a BTreeSet, so every participant’s labels appear once, sorted (:309-311). |
consistencyModel | Does not climb. Unanimity or nothing. |
The consistency rule is the one worth stating carefully, because it is easy to read it as a per-class drop. It is not. The refusal is a return from inside the per-class loop, so a single disagreement on a single class aborts the whole call:
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());
}No intersection is produced minus the offending class. No intersection is produced at all, so no class in the treaty scope is authorised. The four models are substrates rather than intensities: there is no reading under which a CRDT merge and a threshold signature are the same operation performed with different care.
Two things the fold computes rather than copies. The intersectionId is {treatyId}:{nowUnixMs} (:351), so two intersections over the same treaty at different instants are distinct artifacts. The expiresAtUnixMs is the minimum of every manifest’s expiry and the treaty scope’s (:343-348), so the intersection cannot outlive the shortest-lived input.
Two verifier-owned classes
Two action-class ids belong to the verifier rather than to any participant, and they are constants rather than conventions: workflow.grant_issue and workflow.aggregate_publish (chio-attest-buyer-core/src/trust_bundle.rs:28-29). They cover the two workflow-level acts that no pairwise intersection describes: the buyer issuing one grant as the parent of several pairwise vendor intersections, and the buyer publishing a vendor co-signed aggregate workflow receipt as workflow-level evidence.
A trust bundle that omits either is refused when it is assembled: ensure_reference_workflow_classes (chio-federation-authority/src/lib.rs:889-907) collects the declared ids and raises trust bundle action classes must include {required} for the first one missing. The shipped three-vendor trust bundle carries both, at kind: routine (chio-attest-loopback/fixtures/verifier-trust-bundle.json:81-90).
A cross-domain fold, worked
A cybersec operator and a finance operator federate to respond to a compromised payment-issuing agent. Both declare the same action class. Cybersec declares it receipt_backed / n_of_m with a 2-of-3 treaty quorum / quorum-required. Finance declares it receipt_backed / bilateral_required / quorum-required.
The fold produces one class at receipt_backed (equal ranks, no climb), n_of_m (rank 3 beats rank 2), the quorum carried through from the only side that declared one, quorum-required unchanged, and evidenceRequired as the sorted union of both lists plus quorum_signature, which required_evidence_for_action appends because the intersected co-sign is n_of_m. Finance now runs a FROST signing ceremony for a class its own ladder did not require one for. Intersection climbs; it never descends.
Had finance declared totally-ordered instead, there would be no intersection to describe. The call returns chio_federation_ladder_consistency_mismatch and the treaty authorises nothing, including the observation-only classes the two sides agreed on perfectly.
See Treaties for the treaty scope the intersection is folded against and the cross-boundary admission report it feeds, and Bilateral Co-Sign for the DSSE predicate the enforcement runs over.