OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room commits / 2d093f7

LabelBase.ensure: idempotent label write for projectors (0.2.27)
A roster projector re-asserts its whole label set on every pass. label() always writes a fresh cts, and in the content-keyed __label__ log a new cts is a new signed label, so the log would grow on every pass. ensure() writes only when the state (applied/negated) changes and keeps the cts otherwise; negating a never-applied label writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2d093f77c861964245a47b838bebf2b43aa2c78d
by plankatron <fleeky@gmail.com> · 2026-10-14T02:01:52.000Z

Changed files (4) +35 -1

lib/label-base.js +11 -0
……@@ -46,6 +46,17 @@ export class LabelBase {
4646
4747 negate (uri, val, opts = {}) { return this.label(uri, val, { ...opts, neg: true }) }
4848
49 // Bring (uri, val) to a state, writing only on a CHANGE — what a projector re-asserting a whole label
50 // set on every pass needs. An unchanged state keeps its cts: a new cts is a new signed label in the
51 // content-keyed __label__ log, so re-writing it would grow the log on every pass. Negating a label that
52 // was never applied writes nothing either (there is nothing to retract). → { intent, changed }.
53 async ensure (uri, val, { neg = false, cts } = {}) {
54 if (!uri || !val) throw new Error('LabelBase.ensure: uri + val required')
55 const cur = await this.sb.get(this._key(uri, val))
56 if (cur ? !!cur.neg === !!neg : neg) return { intent: cur || null, changed: false }
57 return { intent: await this.label(uri, val, { neg, cts }), changed: true }
58 }
59
4960 // All label intents in the linearized view, INCLUDING negated ones — what the signer materializes
5061 // (it emits neg labels too, so consumers see retractions). [{ uri, val, neg, cts }].
5162 async intents () {
lib/label-room.js +1 -0
……@@ -49,6 +49,7 @@ export class LabelRoom {
4949 // A mod records a label intent (label/negate). Authority to WRITE is StateBase admission.
5050 label (uri, val, opts) { return this.base.label(uri, val, opts) }
5151 negate (uri, val, opts) { return this.base.negate(uri, val, opts) }
52 ensure (uri, val, opts) { return this.base.ensure(uri, val, opts) } // idempotent: writes only on a change
5253
5354 // Read the shared signed-label log (append-ordered), decoded. This is what the projection slice folds
5455 // into a served feed hypercore; consumers (favela readLabelFeed) get plain signed labels.
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "atproto-room",
3 "version": "0.2.26",
3 "version": "0.2.27",
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/label-base.test.mjs +22 -0
……@@ -53,4 +53,26 @@ assert(threw, 'constructor requires src (the labeler DID)')
5353threw = false; try { await lb.label('', 'x') } catch { threw = true }
5454assert(threw, 'label requires uri + val')
5555
56// ensure: the idempotent write a PROJECTOR needs (roster → member badges, re-run on every roster change).
57// An unchanged state writes NOTHING and keeps its cts — a re-written cts is a NEW signed label in the
58// content-keyed __label__ log, so a projector re-asserting its whole set would grow the log every pass.
59{
60 const s = mockSb()
61 const e = new LabelBase(s, { src: 'did:plc:l' })
62 let writes = 0
63 const set = s.set; s.set = async (k, v) => { writes++; return set(k, v) }
64 const D = 'did:plc:member'
65 const a = await e.ensure(D, 'loser-club-member', { cts: '2026-10-13T00:00:00.000Z' })
66 assert(a.changed === true && writes === 1, 'ensure: a new label is written')
67 const b = await e.ensure(D, 'loser-club-member', { cts: '2026-10-14T00:00:00.000Z' })
68 assert(b.changed === false && writes === 1, 'ensure: the same state again writes nothing')
69 assert(b.intent.cts === '2026-10-13T00:00:00.000Z', 'ensure: and keeps the original cts')
70 const c = await e.ensure(D, 'loser-club-member', { neg: true, cts: '2026-10-15T00:00:00.000Z' })
71 assert(c.changed === true && c.intent.neg === true && writes === 2, 'ensure: a negation is a change')
72 const d = await e.ensure(D, 'loser-club-member', { neg: true })
73 assert(d.changed === false && writes === 2, 'ensure: negating an already-negated label writes nothing')
74 const f = await e.ensure('did:plc:never', 'loser-club-member', { neg: true })
75 assert(f.changed === false && f.intent === null && writes === 2, 'ensure: negating a label that never existed writes nothing')
76}
77
5678console.log('\nLABEL-BASE OK: label/negate intents over an injected KV log; active vs intents; reserved-key exclusion')