Foundation Moderation
- Area: Foundation
- Path:
foundation/moderation - Kind: Content moderation, DMCA, fingerprint (RFC-001)
Role in the stack
koder-moderation powers the report-and-strike + fingerprint side of products/horizontal/play/engine (Koder Play). Reports come in via intake, get triaged into queues, accumulate strike weight on the offending channel, optionally fingerprint-match against a per-tenant Content-ID corpus, and wire into automod policy when classifiers fire above thresholds.
Deployed as koder-moderation-server (axum, default 0.0.0.0:8000); RUST_LOG controls tracing; /metrics exposed for Prometheus.
Substrate
- Intake: in-memory v0.1; production swap to
data/kdbkdb-record. SubmitArgs builder; classifier-signal injection per (targetkind, targetid). Reporter PII hashed at intake (#016):reporter_emailreporter_user_idare salt+SHA-256'd (hash_pii,with_saltMODERATION_REPORTER_SALT) before persist — raw identifiers never reach storage/audit; deterministic so dedup still works. - Strikes:
StrikeLedgerwith 90-day linear decay; severity per StrikeKind.prune_decayed()(#025) is the bulk decay job — drops fully-decayed (>90d) strikes + empties channels, weight-preserving; periodic scheduling is serve-time (likeClassifierCache::evict_stale). - Fingerprint:
FingerprintCorpusper tenant; Hamming similarity v0.1. - Extractor (MOD-010):
FingerprintExtractortrait +StubExtractor(always-on) + feature-gated chromaprintPDQ wraps (queued MOD-012#013). - LSH index (MOD-010):
LshIndexbuckets by 16-bit chunks for sub-millisecond candidate retrieval at million-entry corpus. - Classifier bridge (MOD-011):
Classifiertrait +LocalRuleClassifier(keyword fallback, conf cap 0.85) +ClassifierCache(24h TTL + max-size LRU, #024:DEFAULT_MAX_ENTRIES = 50_000,with_ttl_and_max, per-entryAtomicU64recency bumped under the read lock so reads stay concurrent;max_size 0= unbounded). - Automod:
AutoModPolicyTabledecides Ignore / ReviewQueue / AutoHide ondecide_many(labels).
Key features
- [x] Intake submit with triage in single call (MOD-001/#002).
- [x] Strike ledger with 90-day decay (MOD-003).
- [x] Automod policy table (MOD-004).
- [x] Fingerprint corpus + similarity scoring (MOD-005).
- [x] HTTP API + Prometheus + tracing (MOD-006#007#008).
- [x] Bench harness with SLO baseline (MOD-009).
- [x] Pluggable fingerprint extractor + LSH index (MOD-010 part A).
- [x] Pluggable classifier bridge + 24h cache (MOD-011 part A).
- [x] Per-tenant rate limit on
/v1/reports(MOD-014). - [x] Input validation: freetext ≤ 8 KB, evidenceurls ≤ 50 (MOD-015).
- [x] ModerationEventSink trait —
Submitted/AutoHidden/StrikeAdded/ReviewerDecision(MOD-017). - [x]
POST /v1/classifywrapsClassifier+ClassifierCache(MOD-018). - [x]
#[instrument]onintake.submit+automod.decide_many(MOD-019). - [x]
kdb_mod_intake_seconds+kdb_mod_strikes_active+kdb_moderation_rate_limit_drops_totalmetrics (MOD-020). - [ ] chromaprint cgo wrap (MOD-012 — blocked on libchromaprint-dev).
- [ ] PDQ cgo wrap (MOD-013 — blocked on Meta pdq-cpp).
SLO targets
- p99
intake.submit≤ 1 ms (non-classifier path). - Fingerprint match latency ≤ 30 s for 10-min upload.
- NSFW classification ≥ 95% precision / ≥ 90% recall (MOD-011 production gate).
Bench numbers: intake.submit = 219 ns; fingerprintlookup @ 100k = 10 ms; classifiercache hit = 42 ns; automod.decide_many = 36 ns. See services/foundation/moderation/bench/baseline.json.
Interfaces
- HTTP:
POST /v1/intake,POST /v1/fingerprint/match,POST /v1/strikes,POST /v1/queue/:id/resolve(#021 reviewer decision — hidedismissescalate;hidebumps the strike ledger + emitsReviewerDecision), corpus CRUD (#022):POST /v1/corpus·GET /v1/corpus?tenant=…·DELETE /v1/corpus/:work_id?tenant=…,GET /healthz,GET /metrics. - Library:
koder_moderation::{IntakeService, StrikeLedger, FingerprintCorpus, AutoModPolicyTable, LocalRuleClassifier, ClassifierCache, StubExtractor, LshIndex}.