feat: gossiped coordinator election; remove the federation link protocol - #12
Merged
Mearman merged 11 commits intoSep 10, 2026
Merged
Conversation
…frames from the union coordinator-frame lets any reachable peer claim the introduction/rendezvous role via a monotonically increasing term (a higher term always supersedes a lower one, equal terms break by lowest device-id), giving that role the same crash-recovery property a port-race coordinator already has without relying on OS-level port contention. Also documents relay-connect-frame's own access-control gap: it carries no token field, so a gated relay enforces access via an ordinary core/management verb rather than the frame's own shape. The six federation-*-frame variants are removed from $frame-variant in the same edit, since frame.cddl's socket list is one file; the reasoning for removing them lives in spec/federation.cddl (next commit).
…place federation-link-request/accept/reject, share-descriptor, and federation-envelope-frame conflated two problems already solved elsewhere: reachability between principals with no prior relationship is discovery.cddl's job (DNS, an out-of-band record, or local broadcast), and authorisation once connected is tokens.cddl's job (signed, delegatable, narrowing-only capability tokens). A federation-link-accept and a share-descriptor added no security property a token scoped to the resource in question, handed to a bearer outside the issuer's own group, didn't already provide -- the token's own signature already is the explicit, auditable act of agreement the link-negotiation frames were redundantly restating. Cross-scope sharing now works the same way every other capability grant does: mint a token scoped to the resource, hand it to whoever needs it. The file keeps this reasoning as prose rather than being deleted, so a reader looking for the mechanism that used to live here finds why it's gone rather than nothing.
Per the registry's own append-only convention: the string stays reserved (never renumbered or reused for something else), a peer must never advertise or negotiate it, and the registry table records why now that federation.cddl defines no frames for it.
Ownership (a person's, team's, or org's devices) needed no new mechanism: kind: "group" is an ordinary capability-scope, and membership is an ordinary delegated grant through the same token chain every other scope already uses. What would colloquially be called "a mesh" isn't a distinct kind either -- it's simply the largest group a set of devices happens to share, with no wire-level membership list or identifier for it. Also cross-references management.cddl's revocation chain-check rule from the parent field's own comment: a verifier walking a delegation chain must check every ancestor's token-id against the revocation view, not only the leaf's.
revocation-entry was a bare, unsigned {token-id, revoked-at} pair -- any peer could falsely announce any other peer's valid token as revoked, with no attribution at all. It's now a cose-sign1 over revocation-claims (token-id, issuer, issuer-key, revoked-at), the same self-certifying pattern already used for capability-token and handle-record. A verifier must check revocation-claims.issuer against the token's own issuer field, not merely that some signature verifies -- only a token's own issuer may revoke it.
Bare public-key identity with no CA means losing a device's private key permanently orphans everything rooted at it -- no new tokens or revocations can ever be issued under that identity again. Adding rotation/recovery machinery to the wire protocol would be real scope this deliberately minimal identity layer doesn't take on; the mitigation is operational (pre-delegate a recovery-scoped token to an offline backup identity ahead of time), the same way an offline root-CA backup key is operational practice rather than something X.509 itself specifies.
A peer's own log lives on exactly one device until some other peer independently replicates it -- no mandated minimum replication factor, since how much redundancy is worth the cost is a deployment policy question, not a protocol one. A peer wanting durability for its own data can proactively push to its own mailboxes (discovery.cddl) rather than waiting to be replicated, the same fan-out mechanism this file already defines, just owner-initiated. Also updates the one cross-reference to discovery.cddl's mailbox field, renamed to mailboxes in that file's own commit.
…mailboxes DNS was implicitly the only fallback path. Names two more explicitly -- an out-of-band-delivered handle-record, and local broadcast (mDNS/Bonjour) -- each fitting a different reachability situation and none substituting for the others; every channel delivers the same self-certifying record, verified identically regardless of which one carried it. Documents the real limitation this doesn't paper over: a device discovered only via local broadcast has no durable roaming story once it leaves that network segment, unless it also published a DNS handle or a mailbox-replicated record elsewhere. mailbox: device-id becomes mailboxes: [* device-id], matching candidates and relay-offer-frame's own addresses -- holding a replica was never inherently single-device, and naming more than one gives redundancy the singular field couldn't express. The handle-registry fallback (publishing a handle-claims record as an ordinary core/data entry) drops its federation-link dependency: resolution is now gated by an ordinary capability token scoped to the registry, delegated through however many hops trace back to the owner's root identity, not membership in a federation-specific relationship.
…ation model Comparison table, "what the protocol specifies" list, and the Tailscale-alternative section now describe coordinator-frame's gossiped election and capability-token-based cross-scope sharing instead of the removed mesh-id/federation-link mechanism. Adds a design-philosophy paragraph naming the actual test applied throughout this change (does a proposed concept do genuine work identity, tokens, and connectivity/discovery don't already cover, or is it a bespoke wrapper around some combination of those three), so the next proposed domain or frame type has a stated bar to check against rather than needing to rediscover it. Also documents the cloudflare-hub package's two independent roles (relay, announcer) concretely.
Removes the six federation_*_v1 vectors (the frames no longer exist). Adds coordinator_v1_with_capacity_hint. revocation_announce_v1_two_entries' entries are now each a signed cose-sign1 over revocation-claims rather than a bare, unsigned struct, matching the fix in spec/management.cddl. handshake_v1_management_exec_federation is renamed and no longer negotiates the retired core/federation domain. handle_record_v1_dns_anchored's handle-claims now exercises the pluralised mailboxes field. All 26 vectors round-trip byte-exactly (conformance/verify.test.ts).
Mearman
marked this pull request as ready for review
September 10, 2026 10:17
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Mearman
deleted the
feat/coordinator-election-and-simplified-federation
branch
September 10, 2026 12:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related simplifications to the wire protocol, arrived at together:
Coordinator election —
coordinator-framelets any reachable peer claim the introduction/rendezvous role via a monotonically increasing term (higher term wins, equal terms break by lowest device-id), giving that role the same crash-recovery property a port-race coordinator already has without relying on OS-level port contention.Federation, removed — the dedicated
federation-link-request/accept/reject,share-descriptor, andfederation-envelope-framemechanism conflated two problems already solved elsewhere in the spec: reachability between principals with no prior relationship is discovery.cddl's job, and authorisation once connected is tokens.cddl's job. A federation-link-accept and a share-descriptor added no security property a capability token scoped to the resource, handed to a bearer outside the issuer's own group, didn't already provide — the token's own signature already is the explicit act of agreement the link-negotiation frames were redundantly restating. Cross-scope sharing now works the same way every other capability grant does.core/federationis marked retired in the domain registry (the string stays reserved, never reused, per the registry's own append-only convention).Also, three real gaps found and fixed along the way:
revocation-entrywas a bare, unsigned{token-id, revoked-at}pair — any peer could falsely announce any other peer's token as revoked with no attribution. It's now acose-sign1overrevocation-claims, the same self-certifying pattern already used forcapability-token/handle-record; a verifier must check the claim's issuer against the token's own issuer.discovery.cddl's handle-recordmailbox: device-idbecomesmailboxes: [* device-id], matchingcandidatesandrelay-offer-frame's ownaddresses— holding a replica was never inherently single-device.spec/identity.cddlandspec/data-domain.cddlgain documentation-only additions (no key-rotation/recovery is deliberate; durability is opportunistic by design) — no wire shape changes there, both were pre-existing properties made explicit.Conformance: 26/26 vectors round-trip byte-exactly (
conformance/verify.test.ts) — the six removedfederation_*vectors are gone,coordinator_v1_with_capacity_hintis new,revocation_announce_v1_two_entriesandhandle_record_v1_dns_anchoredwere regenerated against the changed shapes.just specvalidates the regeneratedprotocol.cddlagainst a real RFC 8610 parser;just conformancepasses clean.README updated to match (comparison table, "what the protocol specifies" list, a new design-philosophy paragraph naming the actual test applied here — does a proposed concept do genuine work identity/tokens/discovery don't already cover, or is it a bespoke wrapper around some combination of those three — and a concrete cloudflare-hub relay/announcer walkthrough).