diff --git a/AGENTS.md b/AGENTS.md index 93930038..1a255fc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,6 +72,21 @@ Document the run in PR descriptions so QA can cross-reference results. --- +## Moderation Quickstart (Bitvid) + +1. Read `docs/moderation/README.md` for the policy overview. +2. Parse NIP-56 reports and compute `trustedReportCount` using **only F1 followers**. +3. Apply defaults: blur thumbnail at ≥3 F1 `nudity` reports; block autoplay at ≥2. +4. Wire NIP-51 lists — `10000` mute (downrank/hide) and `30000` admin lists (opt-in hard-hide/whitelist). +5. Add a reason chip plus “show anyway” control to every blurred item. +6. Use NIP-45 COUNT when available; render graceful fallbacks when relays omit it. +7. Keep reputation gating out of Home; allow it in Discovery behind a toggle. +8. Run the scenarios in `docs/moderation/testing.md` before shipping. +9. Log moderation decisions locally for debugging without leaking PII. +10. Record a ≤90s demo showing blur/hide/override flows when submitting major moderation work. + +--- + ## 7. Content Schema v3 & Playback Rules * **Event payloads:** New video notes serialize as version `3` with the JSON shape: diff --git a/docs/moderation/README.md b/docs/moderation/README.md new file mode 100644 index 00000000..dc0b0629 --- /dev/null +++ b/docs/moderation/README.md @@ -0,0 +1,33 @@ +# Bitvid Moderation Overview + +## What we’re building +Bitvid is follow-centric. Your Home feed comes from people you follow (F1). Discovery can optionally expand to friends-of-friends (F2) and ranked sources. Moderation is **client-side** and **user-controlled** with optional admin lists. + +## Core principles +- **Freedom to choose**: User picks filters. Defaults are safe but reversible. +- **Explain decisions**: Every blur/hide shows a “why” badge and a “show anyway” control. +- **Whitelist > blacklist**: Prefer showing content from trusted networks over fighting global spam. +- **Minimal central power**: Admin lists are opt-in; users can unsubscribe. + +## Threat model (short) +- Spam/bots, impersonation, malware, NSFW thumbnails, illegal content, brigading/dogpiles, Sybil report attacks. + +## Building blocks (Nostr) +- **Reports**: NIP-56 (`kind 1984`) with types like `nudity`, `spam`, `illegal`, `impersonation`, etc. +- **Lists**: NIP-51 (mute list 10000, categorized people 30000, bookmarks 30001). +- **Replies/threads**: NIP-10 (comments). +- **Counts**: NIP-45 (relay COUNT; optional fallbacks). + +## Defaults (policy) +- Blur video thumbnails if **≥ 3** F1 friends report `nudity`. +- Disable autoplay preview if **≥ 2** F1 friends report `nudity`. +- Downrank author when any F1 has them in mute list (10000). +- Opt-in admin lists (30000 with `d=bitvid:admin:*`) can hard-hide content. + +> You can override all defaults in **Settings → Safety & Moderation**. + +## Files in this folder +- `web-of-trust.md` — how we compute trust signals and thresholds +- `nips.md` — exact NIPs and kinds Bitvid uses +- `relays.md` — relay compatibility and COUNT fallbacks +- `testing.md` — QA checklist + test vectors diff --git a/docs/moderation/nips.md b/docs/moderation/nips.md new file mode 100644 index 00000000..6907e195 --- /dev/null +++ b/docs/moderation/nips.md @@ -0,0 +1,22 @@ +# NIPs Bitvid uses (Moderation-Relevant) + +## Required +- **NIP-56 — Reporting** + `kind 1984` events with a `type` tag such as `nudity`, `spam`, `illegal`, `impersonation`, `malware`, `profanity`, `other`. + Client may act on **reports from friends** (our trusted set). + +- **NIP-51 — Lists** + - `10000` mute list (authors to mute). + - `30000` categorized people (used for admin/curation lists). + - `30001` bookmarks (not moderation, but used elsewhere). + +- **NIP-10 — Replies & Mentions** + Threading for comments; moderation badges appear in the thread UI. + +## Recommended +- **NIP-45 — COUNT** + Event counts; we use it opportunistically for faster totals with graceful fallback. + +## Nice-to-have +- **NIP-36 — Sensitive Content / Content Warning** + If present, we pre-blur content even without reports; user can “show anyway”. diff --git a/docs/moderation/relays.md b/docs/moderation/relays.md new file mode 100644 index 00000000..78931f4a --- /dev/null +++ b/docs/moderation/relays.md @@ -0,0 +1,25 @@ +# Relay Compatibility & Fallbacks + +## COUNT (NIP-45) +- Use COUNT when available to fetch totals efficiently. +- If a relay doesn’t support COUNT: + - Show “—” placeholder. + - Fall back to client-side counting when cheap (narrow queries only). + - Avoid heavy per-relay scans; cap by time window and result size. + +## Query hygiene +- Prefer specific filters: `authors`, `kinds`, `#e`, `#p`, `since/until`. +- Batch requests across relays; debounce UI updates. +- Cache viewer’s F1 set (pubkeys you follow) and their report summaries. + +## Media/thumbnail considerations +- Never block media fetch solely on relay count results. +- Thumbnails respect blur rules locally; “show anyway” toggles do not re-query relays. + +## Timeouts & errors +- Per-relay timeout (e.g., 1500–2500 ms); proceed with partial results. +- Surface a subtle “partial results” indicator when some relays fail. + +## Privacy notes +- Do not leak the viewer’s F1 set to third-party endpoints. +- If using a reputation provider, pass pubkeys in batches; avoid attaching viewer identity unless needed. diff --git a/docs/moderation/testing.md b/docs/moderation/testing.md new file mode 100644 index 00000000..ebf42d84 --- /dev/null +++ b/docs/moderation/testing.md @@ -0,0 +1,71 @@ +# QA & Test Vectors (Moderation) + +## Quick checklist +- [ ] F1 set cached and used for “trusted reports”. +- [ ] Blur at ≥3 F1 reports of `nudity`; autoplay blocked at ≥2. +- [ ] “Show anyway” works and records only local pref. +- [ ] Mute list (10000) downranks/hides consistently. +- [ ] Admin lists (30000) take effect only when subscribed. +- [ ] COUNT fallback shows placeholders and doesn’t crash UI. +- [ ] Comment threads render with NIP-10; moderation badges show in context. +- [ ] Reports from muted reporters are ignored. + +## Fixtures (create on test relays) + +### A) NIP-56 report events +Create three distinct F1 reporter keys; each sends: + +```json +{ + "kind": 1984, + "tags": [ + ["e", "", "nudity"], + ["p", ""] + ], + "content": "test: thumbnail too revealing" +} +``` + +Expected: + +* After 1 report → no blur; autoplay allowed. +* After 2 reports → autoplay blocked. +* After 3 reports → thumbnail blurred; reason chip shown. + +### B) Mute list (10000) + +```json +{ + "kind": 10000, + "tags": [ + ["p", ""] + ] +} +``` + +Expected: author’s items are downranked/hidden for this viewer. + +### C) Admin blacklist (30000) + +```json +{ + "kind": 30000, + "tags": [ + ["d", "bitvid:admin:blacklist"], + ["p", ""] + ] +} +``` + +Expected: when viewer subscribes to this list, author is hard-hidden. + +### D) COUNT fallback + +* Use a relay that doesn’t support COUNT. +* Verify “—” placeholders; no spinner loops; no crashes. + +## Automated checks (suggested) + +* Unit test `trustedReportCount`. +* Integration test: emit events over a local relay; assert UI state. +* Regression test: ensure Home feed ignores reputation gating. diff --git a/docs/moderation/web-of-trust.md b/docs/moderation/web-of-trust.md new file mode 100644 index 00000000..4a0902e3 --- /dev/null +++ b/docs/moderation/web-of-trust.md @@ -0,0 +1,79 @@ +# Web-of-Trust Policy (Bitvid) + +## Graph terms +- **F1 (friends)**: pubkeys you follow. +- **F2**: friends-of-friends (off by default for Home; allowed for Discovery). +- **Trusted report**: a NIP-56 report from an F1 account. + +## Signals we use +- NIP-56 reports by type (`nudity`, `spam`, `illegal`, `impersonation`, etc.). +- NIP-51 lists: + - 10000 mute list → downrank/hide author content. + - 30000 categorized people → optional admin lists (see below). +- (Optional) reputation score from a reputation source (e.g., PageRank/DVM) for **Discovery** only. + +## Default thresholds (can be tuned) +- `blurThumbnail = trustedReportCount(event,'nudity') >= 3` +- `hideAutoplay = trustedReportCount(event,'nudity') >= 2` +- `downrankIfMutedByF1 = true` + +### Why these numbers? +- F1-only reports resist Sybil attacks. +- Blur is reversible; hiding autoplay reduces accidental exposure. + +## Admin lists (opt-in) +- We recognize curated lists using `30000` events: + - `['d','bitvid:admin:blacklist']` → hard-hide when subscribed. + - `['d','bitvid:admin:whitelist']` → always show in Discovery. + - `['d','bitvid:admin:editors']` → trusted channel editors. +- Users can subscribe/unsubscribe any time. + +## Pseudocode + +```ts +type Hex = string; + +interface Report { + reporter: Hex; // F1 must include this key + type: 'nudity'|'spam'|'illegal'|'impersonation'|'malware'|'profanity'|'other'; + targetEvent: string; +} + +function trustedReportCount(eventId: string, type: Report['type'], viewerFollows: Set, reports: Report[]): number { + return reports.filter(r => r.targetEvent === eventId && r.type === type && viewerFollows.has(r.reporter)).length; +} + +function shouldBlurThumb(eventId: string, ctx: Ctx): boolean { + return trustedReportCount(eventId, 'nudity', ctx.viewerFollows, ctx.reports) >= 3; +} + +function shouldHideAutoplay(eventId: string, ctx: Ctx): boolean { + return trustedReportCount(eventId, 'nudity', ctx.viewerFollows, ctx.reports) >= 2; +} +``` + +## UI rules + +* Always show a reason chip: e.g., `Blurred · 3 friends reported “nudity” · Show anyway`. +* Respect per-viewer choices (global off, per-channel off). +* Never bury the override. + +## Discovery (optional reputation) + +* Home feed: **never** gated by reputation. +* Discovery/Trending: may require a minimum score (pluggable `ReputationSource`). +* Keep a toggle in Settings to disable reputation gating. + +```ts +interface ReputationSource { + // returns 0..1 rank per pubkey for a given perspective + rank(pubkeys: Hex[], perspective?: Hex): Promise>; +} +``` + +## Anti-abuse hardening + +* Count unique F1 reporters only (dedupe by pubkey). +* Minimum account age or minimum “being-followed” count for reporters (optional). +* Rate-limit rapid report bursts per reporter. +* Ignore reports from muted or blocked reporters.