The Trust & Safety worker behind Divine. It runs the human-in-the-loop review pipeline for uploaded 6-second videos — ingesting reports, giving moderators a dashboard to triage them, and pushing decisions back out to the relay, CDN, and Nostr. Divine's promise is no slop, all human, and this service is where a person makes the final call on borderline content.
It is a single Cloudflare Worker (divine-moderation-service) serving two custom domains:
moderation-api.divine.video— public and service-to-service API routes.moderation.admin.divine.video— the moderator dashboard, behind Cloudflare Access.
The service runs in reactive mode (REACTIVE_MODERATION_ONLY = "true"). Rather than machine-scanning every upload, it acts on signals that something needs a human look:
- Reports come in — from authenticated Divine clients (
POST /api/v1/report), from public NIP-56 (kind1984) report events polled offrelay.divine.video, and from NIP-17 report DMs sent to the moderation@ inbox. Each report is written to D1 as a review row. - A moderator triages — the admin dashboard presents a swipe-review queue of untriaged content, with video playback, uploader context, and any available provenance/AI signals.
- A decision is applied — the moderator picks an action, and the worker fans it out: it records the decision, publishes Nostr labels/reports, notifies the relay (ban/unban/delete via the relay-admin service binding) and Blossom (blob delete), and hands labels to the ATProto labeler webhook.
Moderation actions are SAFE, REVIEW, QUARANTINE, AGE_RESTRICTED, PERMANENT_BAN, and DELETE.
Because the service is report-driven, the queue consumer for the legacy video-moderation-queue currently acknowledges and skips every message. The automated multi-provider classifier pipeline still lives in the codebase (src/moderation/) and can be re-enabled, but it is not on the upload path in production.
- Human review dashboard — swipe-review queue, per-category verification (confirm/reject an individual detection), direct-message templates for creator communication, stats, and tunable classifier thresholds. Served behind Cloudflare Access on
moderation.admin.divine.video. - Report ingestion — authenticated client reports, inbound public NIP-56 (kind
1984) reports polled from the relay, and NIP-17 report DMs to the moderation@ inbox. Both Nostr paths are review-only and never auto-escalate, since client tags and reporter pubkeys are self-asserted public signals. A relay report is ingested whatever client it came from; the NIP-89clienttag only decides whether its reporter counts toward the distinct-reporter threshold the authenticated path escalates on — reports from clients outsideTRUSTED_REPORT_CLIENTS, like report DMs, do not. OnlyPOST /api/v1/reportcan produce an automaticAGE_RESTRICTED. - Content categories — nudity/NSFW, violence, gore, weapons, recreational drugs, self-harm, hate speech / offensive symbols, AI-generated, and deepfake, plus content-warning labels for alcohol, tobacco, medical, and gambling.
- AI-generation & provenance signals (surfaced to moderators as review context, not auto-enforcement):
divine-ai-detectorfor per-signal detection, with vendor fallback to Hive and Reality Defender.divine-inquisitorfor C2PA / ProofMode verification (inquisitor.divine.video).- Video Seal — interprets the neural-watermark payload extracted upstream;
0x01is reserved for trusted Divine attestations.
- Creator-delete pipeline — honors Nostr kind
5deletion requests from creators, removing their own content from Blossom. - Downstream publishing — NIP-56 reports to Faro and the content relay, NIP-32 (kind
1985) label events for human-verified labels, and a moderation@ NIP-17 DM inbox.
Everything runs inside one Worker (src/index.mjs), which exports three handlers:
fetch— the HTTP API and admin dashboard.queue— thevideo-moderation-queueconsumer (ack-and-skip under reactive mode).scheduled— cron work. The every-minute trigger runs the creator-delete pipeline and the lookup-column backfill; the every-5-minute trigger polls the relay for new video events and for inbound reports.
Storage and integrations, all bound in wrangler.toml:
- D1 (
BLOSSOM_DB, databaseblossom-webhook-events) — moderation results, reports, review rows, uploader enforcement, AI-detection events. Schema lives inmigrations/. - KV (
MODERATION_KV) — poller checkpoints, cached lookups, and operational markers. - Queue (
video-moderation-queue) — producer + consumer bindings retained for the legacy pipeline. - Service binding (
RELAY_ADMIN→divine-relay-admin-api-prod) — worker-to-worker ban/unban/delete-event RPCs, authorized with a shared key from Secrets Store. Bypasses the public edge and Cloudflare Access; falls back to the public HTTPS + CF Access path when the binding is absent.
Reports and creator-delete requests flow in from the Divine clients and the relay. Moderators act in this service. Decisions flow back out to the relay (divine-relay-manager / Funnelcake), the Blossom CDN, Nostr (labels and reports), and the ATProto labeler — so a single human decision is reflected everywhere the content is served.
Requires Node.js 22 and a Cloudflare account with access to the divine.video zone.
npm install # install dependencies
npm run dev # run the Worker locally with Wrangler
npm test # run the Vitest suite
npm run lint # custom repo lint pass
npm run tail # tail production logsTests are written with Vitest on @cloudflare/vitest-pool-workers. Every source module has a colocated *.test.mjs.
Bindings, routes, feature flags, and non-secret vars live in wrangler.toml. Highlights:
| Var | Purpose |
|---|---|
REACTIVE_MODERATION_ONLY |
"true" — queue consumer ack-skips; moderation is report-driven. |
MODERATION_ENABLED |
Master enable flag. |
CDN_DOMAIN |
Blossom CDN host for video access (media.divine.video). |
TEAM_DOMAIN |
Cloudflare Access team domain for Zero Trust JWT verification. |
RELAY_POLLING_* |
Relay video-event polling (URL, lookback, limit, enable). |
REPORT_POLLING_* |
Inbound NIP-56 report polling (URL, lookback, limit, max pages). |
TRUSTED_REPORT_CLIENTS |
Comma-separated NIP-89 client tags whose relay reports may drive automatic outcomes. Reports from any other client are still recorded for review, on a non-escalating source. |
CREATOR_DELETE_PIPELINE_ENABLED |
Enable the kind 5 creator-delete cron. |
AI_DETECTOR_BASE_URL / AI_DETECTOR_MODE_* |
divine-ai-detector endpoint and per-signal cutover mode. |
INQUISITOR_BASE_URL |
divine-inquisitor C2PA / ProofMode service. |
FUNNELCAKE_ADMIN_URL / FUNNELCAKE_LOOKUP_URL |
Relay admin (writes) and video-lookup (reads) hosts. |
*_THRESHOLD_HIGH / *_THRESHOLD_MEDIUM |
Legacy classifier thresholds, retained for provider/classifier tests. |
Secrets are set with wrangler secret put <NAME>. The ones the service reads (see the header of wrangler.toml for the full list):
SERVICE_API_TOKEN— bearer token for authenticatedmoderation-api.divine.videorequests.POLICY_AUD— Zero Trust application audience tag for the admin app.CF_ACCESS_CLIENT_ID/CF_ACCESS_CLIENT_SECRET— Cloudflare Access service token for the relay.NOSTR_PRIVATE_KEY— signs NIP-56 report events.MODERATOR_NSEC/NOSTR_RELAY_URL— signs and publishes NIP-32 human-moderator label events.ADMIN_PASSWORD_HASH— SHA-256 of the admin dashboard password (generate-admin-hash.mjs).ATPROTO_LABELER_WEBHOOK_URL/ATPROTO_LABELER_TOKEN— the divine-labeler webhook and its bearer token.REALITY_DEFENDER_API_KEY— secondary AI verification.SIGHTENGINE_API_USER/SIGHTENGINE_API_SECRET— optional, for the legacy classifier fallback.
RELAY_ADMIN_API_KEY is not a per-worker secret — it is bound from Secrets Store (MODERATION_TO_RELAY_ADMIN_KEY) and sent as the X-Admin-Key header on relay-admin calls. Hive secrets are intentionally unused by the upload and classification paths; do not set them without a reviewed Hive integration.
CI is defined in .github/workflows/ci.yml. On every pull request it runs lint and the test suite; on push to main it also deploys the Worker via cloudflare/wrangler-action using CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID repository secrets. semantic_pr.yml enforces Conventional Commit PR titles.
To deploy manually:
npm run deploy # wrangler deployBinding the Secrets Store secret requires the deploy token to have account Secrets Store: Write permission — see the note in wrangler.toml.
CONTENT_MODERATION.md— moderation strategy and the Video Seal provenance model.AGENTS.md— repository guidelines and contribution guardrails.CDN_INTEGRATION.md,CLOUDFLARE_ACCESS_SETUP.md,ADMIN_SETUP.md— integration and setup detail.CHANGELOG.md— notable changes.
Mozilla Public License 2.0. See LICENSE.
Part of Divine — your playground for human creativity · Brand guidelines