Skip to content

feat: gossiped coordinator election; remove the federation link protocol - #12

Merged
Mearman merged 11 commits into
mainfrom
feat/coordinator-election-and-simplified-federation
Sep 10, 2026
Merged

feat: gossiped coordinator election; remove the federation link protocol#12
Mearman merged 11 commits into
mainfrom
feat/coordinator-election-and-simplified-federation

Conversation

@Mearman

@Mearman Mearman commented Sep 10, 2026

Copy link
Copy Markdown
Member

Two related simplifications to the wire protocol, arrived at together:

Coordinator electioncoordinator-frame lets 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, and federation-envelope-frame mechanism 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/federation is 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-entry was 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 a cose-sign1 over revocation-claims, the same self-certifying pattern already used for capability-token/handle-record; a verifier must check the claim's issuer against the token's own issuer.
  • discovery.cddl's handle-record mailbox: device-id becomes mailboxes: [* device-id], matching candidates and relay-offer-frame's own addresses — holding a replica was never inherently single-device.
  • Handle discovery now names three bootstrap channels explicitly (DNS, an out-of-band-delivered record, local broadcast/mDNS) instead of implying DNS was the only fallback, with the real roaming limitation of local-broadcast-only discovery documented rather than left implicit.

spec/identity.cddl and spec/data-domain.cddl gain 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 removed federation_* vectors are gone, coordinator_v1_with_capacity_hint is new, revocation_announce_v1_two_entries and handle_record_v1_dns_anchored were regenerated against the changed shapes. just spec validates the regenerated protocol.cddl against a real RFC 8610 parser; just conformance passes 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).

…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
Mearman marked this pull request as ready for review September 10, 2026 10:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-10T10:27:23.562385Z b6ef571 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Mearman
Mearman merged commit 8b48137 into main Sep 10, 2026
3 checks passed
@Mearman
Mearman deleted the feat/coordinator-election-and-simplified-federation branch September 10, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant