mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-27 08:31:00 +00:00
BitGate read standard events but published a private vocabulary, making it an
island. NIP-32 (kind 1985) is the shared moderation vocabulary; BitGate now
reads and writes it. See ADR 0007.
The design keeps flexibility central, since the goal is a reusable building
block. A label is (namespace, value, target) and carries no built-in meaning —
"deny", "nsfw", and "MIT" are the same kind of object. What a label means is a
policy decision an application supplies via labelMapping { namespace,
denyValues, allowValues }. The codec itself is vocabulary-free; decodeLabels
returns every label regardless of namespace for generic use.
A matching label becomes a contribution authored by the labeller, gated
identically: it denies someone only if the labeller holds the capability in the
roster, exactly like a community source. An untrusted labeller's deny does
nothing, and labels fail closed without verification like any administrative
event.
- consume: ingestEvent maps kind 1985; loadLabels(labellers) fetches them
- emit: commands.publishLabel(target, value), gated by the same capability as
denying, so the shared form is no weaker than the private one
- the widget bundle re-exports the label API for the drop-in path
Validated against 40 real kind:1985 events from public relays (ISO-639-1
language labels among them): all decoded with targets and namespaces, zero
throws. 897 tests; cold gates green; audit clean.
63 lines
1.9 KiB
Markdown
63 lines
1.9 KiB
Markdown
# Interoperable moderation with NIP-32
|
|
|
|
BitGate reads and writes NIP-32 labels (kind 1985), so a deployment's
|
|
moderators can be honoured by other Nostr clients, and BitGate can act on
|
|
labels published by others.
|
|
|
|
## The model
|
|
|
|
A label is `(namespace, value, target)`. It carries no built-in meaning — a
|
|
`"deny"` label and an `"nsfw"` label are the same kind of object. What a label
|
|
means to *your* application is a policy decision you supply:
|
|
|
|
```js
|
|
createBitGate({
|
|
namespace: "myapp",
|
|
labelMapping: {
|
|
namespace: "com.myapp.moderation", // only labels in this namespace map
|
|
denyValues: ["deny", "spam", "scam"], // your vocabulary for "hide this"
|
|
allowValues: ["trusted"],
|
|
},
|
|
…
|
|
});
|
|
```
|
|
|
|
A label matching that mapping becomes a **contribution**, authored by the
|
|
labeller. It denies someone only if the labeller holds the capability in your
|
|
roster — exactly like a community source. An untrusted labeller's `deny` does
|
|
nothing.
|
|
|
|
## Consuming a third-party labeller
|
|
|
|
Add the labeller to the roster with a contribution capability, then fetch their
|
|
labels:
|
|
|
|
```js
|
|
runtime.admin.setRoles({
|
|
root: ROOT,
|
|
roles: { external_labeller: ["contribute-user-deny", "contribute-event-deny"] },
|
|
actors: { [ROOT]: ["super_admin"], [LABELLER_PUBKEY]: ["external_labeller"] },
|
|
});
|
|
|
|
await runtime.loadLabels([LABELLER_PUBKEY]);
|
|
```
|
|
|
|
## Publishing labels
|
|
|
|
A moderator publishes a label with the same capability that lets them deny
|
|
directly:
|
|
|
|
```js
|
|
await commands.publishLabel({ type: "user", pubkey }, "deny");
|
|
await commands.publishLabel({ type: "event", id }, "nsfw", { content: "graphic" });
|
|
```
|
|
|
|
Other clients that understand your namespace can then act on it.
|
|
|
|
## Generic use
|
|
|
|
The codec is not BitGate-specific. `decodeLabels(event)` returns every label
|
|
(namespace, value, targets) regardless of vocabulary, so an application can
|
|
consume topic or licence labels directly without going through the allow/deny
|
|
mapping.
|