OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room commits / b072718

0.2.32: presence attach says whether it got the channel; StateBase counts it
loser-club's co-owners each saw only themselves present (two signers) while wj62's presence over the same connections worked. A duplicate (protocol, id) attach is a deliberate no-op and was silent. attachPresence returns .attached and an onReceive hook; StateBaseV3.presenceDiag() reports attached / duplicate / received / peers. Diagnostic only. Test-first; units + bare 83/83. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b0727181a968b0173a0040f8c050bf36cf14c095
by plankatron <fleeky@gmail.com> · 2026-10-14T18:07:22.000Z

Changed files (4) +51 -6

lib/signer-presence-channel.js +9 -4
……@@ -23,11 +23,14 @@ export const PRESENCE_PROTOCOL = 'atproto-room/signer-presence/v1'
2323// can tell whether WE are caught up (currency). Returns { send(headRev?), close() }: `send` RE-announces
2424// our head when it changes (the caller wires this to head advances) so a stale head can't mis-elect a
2525// signer; `close` tears the channel down. (A non-object return would break callers that register `send`.)
26export function attachPresence (conn, { selfKey, presence, id = null, getHead = null } = {}) {
26// onReceive: optional (peerKey) => void, called on every received announce (a diagnostic hook).
27export function attachPresence (conn, { selfKey, presence, id = null, getHead = null, onReceive = null } = {}) {
2728 // Returns a DETACH function (call it to tear down — the original contract) with a `.send(headRev)`
28 // property for re-announcing our head when it changes. Hybrid so existing callers that use the return
29 // as a cleanup fn keep working, while the currency wiring calls `.send`.
30 const noop = () => {}; noop.send = () => {}
29 // property for re-announcing our head when it changes, and `.attached`: whether this call actually got the
30 // channel. ⛔ A duplicate (protocol, id) on a mux is a deliberate no-op — and was SILENT: 2026-10-14 loser-club's
31 // co-owners each saw only themselves while wj62's presence over the same connections worked, and nothing
32 // could say whether an attach had been a no-op. `.attached === false` says so.
33 const noop = () => {}; noop.send = () => {}; noop.attached = false
3134 if (!conn || !selfKey || !presence) return noop
3235 const self = String(selfKey)
3336 let peerKey = null
……@@ -41,6 +44,7 @@ export function attachPresence (conn, { selfKey, presence, id = null, getHead =
4144 onmessage: (data) => {
4245 if (!data || !data.key) return
4346 peerKey = String(data.key)
47 if (onReceive) { try { onReceive(peerKey) } catch {} }
4448 presence.markPresent(peerKey) // presence (fires onChange on connect)
4549 if (data.headRev !== undefined) presence.noteHead(peerKey, data.headRev || null) // currency (fires onChange on a head change)
4650 }
……@@ -52,6 +56,7 @@ export function attachPresence (conn, { selfKey, presence, id = null, getHead =
5256 conn.on('close', onclose)
5357 const detach = () => { if (done) return; done = true; try { conn.off('close', onclose) } catch {}; clear() }
5458 detach.send = (headRev) => announce(headRev) // re-announce our head (currency) when it changes
59 detach.attached = true
5560 return detach
5661 } catch { return noop }
5762}
lib/state-base.js +18 -1
……@@ -520,7 +520,18 @@ export class StateBaseV3 {
520520 // first opens). Single-DID and multi-DID both derive the same roomKey per DID, so peers of a DID match.
521521 attachSignerPresence (conn) {
522522 if (!this._presence) return () => {}
523 const handle = attachPresence(conn, { selfKey: this._selfKey.toString('hex'), presence: this._presence, id: this._roomKey, getHead: () => this._selfHead })
523 // Diagnostic counts (presenceDiag): attaches that got the channel, attaches that were a silent duplicate
524 // no-op, and announces received per peer — "channel taken" vs "peer never sends", told apart by a read.
525 const d = this._presenceDiagState || (this._presenceDiagState = { attached: 0, duplicate: 0, received: 0, peers: {} })
526 const handle = attachPresence(conn, {
527 selfKey: this._selfKey.toString('hex'),
528 presence: this._presence,
529 id: this._roomKey,
530 getHead: () => this._selfHead,
531 onReceive: (k) => { d.received++; d.peers[String(k).slice(0, 16)] = Date.now() }
532 })
533 if (handle.attached) d.attached++
534 else d.duplicate++
524535 this._presenceSenders.add(handle.send)
525536 return () => { this._presenceSenders.delete(handle.send); try { handle() } catch {} } // handle() detaches
526537 }
……@@ -542,6 +553,12 @@ export class StateBaseV3 {
542553 holdsSigner (now, netHead = null) { return this._presence ? this._presence.holds(now, netHead) : true }
543554 currentSigner (now, netHead = null) { return this._presence ? this._presence.currentSigner(now, netHead) : (this._selfKey ? this._selfKey.toString('hex') : null) }
544555 signerState (now, netHead = null) { return this._presence ? this._presence.state(now, netHead) : { self: null, signer: null, effective: [], holds: true } }
556 // Presence wiring diagnostics (see attachSignerPresence). Read-only; null without a presence election.
557 presenceDiag () {
558 if (!this._presence) return null
559 const d = this._presenceDiagState || { attached: 0, duplicate: 0, received: 0, peers: {} }
560 return { attached: d.attached, duplicate: d.duplicate, received: d.received, peers: { ...d.peers } }
561 }
545562
546563 // keet's _autobaseApply commit shape: a per-batch transaction; handlers write
547564 // into it; flush on success / close on error (never insert on the db directly).
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "atproto-room",
3 "version": "0.2.31",
3 "version": "0.2.32",
44 "description": "Attestation-gated, encrypted multi-writer room engine — StateBase (keet-model admission) + Lease + signer + RepoBase + LabelBase/LabelRoom + serve-membrane. Extracted from hyper-proto-peer; consumed by the atproto peer (repo devices) and the co-authored labeler.",
55 "type": "module",
66 "main": "index.js",
test/signer-presence-channel.test.mjs +23 -0
……@@ -40,3 +40,26 @@ test('presence handshake over a real connection: elect lowest-key signer, fail o
4040 t.ok(spB.holds(), 'after grace, B fails over to signer')
4141 t.is(spB.currentSigner(), B, 'B is now the sole signer')
4242})
43
44// ⛔ A SILENT NO-OP MUST BE VISIBLE. 2026-10-14: loser-club's two co-owners each saw ONLY themselves present
45// (each elected itself → two signers) while wj62's presence over the same connections worked. A second attach of
46// the same (protocol, id) on a mux is a no-op by design — if a stale StateBase holds the channel, the live one's
47// attach does nothing and nothing says so. The handle now reports whether it attached, and every received
48// announce is reported, so "channel taken" and "peer never sends" are told apart by a read.
49test('attach reports whether it got the channel; a duplicate (protocol, id) reports false; receipts are reported', async (t) => {
50 const spA = new SignerPresence({ self: A })
51 const spB = new SignerPresence({ self: B })
52 const a = new SecretStream(true)
53 const b = new SecretStream(false)
54 a.rawStream.pipe(b.rawStream).pipe(a.rawStream)
55 const id = Buffer.alloc(32, 7)
56 const gotA = []
57 const first = attachPresence(a, { selfKey: A, presence: spA, id, onReceive: (k) => gotA.push(k) })
58 const dup = attachPresence(a, { selfKey: A, presence: spA, id })
59 const other = attachPresence(b, { selfKey: B, presence: spB, id })
60 t.teardown(() => { first(); dup(); other(); try { a.destroy() } catch {} try { b.destroy() } catch {} })
61 t.is(first.attached, true, 'the first attach got the channel')
62 t.is(dup.attached, false, 'a second attach of the same (protocol, id) on this mux is a no-op — and says so')
63 t.is(other.attached, true)
64 t.ok(await waitFor(() => gotA.includes(B)), 'the peer\'s announce is reported to onReceive')
65})