// favela labeler feed — the single-authority labeler (DESIGN §8): a signed hypercore of
// com.atproto.label records on its own key. The producer SIGNS labels with its secp256k1 key;
// consumers replicate the feed by key and VERIFY each label against the labeler's did:key. Simpler
// than a repo (no MST/CAR/commit chain) — a label feed is just N independently-signed leaves.
//
// Multi-mod (co-authored labels) is a later variant — an autobase room + a signer lease (DESIGN §8),
// same as the peer's writer model. This is the single-mod first cut.
import { buildLabel, signLabel, verifyLabel, encodeLabel, decodeLabel } from 'atproto-identity/lib/labels.js'
import { keyFromPrivate } from 'atproto-identity/lib/atproto-identity.js'
export class Labeler {
// corestore: for the feed core. privateKey: the labeler's raw secp256k1 signing key. src: the
// labeler's DID published in each label (defaults to its did:key when no did:plc is minted).
constructor (corestore, { privateKey, src, name = 'label-feed' } = {}) {
this._key = keyFromPrivate(privateKey) // { privateKey, publicKey, didKey }
this.src = src || this._key.didKey
this.core = corestore.get({ name, valueEncoding: 'binary' })
}
async ready () { await this.core.ready(); return this }
get feedKey () { return this.core.key } // share with consumers to replicate the feed
get didKey () { return this._key.didKey } // share with consumers to verify signatures
// Append a signed label. uri = subject (an AT-URI or account DID); val = label value; neg retracts a
// prior (src,uri,val); cts defaults to now. Returns the signed label.
async label (uri, val, { neg, cts } = {}) {
const signed = signLabel(buildLabel({ src: this.src, uri, val, cts: cts || new Date().toISOString(), neg }), this._key.privateKey)
await this.core.append(encodeLabel(signed))
return signed
}
async close () { await this.core.close() }
}
// Consume a labeler feed: decode + VERIFY each label against `didKey` and addLabel the valid ones into
// the LabelStore. Downloads the whole feed then live-tails. `core` is a read replica opened by the
// labeler's feedKey (the caller replicates it over the swarm). Returns a stop() function.
export function readLabelFeed (core, didKey, store, { onError, onLabel } = {}) {
let stopped = false
;(async () => {
await core.ready()
try { core.download({ start: 0, end: -1 }) } catch {}
let i = 0
while (!stopped) {
if (i < core.length) {
try {
const label = decodeLabel(await core.get(i))
if (verifyLabel(label, didKey)) { const changed = await store.addLabel(label); if (changed && onLabel) onLabel(label) }
} catch (e) { if (onError) onError(e) }
i++
continue
}
await new Promise((resolve) => {
const on = () => { core.off('append', on); resolve() }
core.on('append', on)
if (stopped) { core.off('append', on); resolve() }
})
}
})().catch((e) => { if (onError) onError(e) })
return () => { stopped = true }
}