| … | … | @@ -46,6 +46,17 @@ export class LabelBase { |
| 46 | 46 | |
| 47 | 47 | negate (uri, val, opts = {}) { return this.label(uri, val, { ...opts, neg: true }) } |
| 48 | 48 | |
| 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 | ||
| 49 | 60 | // All label intents in the linearized view, INCLUDING negated ones — what the signer materializes |
| 50 | 61 | // (it emits neg labels too, so consumers see retractions). [{ uri, val, neg, cts }]. |
| 51 | 62 | async intents () { |
| … | … | @@ -49,6 +49,7 @@ export class LabelRoom { |
| 49 | 49 | // A mod records a label intent (label/negate). Authority to WRITE is StateBase admission. |
| 50 | 50 | label (uri, val, opts) { return this.base.label(uri, val, opts) } |
| 51 | 51 | 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 | |
| 52 | 53 | |
| 53 | 54 | // Read the shared signed-label log (append-ordered), decoded. This is what the projection slice folds |
| 54 | 55 | // into a served feed hypercore; consumers (favela readLabelFeed) get plain signed labels. |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "atproto-room", |
| 3 | "version": "0.2.26", | |
| 3 | "version": "0.2.27", | |
| 4 | 4 | "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.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "main": "index.js", |
| … | … | @@ -53,4 +53,26 @@ assert(threw, 'constructor requires src (the labeler DID)') |
| 53 | 53 | threw = false; try { await lb.label('', 'x') } catch { threw = true } |
| 54 | 54 | assert(threw, 'label requires uri + val') |
| 55 | 55 | |
| 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 | ||
| 56 | 78 | console.log('\nLABEL-BASE OK: label/negate intents over an injected KV log; active vs intents; reserved-key exclusion') |