// LabelRoom — the runnable co-authored labeler: ties LabelBase (mod intents) + Lease (single active
// signer) over a shared StateBase. N attested MODERATORS are equal writers appending label INTENTS;
// each tick the LEASE-holder signs new/changed intents into the SHARED __label__ log (NOT a
// single-writer core — so the signer can fail over with no fork, exactly like the swarm-PDS writes
// signed #commits into the shared __commit__ log). A served label feed is PROJECTED from that shared
// log per participant (reusing serve-membrane's pattern) — the serve/projection slice.
//
// Failover-safe DEDUP: the "what's already emitted" state IS the shared __label__ log. Each tick reads
// it and signs only intents whose (uri,val,cts,neg) state isn't already there — so a new lease-holder
// never re-signs the previous holder's labels (no in-memory seed to go stale). RFC6979 determinism
// means the same intent always signs byte-identically, so the shared log is consistent across holders.
//
// Substrate INJECTED (StateBaseV3 or any KV log with async set/del/get/list) — unit-testable with a mock.
import b4a from 'b4a'
import { LabelBase } from './label-base.js'
import { Lease } from './lease.js'
import { materializeLabel } from './label-signer.js'
import { encodeLabel, decodeLabel } from 'atproto-identity/lib/labels.js'
const SIGNED_NS = '__label__' // reserved (leading __) — never a repo record or a label intent
const SEP = '\x00'
const stateOf = (l) => l.uri + SEP + l.val + SEP + String(l.cts) + SEP + (l.neg ? '1' : '0')
// ⛔ CONTENT-KEYED, not index-keyed. The log used to be keyed `pad(n)` (n = the list length the signer saw),
// so two signers during a partition both wrote __label__\x00<n> with DIFFERENT labels and last-write-wins
// silently dropped one. A key derived from the label itself cannot collide with a different label, and
// the same label signed twice (RFC6979: byte-identical) lands on the same key. cts first, so the log still
// reads roughly in time order.
const signedKey = (l) => SIGNED_NS + SEP + String(l.cts) + SEP + l.uri + SEP + l.val + SEP + (l.neg ? '1' : '0')
export class LabelRoom {
// statebase: shared StateBase (mods = writers). src: labeler DID. privateKey: shared #atproto_label
// key. self: this mod's device id (drives the lease). ttlMs: lease TTL.
//
// `holds`: () => boolean | Promise<boolean> — the HOST's signer election (hpp's presence election). When
// given, LabelRoom runs no election of its own and writes no __lease__: hpp retired that append-forever
// heartbeat, and two elections could disagree about who signs. Without it, the original Lease (self/ttlMs).
constructor (statebase, { src, privateKey, self, ttlMs = 15000, holds = null, ns } = {}) {
if (!src || !privateKey) throw new Error('LabelRoom: src + privateKey required')
if (!holds && !self) throw new Error('LabelRoom: self required (or pass holds: the host\'s signer election)')
this.sb = statebase
this.src = src
this.privateKey = privateKey
this.base = new LabelBase(statebase, { src, ns }) // ns: see LabelBase — reserved name on a repo's base
this.holds = holds
this.lease = holds ? null : new Lease(statebase, { self, ttlMs })
this._projected = new WeakMap() // feed core -> Set of label hexes already appended
// The last projectTo run: { at, appended, coreLength, logLength }. A diagnostic (2026-10-14: re-signed labels
// sat in the signed log while the gateway served the old ones, and nothing said whether projection had run).
this.lastProjection = null
}
// A mod records a label intent (label/negate). Authority to WRITE is StateBase admission.
label (uri, val, opts) { return this.base.label(uri, val, opts) }
negate (uri, val, opts) { return this.base.negate(uri, val, opts) }
ensure (uri, val, opts) { return this.base.ensure(uri, val, opts) } // idempotent: writes only on a change
// Read the shared signed-label log (append-ordered), decoded. This is what the projection slice folds
// into a served feed hypercore; consumers (favela readLabelFeed) get plain signed labels.
async signedLog () {
const map = await this.sb.list(SIGNED_NS + SEP)
return Object.keys(map).sort().map((k) => decodeLabel(b4a.from(map[k], 'hex')))
}
// Project the shared __label__ log into a served feed hypercore (append-only, idempotent). Every
// participant runs this against its OWN feed core; because __label__ is append-ordered + deterministic,
// each participant's feed is byte-identical — so the labeler is serveable from ANY node (the
// serve-membrane pattern). Consumers (favela readLabelFeed) get plain signed labels. Returns the count
// appended this call.
//
// Appends every label in the log the core does not already hold (in key order), not "entries past the
// core's length": with content keys a label replicated in later can sort BEFORE ones already projected.
async projectTo (feedCore) {
await feedCore.ready()
let have = this._projected.get(feedCore)
if (!have || have.size > feedCore.length) {
have = new Set()
for (let i = 0; i < feedCore.length; i++) have.add(b4a.toString(await feedCore.get(i), 'hex'))
this._projected.set(feedCore, have)
}
const map = await this.sb.list(SIGNED_NS + SEP)
let appended = 0
for (const k of Object.keys(map).sort()) {
const hex = map[k]
if (have.has(hex)) continue
await feedCore.append(b4a.from(hex, 'hex'))
have.add(hex)
appended++
}
this.lastProjection = { at: Date.now(), appended, coreLength: feedCore.length, logLength: Object.keys(map).length }
return appended
}
// One tick: renew/claim the lease; if we hold it, sign intents not yet in the shared __label__ log
// and append them. Returns the count appended (0 if not the lease-holder). Failover-safe.
async tick (now = Date.now()) {
const mine = this.holds ? await this.holds() : await this.lease.renewOrClaim(now)
if (!mine) return 0
const logMap = await this.sb.list(SIGNED_NS + SEP)
// ⛔ Only a label carrying `ver` counts as emitted. The label spec requires ver: 1 on transfer and signs over
// it; labels signed before atproto-identity 0.0.5 lack it and bsky ignores them. Such an entry is re-signed
// below and lands on the SAME content key (signedKey has no ver), replacing the invalid one in place.
const emitted = new Set()
for (const k of Object.keys(logMap)) {
const l = decodeLabel(b4a.from(logMap[k], 'hex'))
if (l.ver === 1) emitted.add(stateOf(l))
}
let count = 0
for (const i of await this.base.intents()) {
if (emitted.has(stateOf(i))) continue
const signed = materializeLabel(i, { src: this.src, privateKey: this.privateKey })
await this.sb.set(signedKey(signed), b4a.toString(encodeLabel(signed), 'hex'))
emitted.add(stateOf(i))
count++
}
return count
}
}
export { SIGNED_NS }