The engine already honored requireAllowlist + allowlistMiss, and the commerce example configured a seller allowlist by hand -- but there was no reusable, named "only show allowlisted publishers" preset, the symmetric twin of the denylist. Integrators (Tessera) punted on allowlist mode for lack of one. Add ALLOWLIST_POLICY (registry name "allowlist"): a publisher is shown only when an authorized actor has contributed a user-allow for them, and every unlisted publisher misses and is hidden across all four dimensions. Administrative denials still apply on top, and protected actors (root, moderators) are never gated -- the people who curate the list can always see. Populate userAllow exactly as the denylist: signed contributions resolved from relays (e.g. NIP-32 allow labels). Exported from @bitgate/core, registered in POLICY_PRESETS, documented in the README, and covered by presets.test.js (unlisted hidden, allowlisted shown, protected bypasses). Addresses the "allowlist / whitelist mode" item of the Tessera integration feedback (docs/TODO.md). Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01BXQCvJPA6sVSQ5j8uBd9bB
12 KiB
BitGate — open items
Updated: 2026-08-10
BitGate is the standalone governance and policy engine in this repository. The
existing @bitgate/core, @bitgate/nostr, @bitgate/runtime, @bitgate/verify,
@bitgate/testing, @bitgate/widget, and compatibility packages are existing
surfaces. The distribution and API work below must be additive and must not
break current BitGate consumers or the behavior characterized from BitVid and
BitRoad.
Priority 1 — Distribution
- Guarantee one browser-ready standalone build:
bitgate.js.- Require no npm at runtime.
- Require no runtime dependencies.
- Work directly from vanilla HTML.
- Publish predictable downloadable release artifacts.
- Provide both an ES module and an npm package.
- Verify the standalone build works from ordinary static HTTPS hosts and localhost development without requiring a framework or bundler.
- Document
file://limitations and the recommended local development server path.
Priority 2 — Canonical API
-
Establish one obvious entry point, for example:
const decision = await gate.evaluate({ viewer, item, action: "view" }); -
Standardize decision output:
{ visibility, ranking, interaction, transaction, reasons, evidence } -
Keep the four decision axes independent:
- visibility
- ranking
- interaction
- transaction
-
Do not collapse all policy outcomes into
allowed/blocked. -
Define stable machine-readable reason codes, for example:
USER_MUTEDFOLLOWED_USER_REPORTEDMALWARE_SIGNALSPAM_SIGNALPOLICY_BLOCK
-
Define how any legacy adapter verdicts map onto the canonical four-axis decision without losing information.
-
Define deterministic behavior when an axis is not applicable to an action, such as transaction policy for a read-only view.
Priority 3 — Shared Context
-
Accept a standardized viewer context:
{ pubkey, follows, mutes, relays, trustGraph, interests } -
Avoid requiring BitGate-specific conversions when data already comes from BitLogin or Nostr.
-
Define optional versus required context fields and safe defaults for missing, stale, or unavailable context.
-
Keep context normalization separate from policy evaluation so applications can pass through native BitLogin/Nostr structures without needless copying.
-
Define privacy boundaries for context: do not require raw viewer identity, cross-site tracking data, or unnecessary behavioral history.
Priority 4 — Policies
- Formalize the
bitgate/1policy schema. - Make policies serializable, hashable, signable, publishable, and forkable.
- Allow policies to be loaded from signed Nostr events.
- Verify policy signatures, schema versions, hashes, and issuer metadata before using a policy; fail safely when verification is ambiguous.
- Support named policy profiles, for example:
socialcommentsmarketplace-browsemarketplace-checkoutmessaging
- Keep app-specific policy configuration outside the core engine.
- Define policy precedence, composition, conflict resolution, and explicit override rules when multiple policies apply.
- Make policy evaluation deterministic and testable for the same input, policy set, and evaluation time.
- Document policy expiry, revocation, cache behavior, and offline behavior.
Priority 5 — Explainability
- Make every decision explainable.
- Return machine-readable reasons.
- Return optional human-readable explanations without making UI copy part of the stable machine contract.
- Support
gate.explain(decision). - Make it straightforward for applications to answer:
- “Why was this hidden?”
- “Why can't I reply?”
- “Why is checkout disabled?”
- Include enough evidence references to audit a decision without exposing unnecessary private viewer data.
- Distinguish the policy reason, the evidence source, and the resulting action; do not imply that a signal is proof when it is only a heuristic.
- Define stable explanation behavior for conflicting signals, unavailable evidence, expired policies, and fail-open/fail-closed choices.
Priority 6 — Documentation
- Add a vanilla HTML example.
- Add a BitLogin + BitGate example.
- Add a social feed example.
- Add a commerce example.
- Document the difference between:
- hide
- downrank
- warn
- interaction deny
- transaction deny
- Document the canonical API in relation to the existing package APIs and how current consumers migrate without a forced rewrite.
- Document the security and privacy boundaries of local policy evaluation, signed Nostr policies, and remotely supplied evidence.
Compatibility and testing gates
- Preserve existing package APIs while introducing the canonical BitGate API.
- Preserve the behavior characterized from BitVid and BitRoad during extraction and future policy-engine changes.
- Add unit tests for canonical decision-axis independence, stable reason codes, legacy verdict mapping, policy precedence, and explainability.
- Add tests for signed policy verification, malformed policies, unknown schema versions, expired policies, and ambiguous evidence.
- Add browser integration tests for the standalone
bitgate.jsartifact from vanilla HTML. - Test the npm package and ES-module import paths from a clean consumer project.
- Test the definition-of-done examples against the published artifact.
- Run the existing build, typecheck, and test suite before and after changes; standalone work must not replace or mutate current package output.
Definition of done
A developer can drop bitgate.js into a page, pass it a viewer, item, and
action, and receive a consistent, explainable policy decision without needing
to understand BitGate internals. The same canonical engine is available as an
ES module and npm package, while existing BitGate consumers continue to work
unchanged.
Open design decisions
- Choose the exact
bitgate/1event kind and signed-policy event format. - Choose the canonical TypeScript types and runtime validation strategy.
- Decide whether
gate.explain(decision)is pure/local only or may resolve additional evidence asynchronously. - Decide default fail-open/fail-closed behavior per axis and per action.
- Decide how policy profile discovery works without creating a centralized authority or leaking private application context.
- Decide whether standalone output is generated from the canonical engine directly or from a separately maintained browser entry point.
- Decide how browser builds expose version and integrity metadata.
BitBlocks App Kit compatibility layer
Do this only after BitLogin, BitGate, and BitFeed each have clean standalone APIs. BitBlocks is an optional integration layer, not a replacement for any of the three libraries. It must not merge their implementations or make an app depend on the kit when it only needs BitGate.
-
Create a tiny optional BitBlocks integration layer.
-
Keep BitLogin, BitGate, and BitFeed as separate replaceable libraries; the App Kit must compose them rather than absorb them.
-
Provide shared initialization, for example:
const app = await BitBlocks.create({ login: {}, gate: {}, feed: {}, relays: [...] }); -
Standardize the viewer context exchanged between the components without changing BitGate's policy engine, signed-policy format, or four-axis decision contract.
-
Centralize optional common Nostr plumbing in the kit: relay connections, subscriptions, EOSE handling, reconnects, deduplication, timeouts, publishing, OK responses, profile lookup, follow-list lookup, and mute-list lookup.
-
Expose
app.viewer,app.signer,app.relays,app.gate, andapp.feedthrough documented interfaces. -
Keep each underlying library independently usable and independently versioned.
-
Do not make applications depend on BitBlocks when they use only BitGate's policy and governance capabilities.
-
Add integration tests proving the kit is an adapter over the standalone APIs, not a second implementation of identity, policy, or feed logic.
BitBlocks definition of done
A developer or AI agent can take bitlogin.js, bitgate.js, bitfeed.js, and
bitblocks.js and build a functional static Nostr application with almost no
infrastructure or integration boilerplate, while each individual library
remains usable without BitBlocks.
Feedback from Tessera building a moderation layer (2026-08-11)
Tessera needed exactly what BitGate provides (moderation as policy over public
Nostr events) but ended up building a client-side stand-in — a kind-33604
"moderation decision" event carrying the four dimensions, ladder-max
composition, a signed root-controlled moderator set, and a browser console —
rather than integrating @bitgate/*. Notes on why, and what would have made
adoption the obvious choice.
Why we didn't reach for BitGate first
- No obvious "just the engine + events over Nostr" path for a static site.
We wanted: moderators publish signed decisions to relays; every client
resolves the honored set and composes decisions ladder-max; enforce in the UI.
That is precisely BitGate's model, but the on-ramp we found was the
<bitgate-provider>/<bitgate-veil>widgets, which assume you're veiling DOM content, not making install/transaction decisions in app logic. A documented recipe for "headless@bitgate/core+ your own decision events + your own enforcement" would have won us over. - The decision event format is ours, not yours. We invented a 33604 shape because we didn't find a canonical BitGate "decision/label event" to emit and consume over relays. If BitGate defines (or blesses a NIP-32-based) wire format for a signed decision across the four dimensions, other clients — including ours — could interoperate instead of each inventing one.
High-value asks
- Headless quickstart:
@bitgate/corecomposing decisions from an array of events + a viewer + a policy, with zero DOM. Show thetransactiondimension gating a non-visual action (install/checkout), which is where the four- dimension model really shines and where ahiddenboolean can't reach. - Moderator-set as data. Tessera made "who is a moderator" a signed, root-controlled kind-30000 set resolved at runtime, so moderators can be added in-app without a redeploy. A first-class BitGate primitive for the honored-moderator/authority set (with delegation) would be broadly useful.
- [DONE 2026-08-11] Allowlist / whitelist mode. The engine already honored
requireAllowlist+allowlistMiss(evaluator.js), and the commerce example configured a seller allowlist by hand — but there was no reusable named preset. ShippedALLOWLIST_POLICY(registry nameallowlist): only publishers with auser-allowcontribution are shown, every unlisted one is hidden across all four dimensions, administrative denials still apply on top, and protected actors (root/mods) are never gated. Symmetric to the denylist — populateuserAllowthe same way (signed contributions / NIP-32 allow labels). Covered by presets.test.js (unlisted hidden, allowlisted shown, protected bypasses); surfaced in the README preset list.
Medium
- Ship
@bitgate/coreas a standalone ESM single-file (like BitFeed'sstandalone) so a no-build static site can vendor it the way we vendor@tessera/core. Dependency-free + no build step was a hard requirement for us. - A worked example of BitGate feeding a feed ranking (the
rankingdimension → a score multiplier) would have saved us reinventing it in the feed recipe.