Files
thePR0M3TH3AN adeba151b8 feat: rename to BitGate and close the drop-in gap
Renames the project and makes it as easy to adopt as BitLogin, which was the
stated bar and which it did not previously meet.

Rename (ADR 0005): @nostr-governance/* -> @bitgate/*, governance-* elements ->
bitgate-*, createGovernanceRuntime -> createBitGate. "Governance" reads as DAO
voting to this audience and named a category rather than a product; BitGate
names the benefit and joins the bit- family that BitVid and BitRoad compose
with. Domain types keep their names — the product is BitGate, the subject
matter is still governance. The migration plan is left as written; this ADR
records the change rather than rewriting history.

Drop-in distribution (ADR 0006): adopting BitGate meant supplying a transport,
authoring a policy, and wiring every element in JavaScript. Now:

- createRelayTransport(urls) — WebSocket transport with reconnection, backoff,
  subscription replay, and cross-relay dedupe
- policy presets (social, commerce, admin-only) addressable by name; the engine
  still carries no thresholds of its own
- <bitgate-provider> — configuration in markup, with descendants finding the
  runtime through a bubbling context event rather than DOM walking, which
  breaks across shadow boundaries
- npm run build:widget — a self-contained dist/bitgate.js

That last one came from running it in a browser rather than from a test. Bare
specifiers resolve in Node and vitest but not in a browser, so the widget
source worked everywhere except a plain static page — the one place it exists
to serve. The bundle is not committed: consumers build from reviewed source.

Also found by running it: the provider's ready event fires during element
upgrade, before a page that defines elements after parsing can attach a
listener, so it now exposes `ready` as a promise too.

Four bugs fixed along the way, three found by new tests and one by the browser:
admin state stayed silent when a roster change altered capabilities but no
denials; the relay transport sent every subscription twice on connect; the
commerce preset computed a meaningless visibility verdict at checkout and could
hide a listing from its own seller; <bitgate-status> threw on an unusable
decision.

759 tests; lint, typecheck, and both builds clean.
2026-08-09 19:58:45 -04:00

7.7 KiB
Raw Permalink Blame History

Checkpoints

Scope note: this repository is the SDK. Migration-plan Phases 6–11 (BitVid shadow mode, cutovers, and duplicate removal) modify the consumer repositories and are tracked there, not here.

Checkpoint 0 — Repo skeleton (completed)

Delivered: npm workspaces monorepo, Node 22 + ESM, Vitest, JSDoc typecheck, GitHub Actions CI, reference import guard, provenance docs.

Later additions: GPL-3.0-or-later LICENSE, CHANGELOG.md, architecture decision records under docs/adr/, and a working per-package type build. The types fields previously pointed at paths the build never produced, so consumers received no declarations; declarations now emit into each package.

Checkpoint 1 — Characterization corpus (completed)

Delivered:

  • 34 fixtures under fixtures/bitvid/cases/, generated by scripts/build-characterization-corpus.mjs
  • conformance harness (packages/testing/src/conformance.js) that builds a snapshot, evaluates it, and diffs against a partial expectation
  • conformance suite (tests/conformance/bitvid-fixtures.test.js) asserting every required behavior, determinism, and input immutability

Characterized values, read from the pinned reference commit and cross-checked against the migration plan's baseline section:

Trusted report blur threshold:       3
Trusted report autoplay threshold:   3
Trusted mute hide threshold:        20
Trusted spam-report hide threshold:  5
Trusted mute validity window:       60 days

These live in @bitgate/bitvid-compat as one application's profile, not as engine defaults.

Provenance: fixtures are authored from the reference source and its documented configuration, not captured from a live relay. They pin the behavior the extraction must reproduce; they are not a recording of production traffic.

Checkpoint 2 — Pure core (completed)

Delivered: identifiers, targets, authority, administrative-state reduction, reasons, evidence, policy engine, fingerprints, evaluator, adapter contract.

Acceptance:

  • identical snapshots produce identical decisions (asserted per fixture)
  • evaluation performs no I/O and reads an injected clock
  • evaluation mutates no input (asserted per fixture)
  • the core imports no browser globals and has no dependencies
  • the core contains no application-specific terms or thresholds

Checkpoint 3 — Nostr codecs (completed)

Delivered: bech32/NIP-19 (verified against the NIP-19 test vector), canonical v1 on kind:30078, NIP-56 reports, NIP-51 mute lists, legacy administrative lists covering all five migration stages, replaceable selection with a deterministic same-timestamp tiebreak, injected signature verification.

Checkpoint 4 — Runtime stores (completed)

Delivered: GovernanceAdminStore, TrustGraphStore, ReportStore, TrustedMuteStore, PolicyStore, OverrideStore, GovernanceRuntime.

Acceptance: injected transport, storage, and clock; change-only emission; viewer switching clears viewer-scoped state while keeping operator seeds; subscriptions close on destroy; report subscriptions are chunked (200 per filter) rather than opened per target; author evidence is queryable in batches; expired mute lists prune against the injected clock.

Correction

An earlier revision of this file claimed storage acceptance that was not met: storage was injected and then never used, so nothing persisted or hydrated and the characterized cache-fallback behavior existed only at the reduction level. Closed in Checkpoint 8.

Checkpoint 5 — Public commands (completed)

Delivered: contribution, role, policy, community-source, report, and override commands with stable error codes.

Acceptance: every command checks capability locally before the signer is invoked, so an unauthorized actor cannot produce a signed mutation; revoked actors' contributions stop affecting effective state; protected actors are refused at the command boundary; first relay acceptance completes the command while remaining relay results stay observable.

Checkpoint 6 — Reusability proof (completed)

Delivered: examples/commerce, a marketplace built entirely on the public API, covering the ten proof-of-fit behaviors the migration plan requires — including that no core change was needed. Plus README.md, docs/integration-guide.md, and examples/commerce/README.md.

Checkpoint 7 — Performance and security fixtures (completed)

Delivered:

  • tests/performance/large-feed.test.js — 5,000 targets, 500 authors, 100 trusted muters per author, multiple report categories and administrative contributors, viewer switching, and roster changes
  • tests/security/requirements.test.js — one case per enumerated security requirement testable at this layer

Acceptance:

  • evaluateMany(5,000) performs no network access
  • report subscriptions are chunked, never one per card
  • updating one report invalidates only that target's decisions
  • updating one author's mute state invalidates only that author's targets
  • changing policy, roster, or viewer invalidates everything
  • destroy leaves no subscriptions or cached decisions

This fixture found a real defect: evaluation was quadratic in state size, because the snapshot and its fingerprint were rebuilt per target. A 5,000-target pass took ~47 seconds. After memoizing both, the same pass takes ~85ms cold and ~6ms warm.

Requirements 15 and 16 (never export signer secrets, avoid shared moderator keys) are properties of the injected signer and of operational practice rather than of this code; the root-authorized contributor model is what removes the need for a shared moderator key.

Checkpoint 8 — Runtime wiring (completed)

Closes the gaps between interfaces that existed and pipelines that used them.

Delivered:

  • Storage: persist() / hydrate(), hydrate-before-query on load, and last-known-good state retained with a stale flag when relays fail. Cache keys are namespaced by root identity — not by the full roster, which would rotate the key every time a moderator was added.
  • Signature verification wired into the load path through an injected verifier, with rejected counts in diagnostics and an explicit signatureVerification: enabled|disabled readout.
  • Trusted mute-list subscriptions by author, chunked. Without this the trusted-mute store never populated in a real deployment.
  • Community source resolution: curator lists are fetched and merged with a source marker, still subject to the curator's capabilities.
  • Root policy application: policy documents are now applied rather than merely announced, accepted only from the root, and a malformed document leaves the working policy in place.
  • Import/export (§10.14) exporting contributions rather than conclusions, so an import re-derives effective state against the importing roster.
  • Capability queries (§10.8): can(), capabilitiesOf(), viewerCapabilities().

Design note: the root administrator is now a constructor option, because it is deployment configuration rather than discovered state. Hydration could not otherwise find its own cache — the key derives from the root fingerprint the cache itself would have had to supply.

Status

CI gates green: reference guard, typecheck, 634 tests, per-package type build. Typecheck and the declaration build both pass from a cold checkout with no dist/ present.

Not done here

Migration-plan Phases 6–11 operate on BitVid and BitRoad:

  • Phase 6 — BitVid compatibility facade and shadow mode
  • Phase 7 — cut over BitVid administrative state
  • Phase 8 — cut over BitVid reports and trust
  • Phase 9 — cut over BitVid policy
  • Phase 10 — validate through BitRoad itself
  • Phase 11 — remove duplicated BitVid logic

The commerce example stands in for Phase 10's API-shape validation without touching the BitRoad repository.