nostr: resolveAuthorityRoster — moderator set as signed data

"Who is a moderator" should change without a redeploy, but apps had to
hand-assemble the createAuthorityState actors map. Add resolveAuthorityRoster
(@bitgate/nostr): turn signed kind-30000 "role set" events into the
actor->roles map, ready to spread into createAuthorityState.

It honors ONLY sets signed by an authorized key -- the root, or (with
`signer: <role>`) an actor already granted that role, giving one-role-
delegates-another chains in a single ordered pass. It keeps the latest set
per publisher (kind 30000 is replaceable) and ignores anyone unauthorized,
which is the security property: the roster is only as trustworthy as the key
that signed it. Signature verification stays the caller's job (inject
@bitgate/verify upstream).

Covered by roster.test.js: root-only grants, unauthorized-ignored,
delegation, replaceable-latest-wins, and that the output feeds
createAuthorityState. README headless section shows the pattern.

Addresses the "moderator-set as data" item of the Tessera feedback
(docs/TODO.md) -- previously marked PARTIAL.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BXQCvJPA6sVSQ5j8uBd9bB
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-08-11 22:59:50 -04:00
1 parent 1abba23df3
commit cecd70c55f
5 files changed
+214 -7

No files matched your search

+9 -7
View File
@@ -253,13 +253,15 @@ adoption the obvious choice.
stays **visible (warn)** but **not installable (transaction deny)** — where a
`hidden` boolean can't reach. Pinned by `tests/headless-quickstart.test.js`
(incl. that an out-of-set labeller's deny is ignored).
- **[PARTIAL 2026-08-11] Moderator-set as data.** The primitive exists
(`createAuthorityState({ root, actors })` + capability-gated
`reduceAdminState`), and the headless example demonstrates resolving the
honored set as data and ignoring an unauthorized labeller. Still open as a
first-class helper: resolving that actor map from a *signed, root-controlled
kind-30000 set* at runtime (with delegation) rather than the app assembling
the `actors` object itself. Left as a focused follow-up.
- **[DONE 2026-08-11] Moderator-set as data.** Shipped `resolveAuthorityRoster`
(`@bitgate/nostr`): resolves the honored actor→roles map from signed kind-30000
"role set" events and spreads straight into `createAuthorityState`. It honors
**only** root-signed sets, plus role-based delegation (`signer: "moderator"`
lets a resolved moderator publish a further set, one ordered pass), keeps the
latest set per publisher (replaceable), and ignores anyone unauthorized — so
moderators change by publishing a signed set, no redeploy. Covered by
`roster.test.js` (root-only, unauthorized-ignored, delegation, replaceable,
and feeds `createAuthorityState`); README "headless" section shows it.
- **[DONE 2026-08-11] Allowlist / whitelist mode.** The engine already honored
`requireAllowlist` + `allowlistMiss` (evaluator.js), and the commerce example
configured a seller allowlist by hand — but there was no reusable named