OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room commits / fac8e90

signer-presence: leak-free single-signer election (S1 core)
Replaces the __lease__{holder,epoch,ts} heartbeat, which wrote a fresh wall-clock ts to the append-only autobase log every ~8s forever (~20MB/day, unreclaimable). Research (keet + autobase + a full hyper-fleeky sweep) converged: liveness belongs on the transport, never in the log — keet and every other holepunch app signal it ephemerally over the connection. This is that idiom: the active signer is a deterministic function of who is CONNECTED right now (lowest-key effective co-owner), computed in RAM, appending nothing. A grace window gives blip-tolerance / the old TTL failover timing. Pure + clock-injectable module; not yet wired. Fork-safety in the connected case is preserved (all peers compute the same signer → one signs); partitions diverge and reconcile on merge exactly as the old lease did (the epoch never prevented that). Test (signer-presence): 16/16 — election, grace, cascade failover, zero-writes. Next (separate careful pass): the presence handshake on the StateBase swarm (connection→member-key, keet's getMemberBySwarmId shape / reuse serve-membrane's announce-push) + swap the sign loop's renewOrClaimLease → holds(), with an offline multi-device failover test before any deploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fac8e906359bac7c951c5ce5f448516558d3f625
by plankatron <fleeky@gmail.com> · 2026-07-18T22:21:47.000Z

Changed files (2) +149 -0

lib/signer-presence.js +63 -0
……@@ -0,0 +1,63 @@
1// SignerPresence — leak-free single-active-signer election from live CONNECTION PRESENCE.
2//
3// Replaces the `__lease__ {holder,epoch,ts}` heartbeat (lease.js), which wrote a fresh wall-clock ts to the
4// APPEND-ONLY autobase log every ~8s forever (~20MB/day, unreclaimable — see the S1 research). Keet, and
5// every other holepunch app, signals liveness EPHEMERALLY over the connection and never persists it. This is
6// that idiom: "which device signs" is a deterministic function of who is CONNECTED right now, computed in
7// RAM. It appends NOTHING.
8//
9// Election = the lowest-keyed device among the currently-effective co-owners (a stable, replication-free
10// tiebreak every connected peer computes identically → they agree on one signer → only one signs → no fork,
11// exactly the guarantee the ts-lease gave in the connected case). A partition still diverges and reconciles
12// on merge — identical to the old lease, which also forked on partition (the epoch never prevented that).
13//
14// GRACE window = blip tolerance: a departed holder stays "effective" for graceMs before failover, so a brief
15// disconnect does not thrash the signer (mirrors the old lease's "never steal a live lease for TTL"). Default
16// graceMs matches the old ttlMs (15s) so failover timing is unchanged.
17//
18// Presence is fed by the transport (a co-owner presence handshake on the StateBase swarm): markPresent on
19// connect+identify, markAbsent on close. This module is pure + clock-injectable → unit-testable with no swarm.
20
21export class SignerPresence {
22 // self: this device's member key (hex). graceMs: how long a departed co-owner stays eligible (blip
23 // tolerance / failover delay). clock: injectable now() for deterministic tests.
24 constructor ({ self, graceMs = 15000, clock = () => Date.now() } = {}) {
25 if (!self) throw new Error('SignerPresence: self (this device member key) required')
26 this.self = String(self)
27 this.graceMs = graceMs
28 this.clock = clock
29 this._present = new Map() // memberKey -> lastSeen: Infinity = connected now; a number = ms it departed
30 this._present.set(this.self, Infinity) // we are always present to ourselves
31 }
32
33 // A co-owner (holds the signing key + is attested) is connected. Idempotent.
34 markPresent (memberKey) { this._present.set(String(memberKey), Infinity) }
35
36 // A co-owner disconnected — start its grace countdown (do NOT drop it immediately; a blip must not fail
37 // the signer over). No-op for a key we never saw.
38 markAbsent (memberKey) {
39 const k = String(memberKey)
40 if (this._present.has(k) && this._present.get(k) === Infinity) this._present.set(k, this.clock())
41 }
42
43 // The co-owners eligible to sign right now: connected, or departed within graceMs. Sorted (stable pick).
44 effective (now = this.clock()) {
45 const out = []
46 for (const [k, seen] of this._present) if (seen === Infinity || (now - seen) < this.graceMs) out.push(k)
47 return out.sort()
48 }
49
50 // The elected signer = lowest-keyed effective co-owner (every connected peer computes the same one).
51 currentSigner (now = this.clock()) {
52 const e = this.effective(now)
53 return e.length ? e[0] : null
54 }
55
56 // Do WE hold the signer role right now? (Drop-in for lease.holds() / renewOrClaim() in the sign loop.)
57 holds (now = this.clock()) { return this.currentSigner(now) === this.self }
58
59 // Diagnostics snapshot (no writes).
60 state (now = this.clock()) {
61 return { self: this.self, signer: this.currentSigner(now), effective: this.effective(now), holds: this.holds(now) }
62 }
63}
test/signer-presence.test.mjs +86 -0
……@@ -0,0 +1,86 @@
1// SignerPresence (S1) — leak-free single-signer election from connection presence. Pure logic + injectable
2// clock, so this runs anywhere (node/bare) with no swarm. Proves: lowest-key election, grace blip-tolerance,
3// cascade failover, self-election, and — structurally — that it can never write to the append-only log.
4// Run: `node test/signer-presence.test.mjs`
5import { SignerPresence } from '../lib/signer-presence.js'
6
7const assert = (c, m) => { if (!c) throw new Error('FAIL: ' + m); console.log(' ok -', m) }
8let NOW = 1000
9const clock = () => NOW
10const A = 'aaaa', B = 'bbbb', C = 'cccc'
11
12// 1. lowest-key co-owner is the signer
13{
14 NOW = 1000
15 const sp = new SignerPresence({ self: B, graceMs: 15000, clock })
16 sp.markPresent(A)
17 assert(sp.currentSigner() === A, 'lowest-key co-owner (A) is elected signer')
18 assert(sp.holds() === false, 'B (higher key) does not hold while A is present')
19}
20
21// 2 & 3. holder departs → grace keeps it → after grace, failover
22{
23 NOW = 1000
24 const sp = new SignerPresence({ self: B, graceMs: 15000, clock })
25 sp.markPresent(A)
26 NOW = 5000; sp.markAbsent(A)
27 NOW = 5000 + 14999
28 assert(sp.currentSigner() === A, 'within grace, departed holder A stays elected (no premature failover)')
29 assert(sp.holds() === false, 'B still does not hold within grace')
30 NOW = 5000 + 15001
31 assert(sp.currentSigner() === B, 'after grace elapses, failover to B')
32 assert(sp.holds() === true, 'B now holds')
33}
34
35// 4. blip (depart + return within grace) does NOT thrash the signer
36{
37 NOW = 1000
38 const sp = new SignerPresence({ self: B, graceMs: 15000, clock })
39 sp.markPresent(A)
40 NOW = 5000; sp.markAbsent(A)
41 NOW = 12000; sp.markPresent(A) // returned within grace
42 NOW = 5000 + 20000 // well past the original grace window
43 assert(sp.currentSigner() === A, 'a blip (return within grace) does NOT fail the signer over')
44 assert(sp.holds() === false, 'B never took over on the blip')
45}
46
47// 5. self is lowest → self holds
48{
49 NOW = 1000
50 const sp = new SignerPresence({ self: A, graceMs: 15000, clock })
51 sp.markPresent(B)
52 assert(sp.holds() === true, 'self=A (lowest) holds')
53}
54
55// 6. all lower co-owners gone + grace → self holds
56{
57 NOW = 1000
58 const sp = new SignerPresence({ self: B, graceMs: 15000, clock })
59 sp.markPresent(A)
60 NOW = 2000; sp.markAbsent(A)
61 NOW = 2000 + 15001
62 assert(sp.holds() === true, 'after the only lower co-owner departs+grace, self holds')
63 assert(sp.currentSigner() === B, 'self is the sole effective signer')
64}
65
66// 7. three co-owners: cascade failover A→B→C
67{
68 NOW = 1000
69 const sp = new SignerPresence({ self: C, graceMs: 10000, clock })
70 sp.markPresent(A); sp.markPresent(B)
71 assert(sp.currentSigner() === A, 'A (lowest of three) signs')
72 NOW = 2000; sp.markAbsent(A); NOW = 2000 + 10001
73 assert(sp.currentSigner() === B, 'A gone+grace → B signs')
74 assert(sp.holds() === false, 'C still not holding while B present')
75 NOW = 30000; sp.markAbsent(B); NOW = 30000 + 10001
76 assert(sp.holds() === true, 'B gone+grace → C (self) holds')
77}
78
79// 8. structurally leak-free: no statebase, no append path
80{
81 const sp = new SignerPresence({ self: A })
82 assert(sp.sb === undefined && typeof sp.set !== 'function' && typeof sp.append !== 'function',
83 'SignerPresence has NO statebase/append path — zero log writes by construction')
84}
85
86console.log('\nSIGNER-PRESENCE OK: lowest-key election, grace blip-tolerance, cascade failover, zero log writes')