21 KiB
Nostr Governance Execution Plan
For Hermes: Use subagent-driven-development skill to implement this plan task-by-task.
Goal: Build a headless nostr-governance SDK that reproduces BitVid governance behavior from fixtures first, then proves it can model BitRoad targets, without modifying the BitVid or BitRoad repositories during extraction.
Architecture: Keep BitVid and BitRoad as read-only reference repos. Build a new repo that owns four layers: pure core, Nostr codecs, runtime stores, and thin consumer adapters. The first meaningful milestone is not a full runtime or UI; it is a fixture-driven vertical slice that turns existing BitVid moderation inputs into stable GovernanceDecision outputs.
Tech Stack: Node 22, npm workspaces, ESM JavaScript, strict JSDoc, TypeScript compiler for type-checking and .d.ts emit, Vitest, GitHub Actions.
1. What is strong vs weak in the current master plan
Strong
- Correctly separates headless governance from consumer UI.
- Correctly treats user, event, and address as first-class targets.
- Correctly insists on one canonical evaluator.
- Correctly identifies BitVid as the parity source and BitRoad as the address-target validation consumer.
- Correctly preserves batched subscriptions and cached fallback behavior.
Weak
- It is too wide for a first build.
- It mixes must-have parity work with later platform work.
- It risks starting with runtime plumbing before the pure evaluator is proven.
- It leaves open a common failure mode: porting BitVid UI-shaped logic into the SDK.
Recommendation
Start narrower.
Do not begin with runtime stores, live relay integration, admin write commands, or consumer-repo integration.
Start with this vertical slice:
BitVid fixture events/state
↓
BitVid legacy codec
↓
normalized governance snapshot
↓
pure evaluator
↓
GovernanceDecision
↓
BitVid decision adapter
If this slice does not achieve parity, the rest of the repository will just harden the wrong behavior.
2. Hard guardrails for this project
- No changes in
bitvidduring extraction. - No changes in
bitroadduring extraction. - No shared source imports from either repo.
- Reference repos are read-only inputs for behavior, fixtures, and provenance only.
- No UI code in the SDK.
- No browser globals in
packages/core. - No second evaluator in adapters, HTTP wrappers, or example apps.
- No consumer integration PR until internal conformance passes in this repo.
Licensing pushback
This part matters.
BitVid is GPL-3.0-or-later. If this repo directly ports logic or structure from BitVid, the safe assumption is that nostr-governance must remain GPL-compatible unless you do a true clean-room rewrite.
Do not ignore this now. Decide the license model in the first repo-bootstrap PR.
3. Read-only reference map
BitVid source files to treat as behavioral references
js/services/moderationService.jsjs/services/moderationUtils.jsjs/accessControl.jsjs/adminListStore.jsjs/adminListBatch.jsjs/adminEventBlacklistHelpers.jsjs/services/trustBootstrap.jsjs/services/moderationDecorator.jsjs/app/moderationCoordinator.js
BitRoad source files to treat as target-model references
src/commerce/address.mjssrc/commerce/storefronts.mjssrc/commerce/product.mjs
Key extracted behaviors already visible in references
- BitVid batches report subscriptions in chunks of 200.
- BitVid distinguishes ranking-only trusted mutes below threshold from hide behavior at threshold.
- BitVid hydrates cached admin state synchronously before live refresh.
- BitVid protects admins from community blacklist inclusion.
- BitRoad treats
kind:30078coordinates as durable object identifiers for storefronts and products.
4. Recommended repository shape
Create this structure in nostr-governance:
nostr-governance/
├── package.json
├── tsconfig.json
├── tsconfig.types.json
├── vitest.config.js
├── .github/workflows/ci.yml
├── docs/
│ ├── architecture.md
│ ├── reference-map.md
│ ├── license-and-provenance.md
│ ├── consumer-adapter-contract.md
│ └── checkpoints.md
├── fixtures/
│ ├── bitvid/
│ │ ├── admin-state/
│ │ ├── reports/
│ │ ├── trusted-mutes/
│ │ ├── overrides/
│ │ └── expectations/
│ └── bitroad/
│ ├── storefronts/
│ ├── products/
│ └── expectations/
├── scripts/
│ ├── import-bitvid-fixtures.mjs
│ ├── import-bitroad-reference-shapes.mjs
│ └── verify-no-reference-imports.mjs
├── packages/
│ ├── core/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── identifiers.js
│ │ │ ├── targets.js
│ │ │ ├── authority.js
│ │ │ ├── policy.js
│ │ │ ├── decisions.js
│ │ │ ├── evaluator.js
│ │ │ ├── snapshots.js
│ │ │ └── index.js
│ │ └── test/
│ ├── nostr/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── codecs/canonical-v1.js
│ │ │ ├── codecs/bitvid-legacy.js
│ │ │ ├── codecs/nip51.js
│ │ │ ├── codecs/nip56.js
│ │ │ ├── event-selection.js
│ │ │ ├── verification.js
│ │ │ └── index.js
│ │ └── test/
│ ├── runtime/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── GovernanceRuntime.js
│ │ │ ├── GovernanceAdminStore.js
│ │ │ ├── TrustGraphStore.js
│ │ │ ├── ReportStore.js
│ │ │ ├── TrustedMuteStore.js
│ │ │ ├── PolicyStore.js
│ │ │ ├── OverrideStore.js
│ │ │ └── index.js
│ │ └── test/
│ ├── bitvid-compat/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── accessControlFacade.js
│ │ │ ├── moderationServiceFacade.js
│ │ │ ├── videoDecisionAdapter.js
│ │ │ └── index.js
│ │ └── test/
│ └── testing/
│ ├── package.json
│ ├── src/
│ │ ├── FakeTransport.js
│ │ ├── FakeSigner.js
│ │ ├── FakeStorage.js
│ │ ├── FakeClock.js
│ │ └── index.js
│ └── test/
└── examples/
├── bitvid-parity/
└── bitroad-address-targets/
5. Package boundaries
@nostr-governance/core
Owns:
- target normalization
- identifiers
- authority resolution
- policy normalization
- evaluator
- decision composition
- explanations
- snapshot fingerprinting
Must not own:
- relay I/O
- localStorage
window- UI terms like video card, product card, modal, toast
@nostr-governance/nostr
Owns:
- legacy BitVid codec
- canonical v1 codec
- NIP-51 parse helpers
- NIP-56 parse helpers
- replaceable event selection
- signature verification hooks
@nostr-governance/runtime
Owns:
- store lifecycle
- transport wiring
- cache hydration
- subscriptions
- recomputation
- invalidation
@nostr-governance/bitvid-compat
Owns:
- mapping
GovernanceDecisionto BitVid-shaped moderation fields - access-control facade compatibility
- temporary method names BitVid expects
Must not own:
- feed orchestration
- playback recovery
- DOM events
- UI refresh behavior
@nostr-governance/testing
Owns:
- fake transport
- fake signer
- fake storage
- fake clock
- reusable conformance helpers
6. What not to port
Do not extract these into the SDK:
moderationCoordinatorapplication flowModerationActionController- video-card rendering
- profile modal logic
- playback coordination
- BitRoad storefront rendering
- BitRoad checkout UI
- toasts, DOM dispatch, CSS, or app copy
This is the line:
SDK decides policy.
Consumer decides presentation and UX.
7. Execution phases with stop points
Phase 0 — Freeze references and provenance
Objective: Make the repo safe before writing extracted code.
Files:
- Create:
docs/reference-map.md - Create:
docs/license-and-provenance.md - Create:
docs/checkpoints.md - Create:
scripts/verify-no-reference-imports.mjs
Work:
- Record pinned reference commits for BitVid and BitRoad.
- Map each planned module to source reference files.
- Record which behaviors are parity requirements vs future changes.
- Add a repo check that fails if any package imports from paths outside this repo.
Validation:
node scripts/verify-no-reference-imports.mjs- Expected: passes with zero external repo imports.
Checkpoint gate: Do not create runtime stores before this provenance map exists.
Phase 1 — Bootstrap the workspace
Objective: Create the empty repo skeleton and CI.
Files:
- Create:
package.json - Create:
tsconfig.json - Create:
tsconfig.types.json - Create:
vitest.config.js - Create:
.github/workflows/ci.yml - Create: package
package.jsonfiles underpackages/*
Scripts to add at root:
testtest:unittest:conformancetypecheckbuild:typeslintcheck:references
Validation:
npm installnpm run testnpm run typechecknpm run build:types
Acceptance:
- Empty packages import cleanly.
- CI runs with no browser globals required.
Checkpoint gate: Stop here and confirm the workspace shape before porting any behavior.
Phase 2 — Build the conformance corpus first
Objective: Turn BitVid behavior into committed fixtures and expectations.
Files:
- Create:
scripts/import-bitvid-fixtures.mjs - Create:
fixtures/bitvid/... - Create:
packages/testing/src/fixtureLoader.js - Create:
tests/conformance/bitvid-fixtures.test.js
Reference inputs:
bitvid/js/services/moderationService.jsbitvid/js/services/moderationUtils.jsbitvid/js/accessControl.jsbitvid/js/adminListStore.jsbitvid/js/adminListBatch.jsbitvid/js/adminEventBlacklistHelpers.jsbitvid/js/services/trustBootstrap.jsbitvid/js/services/moderationDecorator.js
Required fixture scenarios:
- trusted report threshold reached
- duplicate reporter dedupe
- blocked reporter ignored
- trusted mute below threshold
- trusted mute at threshold
- expired trusted mute
- personal block precedence
- admin blacklist
- event blacklist
- community blacklist
- moderator trust seed
- viewer override
- author override
- cache fallback
Validation:
npm run test:conformance -- bitvid-fixtures
Acceptance:
- The repo can replay BitVid behavior from committed fixtures without importing BitVid code.
Checkpoint gate: If fixture expectations are fuzzy, pause and fix the corpus before writing evaluator logic.
Phase 3 — Implement the pure core
Objective: Create the deterministic evaluator with zero I/O.
Files:
- Create:
packages/core/src/identifiers.js - Create:
packages/core/src/targets.js - Create:
packages/core/src/authority.js - Create:
packages/core/src/policy.js - Create:
packages/core/src/decisions.js - Create:
packages/core/src/evaluator.js - Create:
packages/core/src/snapshots.js - Create:
packages/core/test/*.test.js
First tests to write:
packages/core/test/targets.test.jspackages/core/test/authority.test.jspackages/core/test/evaluator-precedence.test.jspackages/core/test/evaluator-thresholds.test.jspackages/core/test/evaluator-overrides.test.js
Rules:
- all inputs immutable
- all outputs deterministic
- no
Date.now()without injected clock context - no hidden consumer-specific defaults in core
Validation:
npm run test:unit -- packages/corenpm run typecheck
Acceptance:
- Same snapshot + same policy = same decision.
- Core contains no
bitvidorbitroadterminology.
Checkpoint gate: Do not build codecs until the evaluator can pass core policy tests in isolation.
Phase 4 — Implement Nostr codecs
Objective: Convert legacy and canonical events into normalized snapshots.
Files:
- Create:
packages/nostr/src/codecs/bitvid-legacy.js - Create:
packages/nostr/src/codecs/canonical-v1.js - Create:
packages/nostr/src/codecs/nip51.js - Create:
packages/nostr/src/codecs/nip56.js - Create:
packages/nostr/src/event-selection.js - Create:
packages/nostr/src/verification.js - Create:
packages/nostr/test/*.test.js
Specific reference behaviors to preserve:
- BitVid admin-state cache shape and guardrails.
- Event blacklist normalization from
adminEventBlacklistHelpers.js. - Community source batching logic from
adminListBatch.js. - NIP-56 report dedupe by reporter and category.
- Replaceable-event newest selection with deterministic tie-breaking.
Validation:
npm run test:unit -- packages/nostrnpm run test:conformance -- bitvid-fixtures
Acceptance:
- Legacy BitVid events produce normalized snapshots that the core evaluator understands.
Checkpoint gate: If codecs need consumer-specific hacks, the snapshot model is wrong. Fix the model, not the adapter.
Phase 5 — Deliver the first vertical slice
Objective: Prove BitVid parity entirely inside this repo.
Files:
- Create:
packages/bitvid-compat/src/videoDecisionAdapter.js - Create:
packages/bitvid-compat/test/videoDecisionAdapter.test.js - Create:
examples/bitvid-parity/README.md - Create:
tests/conformance/bitvid-parity.test.js
Work:
- Feed committed BitVid fixtures through legacy codec.
- Evaluate them through the pure core.
- Map the result back into BitVid-shaped moderation fields.
- Compare expected hide/blur/restrict/ranking outcomes.
Validation:
npm run test:conformance -- bitvid-parity
Acceptance:
- The new repo can reproduce current BitVid policy outcomes without importing BitVid application code.
Checkpoint gate: This is the first milestone that matters. Do not move to runtime or live relays until this passes.
Phase 6 — Add BitRoad proof-of-fit without touching BitRoad
Objective: Prove the target model works for addressable commerce objects before any BitRoad integration PR.
Files:
- Create:
fixtures/bitroad/storefronts/*.json - Create:
fixtures/bitroad/products/*.json - Create:
examples/bitroad-address-targets/README.md - Create:
tests/conformance/bitroad-address-targets.test.js - Create:
docs/consumer-adapter-contract.md
Reference behaviors to model:
src/commerce/address.mjscoordinate parsingsrc/commerce/storefronts.mjsstorefront address structuresrc/commerce/product.mjsproduct address and replaceable semantics
Proof cases:
- seller denied by user target
- storefront denied by address target
- product denied by exact event only
- product address denial survives replacement revision
- checkout policy can deny transaction without requiring hidden listing UI
Validation:
npm run test:conformance -- bitroad-address-targets
Acceptance:
- The SDK can express BitRoad governance semantics without BitRoad-specific core logic.
Checkpoint gate: Only after this passes should you consider a BitRoad integration branch.
Phase 7 — Runtime stores
Objective: Add live transport, cache, invalidation, and recomputation.
Files:
- Create:
packages/runtime/src/GovernanceRuntime.js - Create:
packages/runtime/src/GovernanceAdminStore.js - Create:
packages/runtime/src/TrustGraphStore.js - Create:
packages/runtime/src/ReportStore.js - Create:
packages/runtime/src/TrustedMuteStore.js - Create:
packages/runtime/src/PolicyStore.js - Create:
packages/runtime/src/OverrideStore.js - Create:
packages/runtime/test/*.test.js
Must preserve from BitVid:
- synchronous cached fallback hydration
- emit-on-change only
- batched report subscriptions
- bounded filter chunking
- trust-seed recomputation on admin changes
- trusted-mute expiry with injected clock
Validation:
npm run test:unit -- packages/runtimenpm run test:conformance
Acceptance:
- No per-card subscription behavior.
- No network I/O during
evaluateMany().
Checkpoint gate: If runtime changes evaluator output relative to fixture mode, stop and resolve before adding commands.
Phase 8 — Admin write commands
Objective: Add mutation flows only after read-path parity is stable.
Files:
- Create:
packages/runtime/src/commands/*.js - Create:
packages/runtime/test/commands/*.test.js - Create:
docs/authority-model.md
Pushback: This phase should come later than the original wide plan suggests. Read parity is the product. Write commands are riskier and easier to get wrong.
Validation:
npm run test:unit -- packages/runtimenpm run test:conformance
Acceptance:
- unauthorized writes are rejected
- revoked actors no longer influence effective state
- protected actors cannot be denied
8. First four PRs I recommend
PR 1 — Repo bootstrap only
Deliver:
- workspace
- CI
- typecheck
- test runner
- package skeleton
- license/provenance docs
PR 2 — Fixture corpus only
Deliver:
- import scripts
- committed BitVid fixtures
- conformance harness
- no evaluator yet
PR 3 — Pure core only
Deliver:
- identifiers
- targets
- policy
- evaluator
- core tests
PR 4 — BitVid vertical slice only
Deliver:
- legacy codec
- parity tests
- BitVid decision adapter
- passing conformance
Stop after PR 4 and reassess before live runtime work.
9. Concrete test matrix
Core
- personal block precedence
- user deny
- event deny
- address deny
- allowlist miss
- protected target
- viewer override
- transaction deny
- deterministic explanation trace
Trust/reporting
- F1 report counts
- duplicate report dedupe
- blocked reporter ignored
- muted reporter ignored
- trust seed fallback
- trust graph recompute
Trusted mutes
- below threshold = downrank only
- at threshold = hide/restrict
- expiry window
- category counts
Runtime
- batched
#efilters - latest replaceable event selection
- cached fallback survives relay failure
- viewer switch clears viewer-local state
- destroy closes subscriptions
BitRoad proof
- address denial survives product replacement
- exact event denial does not automatically deny newer replacement event
- checkout profile can deny transaction independently of visibility profile
10. Risks and answers
| Risk | Why it matters | Mitigation |
|---|---|---|
| GPL inheritance | Direct extraction likely carries GPL obligations | Decide license in PR 1; document provenance |
| Authority ambiguity from BitVid | Current moderator vs super-admin loading semantics are not fully clean | Normalize around root-authorized contributor model before commands |
| Hidden policy duplication | BitVid currently computes overlapping policy in more than one layer | Keep one evaluator; adapters only map decisions |
| Address-target mistakes | BitRoad uses replaceable kind:30078 coordinates |
Treat address denial as first-class in core tests |
| Premature runtime complexity | Live relay code can hide policy bugs | Prove fixture parity before runtime |
| Consumer leakage into core | Video/product/storefront concepts can pollute the SDK | Enforce neutral naming and adapter contracts |
11. Open questions to answer before implementation starts
- License: Are you comfortable making
nostr-governanceGPL-compatible from day one? - Release scope: Should
0.1be read-only evaluation only, with no admin write commands yet? - Canonical schema: Do you want one canonical governance schema now, or legacy BitVid-only read support first?
- Package manager: npm workspaces are fine; do you want to keep it that simple rather than introducing pnpm?
My recommendation:
- yes to GPL-compatible now
- yes to read-only
0.1 - yes to supporting a canonical v1 schema, but only after BitVid legacy parity exists
- yes to plain npm workspaces
12. Immediate next action
The correct next move is:
Create the repo skeleton plus provenance/conformance harness, then stop.
Do not start by moving moderationService.js wholesale. That is the easiest way to transplant BitVid application assumptions into a package that is supposed to be generic.
13. Definition of success for the first milestone
Success is not “the runtime exists.”
Success is:
Given pinned BitVid fixtures,
nostr-governance produces the same hide/blur/restrict/downrank outcomes
through a legacy codec + pure evaluator + BitVid adapter,
with no imports from BitVid.
Once that is true, the rest of the plan becomes safer and much more mechanical.