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

8.2 KiB

Changelog

Notable changes to this repository. The package version and the wire schema version move independently: a breaking package release does not imply a new event schema, and a new schema does not require a major package bump unless the public API changes with it.

[Unreleased]

Changed — renamed to BitGate

@nostr-governance/* is now @bitgate/*, governance-* elements are bitgate-*, and createGovernanceRuntime / defineGovernanceElements are createBitGate / defineBitGateElements. Domain types keep their names: the product is BitGate, the subject matter is still governance. See ADR 0005.

The storage key prefix changed from nostr-governance: to bitgate:.

Added — drop-in distribution

Adopting BitGate previously meant supplying a transport, authoring a policy, and wiring every element in JavaScript. Three pieces close that gap, plus a build step. See ADR 0006.

  • createRelayTransport(urls) — a small WebSocket transport with reconnection, backoff, subscription replay, and cross-relay deduplication.
  • Policy presets — social, commerce, admin-only, addressable by name. The engine still carries no thresholds of its own.
  • <bitgate-provider> — configuration in markup; descendants find the runtime through a bubbling context-request event rather than DOM walking, which would break across shadow boundaries. Targets can be read from attributes (target-user, target-event, target-address).
  • npm run build:widget — a self-contained dist/bitgate.js via esbuild. Bare specifiers do not resolve in browsers, so the unbundled source worked everywhere except a plain static page. Not committed: build from source.

Added

  • Core decision model. Four independent dimensions (ranking, visibility, interaction, transaction) with severity ladders and commutative max-merge composition.
  • Stable reason identifiers (REASON_IDS) as part of the public contract. Renaming or removing one is a breaking change.
  • Structured evidence, redacted by default and merged across an object's targets, so a multi-target object explains itself with everything that contributed.
  • Administrative-state reduction with capability filtering at merge time, community-source marking, and protected-actor stripping.
  • Snapshot fingerprints for cache invalidation and mismatch records.
  • Application adapter contract (createApplicationAdapter, evaluateObject, collectTargets).
  • Codecs: canonical v1 on kind:30078, NIP-56 reports, NIP-51 mute lists, legacy administrative lists, bech32/NIP-19, replaceable selection with a deterministic same-timestamp tiebreak, injected signature verification.
  • Runtime: six stores, transport orchestration, chunked report subscriptions, viewer lifecycle, diagnostics, idempotent teardown.
  • Commands with local capability checks, protected-target refusal, partial relay acceptance, and stable error codes.
  • Characterization corpus: 34 generated fixtures covering the behaviors the migration plan's Phase 0 requires, plus a conformance suite that evaluates them.
  • Commerce proof-of-fit (examples/commerce) demonstrating the engine outside a video application using only the public API.
  • Decision caching keyed by (profile, target) with targeted invalidation: report and mute changes invalidate only the targets they touch, while administrative, trust, policy, and viewer changes drop the whole cache. Cached decisions are deeply frozen so a consumer cannot corrupt later reads.
  • Performance fixture (tests/performance/) over 5,000 targets, 500 authors, and 100 trusted muters per author, asserting no network access, bounded subscription counts, and targeted invalidation.
  • Security requirements suite (tests/security/) covering the enumerated requirements testable at this layer.
  • @bitgate/widget: framework-agnostic custom elements. Viewer surfaces (bitgate-veil, bitgate-report, bitgate-status) and moderator surfaces (bitgate-capabilities, bitgate-action, bitgate-admin-panel), plus a runnable demo page. Elements render decisions and issue commands; they compute no policy.
  • Runtime wiring: storage persistence and hydration, signature verification in the load path, trusted mute-list subscriptions, community source resolution, root-policy application, import/export, and capability queries.
  • GPL-3.0-or-later LICENSE, README, and an integration guide.

Fixed

  • GovernanceAdminStore announced nothing when a roster change altered capabilities without changing any denial, leaving capability-gated UI and the decision cache stale.

  • The relay transport sent every subscription twice on connect: queued REQ frames were flushed and subscriptions were replayed from the map.

  • The commerce preset computed a visibility verdict at checkout, a surface that renders nothing, and could hide a listing from its own seller — leaving no route to appeal. Checkout now declares only transaction gates, and the seller dashboard caps visibility at a warning.

  • <bitgate-status> threw if a runtime returned an unusable decision.

  • ReportStore.ingest required a target field it never read.

  • Per-package types fields pointed at paths the build never produced, so consumers received no type declarations at all. Declarations now emit per package and resolve.

  • reduceAdminState accepted malformed target identifiers, because getTargetKey formats anything it is given.

  • community-user-deny fired for any contributor rather than only for community-curated sources, making a direct moderator action indistinguishable from a federated one.

  • composeDecisions kept only the first decision's evidence, so an object governed by several targets lost the evidence that drove its verdict.

  • OverrideStore.clear() shadowed the inherited listener clear(), leaking subscribers on teardown. Now clearOverrides().

  • GovernanceAdminStore emitted only when reduced denial state changed, so revoking a role that happened to deny nobody announced nothing — leaving capability-gated UI and the decision cache stale. It now also emits on authority changes.

  • ReportStore.ingest required a target field it never read, since the target is identified by the key argument.

  • The performance fixture was intermittently failing. Its setup repeated the same 100 mute-list writes 50 times with identical data, making construction 50x more expensive than the fixture it describes and pushing individual tests past their timeout under load. The fixture's shape is now asserted so it cannot silently degrade again.

  • The performance suite's wall-clock assertion was replaced with structural ones. A timing budget measured the machine as much as the code — it varied roughly 20x between idle and loaded runs here — so it now asserts that state is materialized once per batch, which is the actual regression that made a 5,000-target pass take ~47 seconds.

  • Evaluation was quadratic in state size: both the snapshot and its fingerprint were rebuilt for every target, so each call walked every report and mute list. A 5,000-target pass took ~47s. The runtime now memoizes the snapshot and its fingerprint until a store changes, and evaluateMany computes the fingerprint once for the batch. The same pass now takes ~85ms cold and ~6ms warm.

  • npm audit reported 5 vulnerabilities (1 critical, 2 high, 2 moderate), all from [email protected]'s dependency tree (vite → esbuild, postcss → nanoid). Dev-only and never shipped, since packages publish src and dist alone. Upgrading to vitest@3 clears all five with no test changes.

Changed

  • The evaluator no longer carries application thresholds. The previous invented defaults (5/3/10/5) are gone; applications supply category thresholds through a policy definition. The characterized reference values live in @bitgate/bitvid-compat as one application's profile.
  • createAuthorityState takes an options object and always grants the root every capability and protection.
  • Evaluation takes a GovernanceSnapshot and a ViewerState rather than a flat state object, and reads an injected clock instead of wall time.