Files
bitgate/.hermes/plans/2026-07-26_184722-nostr-governance-execution-plan.md
T

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

  1. No changes in bitvid during extraction.
  2. No changes in bitroad during extraction.
  3. No shared source imports from either repo.
  4. Reference repos are read-only inputs for behavior, fixtures, and provenance only.
  5. No UI code in the SDK.
  6. No browser globals in packages/core.
  7. No second evaluator in adapters, HTTP wrappers, or example apps.
  8. 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.js
  • js/services/moderationUtils.js
  • js/accessControl.js
  • js/adminListStore.js
  • js/adminListBatch.js
  • js/adminEventBlacklistHelpers.js
  • js/services/trustBootstrap.js
  • js/services/moderationDecorator.js
  • js/app/moderationCoordinator.js

BitRoad source files to treat as target-model references

  • src/commerce/address.mjs
  • src/commerce/storefronts.mjs
  • src/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:30078 coordinates as durable object identifiers for storefronts and products.

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 GovernanceDecision to 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:

  • moderationCoordinator application flow
  • ModerationActionController
  • 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:

  1. Record pinned reference commits for BitVid and BitRoad.
  2. Map each planned module to source reference files.
  3. Record which behaviors are parity requirements vs future changes.
  4. 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.json files under packages/*

Scripts to add at root:

  • test
  • test:unit
  • test:conformance
  • typecheck
  • build:types
  • lint
  • check:references

Validation:

  • npm install
  • npm run test
  • npm run typecheck
  • npm 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.js
  • bitvid/js/services/moderationUtils.js
  • bitvid/js/accessControl.js
  • bitvid/js/adminListStore.js
  • bitvid/js/adminListBatch.js
  • bitvid/js/adminEventBlacklistHelpers.js
  • bitvid/js/services/trustBootstrap.js
  • bitvid/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.js
  • packages/core/test/authority.test.js
  • packages/core/test/evaluator-precedence.test.js
  • packages/core/test/evaluator-thresholds.test.js
  • packages/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/core
  • npm run typecheck

Acceptance:

  • Same snapshot + same policy = same decision.
  • Core contains no bitvid or bitroad terminology.

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/nostr
  • npm 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:

  1. Feed committed BitVid fixtures through legacy codec.
  2. Evaluate them through the pure core.
  3. Map the result back into BitVid-shaped moderation fields.
  4. 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.mjs coordinate parsing
  • src/commerce/storefronts.mjs storefront address structure
  • src/commerce/product.mjs product 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/runtime
  • npm 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/runtime
  • npm 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 #e filters
  • 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

  1. License: Are you comfortable making nostr-governance GPL-compatible from day one?
  2. Release scope: Should 0.1 be read-only evaluation only, with no admin write commands yet?
  3. Canonical schema: Do you want one canonical governance schema now, or legacy BitVid-only read support first?
  4. 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.