Repository navigation
ciris-persist v59.0.0
[59.0.0] - 2026-10-19
Security — an aggregate's sources are distinct ids (CIRISPersist#1102, CC 6.1.1 rc9 37494c0, CLM-member-commitment-unique)
- verify-core's
member_commitmentsorts its leaves but does not deduplicate,
so{a, b, c}and{a, b, c, c}share one root, and the descent set
source_count = n_eff = len(sources)with no duplicate check: a padded
list inflated the count the mass-dominance gate reads. A repeated id is now
refused before anything is read or hashed, at the descent
(fountain::descend_aggregated_sources_on_backend, soEngineand Python
descend_aggregated_sources) and at the builder
(fountain::member_commitment, now persist's: it returns
Result<[u8; 32], federation::Error>— a Rust signature change — instead of
re-exporting verify-core's), through ONE check
fountain::check_distinct_sources. The refusal is
federation_malformed_rowwith the new reason
aggregate_source_duplicate(appended to the closed
malformed_row::ALL); on the store path it rides the new
store::Error::MalformedRow(kindfederation_malformed_row, Python
ValueError("federation_malformed_row: aggregate_sources: aggregate_source_duplicate"), the type a rejected aggregation always had).
A distinct list is unchanged. Witness I1152
(tests/aggregation_tier.rs#i1152_member_commitment_unique, memory, sqlite,
postgres).
Security — a key id names its own key (CIRISPersist#1103, CC 2.6.8 rc9 957d1dd, CLM-key-id-fingerprint-verified)
- At the DOOR only — registration (
Engine::register_federation_key, Python
register_federation_key, and a host's raw registration: the capsule's
PutPublicKeyop and Pythonput_public_key) and first contact (akey:record:v1row offered
throughput_attestation/apply_replicated_attestation/
ApplyReplicatedAttestation, the proof-of-possession door) — a key whose
key_iddoes not end in-<fp>is refusedfederation_malformed_rowwith
the appended reasonkey_id_fingerprint_mismatch(Python
ValueError("federation_malformed_row: key_registration: key_id_fingerprint_mismatch")).fpis verify-core fedcode's fingerprint
of the record's OWN raw Ed25519 key (ciris_verify_core::fedcode, via
register::key_id_fingerprint; not re-implemented). One check,
register::check_key_id_fingerprint, after the registration signature
verifies. - Exempt, keyed off the bundle and the families (never a string prefix): an
accord holder or serve node of the pinned genesis bundle, or an accord
holder this build seeds, under the SAME Ed25519 key; the bundle's family id;
a held family's id. - Grandfathered: a key already held under a non-matching id still resolves
and nothing re-keys it, but it is refused when registered or offered at
first contact again under that id.device-<hex>-<fp>passes (the
roster-id form is a separate rule). - Fixtures: the registration matrix, I976/I977 and I1146 now name their keys
withtier_ingest::test_support::fp_key_id(the rule was not loosened).
Witness I1153 (key_id_fingerprint_invariants, memory, sqlite,
postgres; and the registration door on sqlite, postgres).
Added — the owner may author the community-trust capture grant (CIRISPersist#1096, CC 3.3.1 rc9 1a46e03, CLM-consent-community-trust-owner-authored)
consent:community_trust:v1admits an OWNER-authored grant beside the
node's own: attester a live roster key of the owner's fedID at the grant's
asserted_at(self_roster::acts_as_steward_at, with its W bound
check_roster_act_fresh— the one steward predicate, not a second),
attested the node. Anyone else is refused
community_trust_attester_not_owner(InvalidArgument, Python
ValueError, the type this gate always had; it replaces the v52 "a capture
grant is the NODE's own row" refusal).- In BOTH forms
subject_key_idsnames the node, its owner atasserted_at
and the agent key that acts on it (a heldagent-role key); a NEW grant
missing one is refusedcommunity_trust_subjects_incomplete. Each named
party withdraws (rule 2); only the attester recants. - The fold (
fold_community_trust) and the read door
(community_trust_consent_for, no longer filtered to the node's own rows)
group both forms byattested_key_id; the latest grant after the boundary
Rgoverns, and a revocation of any grant — the owner's recant of its own
included — ends every grant at or beforeR. An agent key's withdrawal is a
boundary over grants only; captured traces stand. - Grandfathered: a held node-authored grant in the v52 shape (the owner
alone) keeps counting, its byte-identical re-offer is admitted as held, and
the sqlite / postgres open counts them in one log line
(legacy_community_trust_grants_at_open,
community_trust_consent::is_legacy_node_authored_grant); nothing stored
is refused or rewritten. - Fixtures I950, I536, I538 and I148 now name all three parties. Witness
I1154 (memory, sqlite, postgres; the open-time count on sqlite,
postgres).
Added — the legacy occurrence revocation's carrier row (CIRISPersist#1104, CC 3.1.5 rc9 c901645, CLM-occurrence-revocation-carrier)
identity:occurrence_revocation:v1
(federation::occurrence_revocation_carrier): persist mints it from a held
framed occurrence revocation the way the key plane mints its rows.- Attester is the revoker; the framed object rides under
occurrence_revocation. - The signature columns are the object's own hybrid signature.
- id =
sha256("ciris:identity:occurrence_revocation:v1:" ‖ JCS(object))[..16]. - Score −1, never withdrawn.
- Federation scope: the key plane's global reach, so a stranger holding the
occurrence receives it. - A receiver re-derives the row and refuses a byte difference.
- Domain and rules:
FSD/KEY_PLANE_ROWS.md.
- Attester is the revoker; the framed object rides under
- Admission is structural, never an instant
(check_carrier_admission):- a
nodeoragentidentity's occurrence is admitted; - a user self's is admitted only when cosigned (row
additional_scrubs,
hybrid over the row envelope) by a strict majority of its CURRENT live
roster (self_roster::roster_now, verify-corestrict_majority); - otherwise it is refused
federation_malformed_rowwith the appended
reasoncarrier_without_roster_quorum.
A backdated framed object freshly signed by one key is refused whatever its
instant. An admitted carrier projects the revocation on every node that
admits it.
- a
- Migration at open: every held
legacy_framedrevocation is offered as
its carrier. The rehome report gainscarriers: minted;without_quorum,
a user self's pairs NAMED in the open's log line for the owner to re-propose
through the two-path door; andnot_carried. Nothing held is rewritten. - The one predicate (for CIRISEdge):
key_plane::occurrence_revoked_by(dir, row) -> Option<(identity, occurrence)>
recognises awithdrawsof anidentity:occurrence:v1row and a carrier. - Witness I1155 (memory, sqlite, postgres; the migration on sqlite,
postgres); I1144 asserts its legacy revocation is carried.
Added — the genesis bundle seeds the serve nodes' transit pairs (v4); amend mode (CIRISPersist#1106, CC 5.3.4 rc9 0edac07, CLM-bundle-seeds-transit-pairs)
GENESIS_BUNDLE_VERSION3 → 4. New member
serve_transport_destinations(wire name the same): each seated serve
node's signedSignedTransportDestinationcarrying its transit KEX pair.- It is serde-defaulted and omitted when empty, so a v2 / v3 bundle parses,
re-serializes byte for byte and behaves as before. - From version 4 the member is inside
authorization_digest, each row
projected to{attesting_key_id, signed_envelope}. The v2 / v3 preimage is
byte-identical; the compiled-in v3 bundle's production authorizations
still verify.
- It is serde-defaulted and omitted when empty, so a v2 / v3 bundle parses,
- Bundle verification:
- Done by
genesis::check_serve_transport_destinations, run by
verify_bundle_quorumand at seeding. - Every row must be about and signed by a key in that bundle's
serve_nodes(hybrid overJCS(signed_envelope)against the record's
keys), carry a transit pair, and pass the separation rule. - Any failure fails the WHOLE bundle with
genesis_serve_transport_destination_invalid. A v3 bundle carrying
rows is refused.
- Done by
- Seeding at first open:
genesis::seed_serve_transport_destinationsruns after the serve nodes'
keys are seeded.- It goes through the ORDINARY signed transport door, so the row lands
Rootedandself_at_login::transit_kex_pair_for(serve_node)returns it
on a fresh node with no network.
- The ceremony carries
CeremonyInputs::serve_transport_destinations
(each seated serve node's current signed row, gathered by the caller) and
an optionalbundle_version. The test ceremony mints v4 with the test
node's row. - Server must re-mint the pinned bundle. The baked
canonical_seed.json
stays v3 and seeds no pair until then. - The AMEND ceremony (
genesis::amend::AmendState):- It verifies the pinned bundle with the existing doors, then carries
holders, serve nodes, consensus protocol, rows, roster records and
produced_atbyte for byte. - It adds the member, and its one signing item is the authorization over the
new digest. It finishes atmax(charter M, strict_majority(roster))
DISTINCT holder authorizations, then runsverify_ceremony_outputs. - Refusals:
genesis_amend_carried_bytes_changed,
genesis_amend_quorum_not_met,genesis_amend_input_refused. verify_ceremony_outputsstill requires every holder's signature on the
accord FAMILY RECORD and every founder's on the community birth. Both are
carried bytes from the original unanimous ceremony, so an amend satisfies
them without re-signing. No door requires every holder's authorization.
- It verifies the pinned bundle with the existing doors, then carries
- A serve node's pair rotates by holder quorum (CC 5.3.4, 3.3.6.2):
- The holders' re-bind is an attestation row
transport:canonical_rebind:v1
(persist-named) about the serve node. It carries the node's new signed row,
is authored by a holder and cosigned inadditional_scrubs. - It is admitted at the accord charter's threshold
(trust_root::family_quorum_over), else
transport_rebind_without_holder_quorum. - On admission it applies the carried row through the transport door, where
it supersedes the bundle-seeded binding. - A UNILATERAL rotation that would retire a seated serve node's
bundle-pinned pair before a replacement is Rooted is refused
serve_transit_pair_rotation_before_rebind
(transport_rebind::check_serve_rotation, in every backend's transport
door). - Resolver rule: one live binding per
(node, kind); the re-bind supersedes
the seeded row in that slot, sotransit_kex_pair_fornever sees both. - "Seated" is read from the pinned bundle.
- A pair rotation needs this re-bind or a re-mint.
- The holders' re-bind is an attestation row
self_at_login::check_transit_kex_pair(pair: &TransitKexPair, other_keys: &TransitKexOtherKeys) -> Result<(), TransitKexRefusal>is THE
separation rule, pure and exported. The admission's in-row check, the
resolver and the bundle check call it.TransitKexOtherKeystypes the four groups, so a caller cannot mislabel
one:- (a)
federation_signing: the node's Ed25519 and ML-DSA-65; - (b)
transport_identity: Reticulum X25519 and Ed25519 from the
occurrence binding and every live transport binding; - (c)
encryption: the occurrence'sencryption_pubkeys; - (d)
bound_identity_signing: each bound identity's federation keys.
- (a)
- A caller holding only
key_id, its transport identity and its federation
key passes (a) + (b). Omitting (c) + (d) weakens only the cross-row half,
which the stored check (transit_kex_pair_for) closes. TransitKexRefusaltokens:transit_kex_x25519_reuse,
transit_kex_ml_kem_768_reuse.
- Witnesses (
tests/test_ceremony_1106.rs, featuretest-anchor; memory,
sqlite, postgres):- I1157: offline Rooted pair; non-serve signer, no pair and a swapped
row refused; v3 verifies and seeds none; the baked v3 still verifies. - I1158: rotation by quorum.
- I1159
i1159_amend_is_two_of_three_for_every_holder_pair: A+B, B+C
and A+C each boot; 1-of-3 refused; a carried byte refused.
- I1157: offline Rooted pair; non-serve signer, no pair and a swapped
Changed — typed refusals CIRISEdge asked for (CIRISPersist#1064)
- Pending vs refused through the replication apply.
AttestationRefusalReason
gains the APPENDED armSelfRosterPending(tokenself_roster_pending):
a roster change the self-roster door holds as an open proposal. A refused
one staysKeyPlaneRefused.- New
AttestationRefusalReason::is_retryable():truefor
SelfRosterPendingandStoreConflict. - New
AttestationRefusalReason::of_self_roster_decision(decision). - This is an enum change inside
ReplicatedAttestationOutcome, so
DIRECTORY_ABI_VERSION9 → 10. TheDirectoryOp/DirectoryOpResult
structural digests are unchanged (they pin the top-level variants):
784929785eec65791e29a7c00cc24914f9f1751ad4e098d3e04b550e86eec931and
2295b94a42f9da5c7d758c6a0764da0fca8953aaf408dcfd6d672f13a9eff827.
- New
replication_policy::ENVELOPE_KIND_RETIREDexports the
federation_envelope_kind_retiredtoken, andkind()returns it.Error::AssertedAtStale { author, steward, asserted_at, w_secs }:
asserted_at_staleis typed out ofInvalidArgument.kind()staysfederation_invalid_argument, and Python still raises
ValueError.- The Display still leads with
asserted_at_stale
(self_roster::ASSERTED_AT_STALE).
Error::SignerDoesNotActForgainsreason: SignerNotActingReason:
NotBound(retryable) orGenesisEvicted(terminal: the self evicted its
genesis key through the two paths).- The reason has
as_str()(not_bound/genesis_evicted) and
is_retryable(). kind()is unchanged. A Rust consumer that matches the variant with all
its fields named must add..orreason.
- The reason has
- Witness I1156 (memory, sqlite, postgres). I1141 asserts
GenesisEvicted
and I1142 asserts the typed stale variant. Mutations collapsing either
split are red.
Added — the two-path decision (CIRISPersist#1099, CC 3.3.6.3, CLM-self-roster-two-paths)
federation::two_path— ONE pure, backend-free decision for "one actor
proposes, a window, the standing set decides":decide/decide_all→
TwoPathDecision::{Lands { path: quorum | lone }, Pending { earliest_lone_at, needs }, Refused { reason }}. Quorum: a strict majority of the live roster
at the decision instant, proposer counted (strict_majorityfrom
verify-core; no threshold is spelled). Lone: the window elapsed on THIS
node's clock (from the instant the node first held the proposal, never the
signer'sasserted_at), no strict majority of the other members objects,
and for an eviction the target signed nothing since the proposal arrived.
A vote from a key off the roster is void; one open proposal per proposer
(never a global cap); one open proposal of a kind per target. Refusal
tokens:proposer_not_on_roster,target_already_on_roster,
target_not_on_roster,proposer_is_target,proposer_has_open_proposal,
target_has_open_proposal.- The policy is data (steward rulings 2026-10-18; CC rc9
formal/two_path
PolicyCorridor.lean,TwoPath.tla,tools/build_governance_policy.py;
CIRISPersist#1100, CIRISConstitution#180):TwoPathPolicy { base, enact, cancel, cancel_excludes_proposer, floor, floor_class, reverse_path, at_window, irreversible, window_secs, silence, one_open_per_proposer, one_open_per_target }.Threshold::{StrictMajority, Fraction { num, den, cmp: MoreThan | AtLeast }}resolved overBase::StandingRoster(Base::count;
a future expected-active base is data). CANCEL and FLOOR are lifted to the
table's count minimum (FloorClass::Voters2,SameIdentity1); ENACT keeps
its own rule.decide_countsis the governance decider: ENACT lands at once
unless irreversible; before the window, pending; at the window, CANCEL
reached ⇒Cancelled { reason: cancel_reached }, ENACT (irreversible) ⇒
lands, a floored silent reverse path ⇒ lands, elseCancelled { reason: below_floor | no_reverse_path | target_spoke }(AtWindow::Cancel) — the
floor is consulted only at the window.check_corridor(base_n, family_floor_secs)isinCorridor, refusingenact_by_single_voter,
cancel_needs_everyone,cancel_above_enact,enact_below_strict_majority,
floor_above_enact,cancel_by_single_voter,enact_needs_everyone,
floor_below_two_voters(not forSameIdentity),window_below_floor. cancel_over: CancelOver::{Base, Others}(CC rc92643ff1; table re-pinnedb7d2370dat69fd22e):
every community row cancels over ENACT's base, proposer counted; the self
roster over the others. A self-roster proposal its proposer withdraws (a
withdrawsagainst itsobjection:proposal:v1row) leaves the decision and
settleswithdrawn(I1143). The halt'slapsedand moderation'stallied
window outcomes are not built: neither door is in v59 and no vector reaches
them.- The self roster is
TwoPathPolicy::self_roster(86 400): strict majority of
the roster forward, of the others reverse, floor0/1lifted to one key
(SameIdentity), reversible,AtWindow::StayOpen(asSelfRoster.tla) —
its door is unchanged. In its own corridor at one and three keys; at two,
cancel_needs_everyone(the stated two-key bound). - CC's 732 governance policy vectors (rc9
69fd22e,policy_sha256 b7d2370d…, vendored as TEST fixtures undertests/fixtures/governance_policy/)
all replay through the struct: thresholds, verdict, path and cancel reason.
216 of them carry the provisional expected-active base (N = 10 000 → 344),
which is not built; they take their stated base count as given. No
community default or window floor is built beyond a test's named example. - The six CC
formal/self_rostertraces (rc9cb0513c8) are vendored under
tests/fixtures/self_roster/and replayed as door tests: everyExecuteof
combined3_stolen,combined3_lostandcombined4_mixedlands (by the path
the trace implies);unilateral3_stolen's finalExecute(the thief's lone
eviction at age 0) does not, and once the owner answers it never does;
combined1_stolen(one key) andcombined2_stolen(two keys, stalemate) are
pinned as the stated bounds. memberis the live roster at the DECISION instant, as the model's is at
Execute:combined4_mixedlands its second eviction by two votes of a
roster the first eviction shrank to three.
Changed — the self-key roster door is two-path, not unilateral (CIRISPersist#1099, CC 3.3.6.3, CLM-self-roster-two-paths; a defect, formal/self_roster unilateral3_stolen)
federation::self_roster— a self is auser-role fedID (a node or agent
identity's occurrences are not a roster); its roster is its genesis key plus every
ACTIVEidentity_occurrenceunder it whose key isuser-role (a node's own
key and an agent's key are not roster keys; their occurrences are KEM
targets, written as before). An ADDITION (a signed occurrence of a user-role
key not on the roster) or an EVICTION (awithdrawsagainst the pair's
identity:occurrence:v1row, of a live roster key) signed by one key of a roster of TWO OR MORE is a proposal
decided bytwo_path::decide_allunderW_SELF_SECS = 86 400with target
silence for evictions. At one key the addition still lands at once (the
add-device approval is unchanged). A node's own key is not a roster key: a
node its owner bound still publishes its own occurrence, but its signature
on a user-role key's occurrence is refusedproposer_not_on_roster(I1140).- Witnesses I1130–I1140 on memory, sqlite and postgres, each door condition
with a mutation that turns it red. - Every door that can change a roster asks
self_roster::gateafter its
signature gate and before any write:put_identity_occurrenceon memory, sqlite and postgres, and the attestation
door for an occurrencewithdraws(v59's sealed-transport cut removed the
framed revocation door; see Removed below). A change that lands proceeds
exactly as before. One that does not is HELD and the door returns the new
Error::SelfRosterNotLanded(kind()federation_self_roster_not_landed,
PythonValueError("federation_self_roster_not_landed: <decision>: <proposal_id>")),
carryingidentity_key_id,target_key_id,kind,proposal_id,
decision(pendingor a refusal token),earliest_lone_at,needs.
Nothing on the roster changed. Throughapply_replicated_attestationthe
same outcome isRefused { reason: key_plane_refused }(no new token). - The rows. A held change is an
objection:proposal:v1row (proposer →
fedID) minted from the signed object it carries (roster_proposal:
{identity_key_id, kind, target_key_id, carrier, <carrier>}, carrier ∈
identity_occurrence|withdraws|identity_occurrence_revocation); its
signature is the object's own and its idsha256("ciris:objection:proposal:v1:" ‖ JCS(object)), so every node names the same proposal. Answers are
scoresrows a roster key signs, attested to the fedID:
objection:assent:v1/objection:raised:v1withcohort: "self",
cohort_key_id,objects_to_attestation_id(self_roster::vote_envelope).
proposalandassentare new states of CC 3.1.9.2'sobjection:{state},
reported to CC for registration. Any signed act by the target closes the
lone path; a key that both assents and objects counts as an objector; a vote
from a key off the roster is void. - Execution. A held proposal lands by itself: an admitted proposal or
answer row runsself_roster::execute_forfor its self, and the sweep
execute_due(PythonEngine.execute_self_roster_due()) lands what the lone
path allows once the window closes. The executor re-offers the SAME signed
object through the SAME door, which now lets it through — the write path is
not spelled twice.key_plane::minted_rowis the one builder of the key
plane's rows and the proposal row. - V199
federation_self_roster_proposals(sqlite + postgres twins; memory
mirror): this node's first-seen instant per proposal (the window's start, on
the node's clock — the purge-dwell precedent) and its settlement. Node-local;
never hashed or served. Three trait methods (note_self_roster_proposal,
list_open_self_roster_proposals,settle_self_roster_proposal), defaulted
Unsupported, overridden by every backend and delegated by the double. - Python:
Engine.self_roster_standing(identity_key_id)(every open proposal,
decided now),Engine.execute_self_roster_due(),
Engine.self_roster_vote_envelope(identity_key_id, proposal_id, assent). - What a peer sees: a pending proposal is not on the
identity:occurrence:v1
row stream (and, since the sealed-transport cut below, there is no framed
carriage); once it lands it is served as a row. A v58 peer receives the
proposal row and refuses it (its signature is the carried object's). - Staged, not refused: the
device-<hex>-<fp>roster key id
(self_roster::DEVICE_DERIVED_ROSTER_KEY_PREFIX,CLM-self-roster-key-id)
waits for CIRISServer#793 to ship<fedID>-<fp>. - The genesis key is evictable (CC 3.3.6.3, rc9
69fd22e): an
identity-occurrence revocation of the pair(fedID, fedID)by ANOTHER live
roster key — awithdrawsagainst the self pair'sidentity:occurrence:v1
row (the transport self-occurrence) — is a roster eviction through the two
paths. It counts only once it LANDED: this node's door settled its proposal
lands_quorum/lands_lone(V199;list_settled_self_roster_proposals,
indexed by V201). Once it lands the genesis key leaves the roster,
signer_acts_forno longer lets it act for the self, and it is no steward;
the fedID string keeps naming the self and its other keys act as it
(self_roster::genesis_evicted_at, terminal: a later assertion does not
re-establish it); its key record stays held and valid, so every row the key
signed while live still verifies. The genesis key's ownwithdrawsof that
row retires its transport self-occurrence and evicts nothing; awithdraws
of itskey:record:v1row evicts nothing; a revocation of the pair admitted
before the door (a v58withdraws, or a framed one keptlegacy_framed)
evicts nothing (I1141, I1148). - Not carried in this cut: re-asserting a live key's occurrence by another
roster key (not a roster change; stayssigner_acts_for).
Changed — a live roster key acts as its self's steward (CIRISPersist#1099, CIRISServer#793, CC 3.3.6.3, CLM-self-roster-authority)
- ONE predicate,
self_roster::acts_as_steward_at(steward, author, at): the
steward itself, or a live roster key of the steward's self at the row's
signedasserted_at. The consent fold's steward check
(grant_is_authored_for, and through itconsent_peers_by_principals, now
read over the live grants themselves) and the one-responsible-steward gate
(check_single_node_owner_admission) ask it, andowner_ofnames each
owner-binding's granter as the self it signed as (self_of_at), so a second
device restating its owner's binding is the same one steward. A key of
another self is refused at both. - The predicate's instant is bounded at admission by W (CIRISPersist#1049, CC
2.6.7, ruled 72 h;self_roster::PAST_FRESHNESS_W_SECS): a row admitted on a
roster key's standing at its signedasserted_at— afor_key_idconsent
row, an owner-binding — by a key that is NOT a live roster key of that self
at receipt (evicted or revoked since) is refusedasserted_at_stalewhen that
instant is more than W behind this node's clock, so an evicted key cannot
sign into the window in which it was still live (I1142). A key that is live
now is admitted at any age, so a new device or node syncing a self's history
admits its old rows (I1149; CC 3.3.6.3 rc969fd22e). A steward's own rows
are not bounded here.
Fold survey — the two-path decision's relatives (CIRISPersist#1099 ruling 3)
Nothing was folded; each site's thresholds differ, so a move would not be
behaviour-preserving:
reverse_quorum.rs(act-unless-objected) — differs because the act lands
ON ARRIVAL and is reversed by objections (no pending state); the reverse
threshold is the cohort's declaredmcapped at the roster, the brake is
1-of-N, and the window is keyed on the action's signedasserted_at.ownership_reclaim.rs(a dwell with a challenge) — differs because the
authority is a Wise-Authority quorum FINDING, a separate body, over a
four-step ceremony with a published 180-day (90-day floor) abandonment
window; there is no proposer, no lone path and no roster vote.- The accord halt confirmation within the fuse — differs because the tally is
verify-core'saccord_live_quorum, run by CIRISServer; persist stores the
participations and the frozenAccordDecisionand decides nothing. consensus.rs— differs because it evaluates a group's DECLARED
consensus_protocol(quorum:M/N, unanimous, weighted, custom,
reverse_quorum) over co-signatures on one row at one instant: no window, no
pending state, and a declaredMis notstrict_majority.
Removed — the framed key-plane compat reads (sealed-transport floor, operator ruling 2026-10-19)
CC 5.3.3.5 E1 option (i) puts every RNS byte under a per-link hybrid seal with
no classical fallback, enforced at once, so an unsealed pre-v58 peer is
unreachable. v58's compat reads served exactly those peers; v59 drops them.
Edge adopts v59 only in its sealed cut.
- Removed from the
FederationDirectorytrait, the memory / sqlite /
postgres backends, the directory double, the capsule and the Python surface
(and its.pyi):list_signed_key_records_since,
list_signed_revocations_since,list_signed_identity_occurrences_since,
list_signed_identity_occurrence_revocations_since,
list_signed_identity_occurrence_revocations_for,
put_identity_occurrence_revocation, the types
SignedIdentityOccurrenceRevocationandServedIdentityOccurrenceRevocation,
admission::verify_signed_identity_occurrence_revocation, and the framed
wire-index entries, reloads and refs of the four kinds (Key,Revocation,
IdentityOccurrence,IdentityOccurrenceRevocation; the four
EnvelopeKinds stay in the hashed vocabulary). Capsule ops
ListSignedKeyRecordsSince,ListSignedRevocationsSince,
ListSignedIdentityOccurrencesSince,
ListSignedIdentityOccurrenceRevocationsSinceand their results are gone.
The v31.1.0 boot backfill of the framedRevocationindex kind is gone with
it. The v58 mixed-fleet authoring rule (the framed occurrence revocation
beside thewithdrawsroute) is retired. - Refused inbound: the framed key-record replication door
(ApplyReplicatedKeyRecordop,Engine::apply_replicated_key_record, the
Pythonapply_replicated_key_record) and thePutIdentityOccurrenceRevocation
op (its payload now raw JSON) answer the new terminal
Error::EnvelopeKindRetired—kind()federation_envelope_kind_retired,
Displayfederation_envelope_kind_retired: <kind> …with<kind>one of
key,revocation,identity_occurrence,identity_occurrence_revocation
(replication_policy::retired_detail; general, so any kind retired later
reuses it); PythonValueError, the type those doors' refusals had. A key
replicates as itskey:record:v1row (ApplyReplicatedAttestation). The
trait methodapply_replicated_key_recordstays as thekey:record:v1row
door's executor, not a wire door. - Canonical occurrence-revocation doors: a user-role self's roster key is
evicted through the two-path proposal (awithdrawsagainst its
identity:occurrence:v1row); a node or agent occurrence is a single-signer
withdrawson its row. DIRECTORY_ABI_VERSION8 → 9 (ops removed, one payload retyped).
DirectoryOpdigest784929785eec65791e29a7c00cc24914f9f1751ad4e098d3e04b550e86eec931,
DirectoryOpResultdigest2295b94a42f9da5c7d758c6a0764da0fca8953aaf408dcfd6d672f13a9eff827.- Migration (one-way, at first open): the v58 lazy rehome is made eager —
run_migrationsends withrehome_key_plane_at_openon sqlite and postgres,
so every v57-shape key record, key revocation and signed occurrence that
verifies has its key-plane row before anything reads or serves. A record that
does not verify is held without a row (as v58) and counted in the one log
line the open writes (KeyPlaneRehomeReport). V200 marks each framed
occurrence revocation already held aslegacy_framed: it cannot be re-signed
as awithdraws, so its projection row stays; the occurrence fold keeps
reading it as revoked, and nothing un-revokes it. - Witnesses I1144 (a V195 store with keys, a key revocation, occurrences, a
route and a framed occurrence revocation migrates forward: every object is a
row or a fold, no key or route lost, the legacy-revoked occurrence stays
revoked; sqlite + postgres), I1145 (the framed reads refuse or resolve
nothing; every backend), I1146 (the capsule's retired ops answer the token),
I1147 (from disk: the removed symbols stay gone; carries v58's I1123). The
v58 compat witnesses I1120–I1123 and the framed-cursor witnesses
(exercise_key_mutation_moves_serve_position,
exercise_late_replicated_key_record_is_served, the #668/#682/#655 cursor
pins) are removed with what they measured; I980 now asserts the rehome at
open. - The genesis key's eviction (#1099 above) moves off the framed revocation: it
is awithdrawsagainst the self pair(fedID, fedID)'s
identity:occurrence:v1row, signed by another live roster key and landed
through the two paths. - The replication path takes in no framed object: a peer's advertisement
of a hash of a retired kind (insert_known_wire_hash, the wire-index ingest)
is refusedfederation_envelope_kind_retired: <detail>
(replication_policy::RETIRED_FRAMED,refuse_retired_framed), so no
framed body is fetched to be re-authored locally as a row. The authoring
doorsput_revocation/put_identity_occurrence(and the capsule's
PutRevocation/PutIdentityOccurrence) stay: their objects become rows.
persist runs no sync loop of its own that routes a framedRevocationor
IdentityOccurrenceinto them; a host's loop must not (I1150). The trait
methodapply_replicated_key_recordstays as thekey:record:v1row door's
executor; every external entry to it (capsule op, Engine, Python) refuses.