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.
Follows bitlogin's layering: the engine stays headless and the widget is a
consumer of the published API, never a dependency of it. Building it was also a
test of that API — an element needing something unexposed would have been a
finding, the way the commerce example was.
Viewer elements: governance-veil (blur on restrict, disclosure on hide, warning
on warn), governance-report, governance-status. Moderator elements:
governance-capabilities, governance-action, governance-admin-panel.
Deliberate behaviors:
- hidden content leaves the accessibility tree rather than being visually
covered, so a screen reader does not read out what the viewer must not see
- a hidden decision renders a disclosure, not nothing: content that silently
vanishes is indistinguishable from a bug and leaves no route to appeal
- refused moderator actions stay visible and name the missing capability. A
hidden button teaches nothing about the authority model, and the command
layer enforces the same check independently — the UI is an explanation,
never the gate
- no element computes policy; wording maps from stable reason identifiers and
is overridable per application
Found while building it: GovernanceAdminStore emitted only when reduced denial
state changed, so revoking a role that denied nobody announced nothing, leaving
capability-gated UI and the decision cache stale.
The root tsconfig deliberately omits the DOM lib — that absence is what keeps
core, nostr, and runtime from reaching for browser globals — so the widget
carries its own DOM-aware config rather than relaxing the guarantee globally.
687 tests; lint, typecheck, and the declaration build pass from a cold checkout.
Proves the engine did not stay video-specific during extraction.
- application adapter contract in core (createApplicationAdapter,
evaluateObject, collectTargets): an object names the targets that govern it
and the strictest verdict reaching any of them wins, so denying a seller
takes their whole catalogue down with no cascade logic in the application
- examples/commerce: a marketplace on the public API covering all ten
proof-of-fit behaviors the plan requires, including that no core change was
needed. One product, one snapshot, four surfaces, four different verdicts.
- README, integration guide, per-example README, CHANGELOG, four ADRs, and
GPL-3.0-or-later LICENSE (the license was listed in Phase 1 but missing)
- checkpoints rewritten to reflect actual state and to say plainly which
migration-plan phases belong to the consumer repos rather than here
Fixes a real gap found by the proof-of-fit: composeDecisions kept only the
first decision's evidence, so an object governed by several targets lost the
evidence that drove its verdict. Evidence now merges — counts summed,
contributor lists unioned, flags or-ed.
556 tests; lint, typecheck, and per-package type build clean.