⚖️ 🛡️ ⚖️
⚖️

JUDGE DREDD

Moderação silenciosa e plugável — o feed tem lei.
Módulo de safety do vtp-webservices: sorteia aleatoriamente uma amostra de tudo que o aluno publica (texto, imagem, vídeo) e manda para análise de toxicidade, nudez e violência. Ninguém percebe que ele existe — nada é bloqueado, nada fica mais lento; só ficam os vereditos registrados para consulta.
vtp-webservices · specs/045-judge-dredd · fases 1–5 mergeadas · MODERATION_LEVEL=none em produção

🧨 O problema
Ponto de partida, em uma frase.

Hoje o UGC entra sem nenhuma análise

Post, comentário, clip, foto: tudo que um aluno publica entra na plataforma sem nenhuma verificação automática de segurança. O Judge Dredd fecha esse buraco sem tocar no core — a integração inteira são chamadas de 1 linha nos pontos de publicação. Se um dia quisermos agir (ocultar, revisar, notificar o produtor), a fundação já está pronta; a v1 só observa.

🚦 O que acontece quando alguém publica JudgeDredd::submitText() / submitMedia()
Todo o caminho abaixo é assíncrono e engolido em try/catch: qualquer falha do módulo nunca chega no write path de quem postou.
🪝1/5

Gancho de 1 linha 4 pontos de publicação

O core não conhece o módulo — ele só avisa que algo nasceu.

  • AddCommentAction — comentário
  • PostAppController — post do aluno
  • CreateShortAction — clip / reel
  • ValidateUploadedObjectAction — imagem e vídeo enviados
  • Remover o módulo é apagar 4 linhas: não deixa rastro
🎲2/5

Sorteio SamplingDecider

O ModerationConfig lê o nível global e o SamplingDecider tira a sorte (fonte criptográfica) com a probabilidade daquele tipo de mídia. Ninguém sabe quando será olhado — dissuasão sem o custo de analisar tudo.

  • Em none o JudgeDredd retorna antes de tocar o container: zero query, zero chamada externa, custo zero
  • Nível inválido no env cai para none (falha segura)
📮3/5

Fila ModerationScanJob · queue low

Sorteado, vira um moderation_scan pendente e um job na fila de baixa prioridade. Quem postou já seguiu a vida — a análise roda depois, e o job é idempotente (reprocessar não duplica veredito nem custo).

🔬4/5

Análise provider por tipo de mídia

Providers plugáveis atrás de contratos (TextModerationProvider / MediaModerationProvider) — o default é null, então nada quebra se ninguém estiver configurado.

  • Texto → OpenAI omni-moderation (gratuito, multilíngue). O Amazon Comprehend foi reprovado no gate: DetectToxicContent só aceita en. Teste real em PT-BR: frase tóxica → harassment 0.979; frase inocente → clean
  • Imagem → Rekognition DetectModerationLabels, enviada em Bytes (≤ 5 MB), cross-region us-east-1
  • Vídeo → Rekognition assíncrono StartContentModeration: exige o objeto na região do Rekognition, então o job copia para o bucket de staging vtp-moderation-staging (us-east-1, lifecycle de 1 dia) e vai buscando o resultado em polls agendados
  • Só o excerpt (2000 chars) é guardado e analisado — teto de evidência por item
🧾5/5

Veredito + ledger append-only

Nada é bloqueado nem ocultado: o resultado só é registrado.

  • moderation_scansclean / flagged, labels e scores, com campos já prontos para uma revisão humana futura
  • moderation_usage_ledger — custo por análise, para saber quanto o nível escolhido está custando
  • Threshold de flag: score ≥ 0.5

👀 Consulta (admin, read-only)

Fase 5: os vereditos são consultáveis pela API admin — ainda sem tela no painel.

GET /api/v1/admin/moderation/scans
GET /api/v1/admin/moderation/scans/{scan}
GET /api/v1/admin/moderation/usage
🎚️ Níveis de vigilância MODERATION_LEVEL
Uma variável de ambiente controla a probabilidade de cada publicação ser sorteada. Vídeo tem probabilidade menor de propósito — é o único item que custa caro.
none
0%
desligado — nenhuma chamada, custo zero (default em todo lugar)
slow
10%
ronda leve · vídeo 5%
medium
30%
equilíbrio do dia a dia · vídeo 15%
high
70%
vigilância reforçada · vídeo 40%
full
100%
tudo analisado, sem sorteio
🧭 Princípios (decisões fechadas)
O que o módulo promete — e o que ele deliberadamente não faz na v1.

🤫 Silencioso

Não bloqueia, não oculta, não devolve erro. Quem publica não nota absolutamente nada — a v1 só gera scan e veredito.

🎲 Aleatório

Amostragem por sorteio, não por regra visível. Ninguém consegue prever o que será analisado.

🛡️ Inofensivo por design

Qualquer exceção do módulo é engolida. O post nunca é afetado por um problema de moderação.

🧾 Auditável

Cada análise registra veredito e custo em ledger próprio, append-only.

🔌 Plugável

Tudo vive em app/Support/Moderation/, no molde da plataforma de IA. Sai do projeto sem cicatriz.

🗣️ Vocabulário próprio

scan e verdict — nunca "report", que já é outra coisa no sistema (Report/ReportExport).

🔌 Motores externos
Cross-region us-east-1: o Rekognition não existe em sa-east-1.
🧠 OpenAI omni-moderation · texto — toxicidade, ódio, assédio, violência (grátis)
🖼️ Amazon Rekognition · imagem — DetectModerationLabels (Bytes ≤ 5 MB)
🎬 Amazon Rekognition Video · Start/GetContentModeration + bucket de staging
📮 SQS · fila low, análise fora do request
🪣 S3 · vtp-moderation-staging[-dev], us-east-1, lifecycle 1 dia
💵 Custo
Em none o custo é literalmente zero — nem query o módulo faz.
US$ 0
por análise de texto — o endpoint de moderação da OpenAI é gratuito
US$ 0,001
por imagem analisada (Rekognition)
US$ 0,10
por minuto de vídeo analisado (Rekognition)
≈ US$ 1–4/mês
tenant típico em medium — a conta é praticamente só o vídeo
📌 Onde está hoje
Piloto completo — texto, imagem e vídeo — validado no dev.

✅ Fases 1 a 5 mergeadas

  • Fundação e sorteio · #188
  • Scans persistidos, job e texto · #189
  • Imagem via Rekognition · #190
  • Vídeo com bucket de staging · #192
  • Endpoints admin read-only · #191

🧪 Validado ponta a ponta no dev

  • Comentário tóxico → flagged (harassment 0.979); comentário inocente → clean
  • Smoke de vídeo → SUCCEEDED
  • Migrations rodadas nos 12 tenants de dev

🔭 Backlog (fase 6)

  • Live: StorePollingCommentController cria comentário sem passar pelo gancho
  • Vídeo de post/clip tem upload próprio, ainda fora da moderação
  • Permissão granular moderation.view + tela no painel
  • Nível por tenant (hoje é global por ambiente)
Como ligar (quando quisermos): é só trocar variável de ambiente — nenhum deploy de código é necessário.
MODERATION_LEVEL=slow            # none | slow | medium | high | full
MODERATION_VIDEO_STAGING_BUCKET=vtp-moderation-staging   # obrigatório em produção
⚠️ Em produção ele nasce desligado. MODERATION_LEVEL tem default none em todo lugar, de propósito: o piloto só observa e reporta. E lembrar do gotcha de processo avulso — rodar tinker contra o módulo exige REDIS_HOST=redis (circuit breaker).