Pipeline di moderazione

Come Gemini, la quarantena e la revisione umana lavorano insieme per filtrare contenuti prima della pubblicazione.

Perché una pipeline a due fasi

I client non scrivono mai direttamente su poems. Ogni nuova poesia va in pendingPoems: uno staging invisibile al resto dell’app. Da lì una Cloud Function la consegna a Gemini 2.5 Flash e decide se promuoverla o metterla in quarantena. Stessa logica per commenti e foto profilo, con trigger diversi ma stesso contratto.

Separare staging e pubblicazione ha due vantaggi concreti: i client non conoscono l’esistenza di contenuti non approvati, e le Firestore rules possono proibire del tutto la scrittura diretta su poems.

Flusso di una poesia

client → pendingPoems/{id}
            ↓ onDocumentCreated
         Gemini moderation
         ├─ approved → poems/{id} (moderationPassed: true)
         │              ↓ sendNewPostNotification (follower push)
         └─ blocked  → quarantine + notifica autore ("contentHidden")

Il trigger sendNewPostNotification è anche lui su poems/{poemId}, ma controlla moderationPassed === true prima di procedere. Questo permette ai moderatori di forzare una pubblicazione manuale senza bypassare le notifiche.

Fail-open esplicito

Se Gemini non risponde — outage, quota, timeout — la poesia viene pubblicata comunque, ma marcata moderationDeferred: true con timestamp. Un utente non perde contenuto per problemi infrastrutturali non suoi; gli admin hanno un campo distinto da moderationPassed per eseguire un retry mirato, e i client più vecchi ignorano il flag aggiuntivo.

Il caso opposto — fail-safe — è riservato alle immagini: se una foto non può essere scaricata per essere analizzata, si blocca. Lasciar passare un’immagine non vista sarebbe più rischioso di perdere una pubblicazione.

Override moderatore

Il flag moderationOverride: true in pendingPoems segnala un’approvazione umana già avvenuta: la function salta la chiamata a Gemini e promuove direttamente. Serve per ripubblicare contenuti falsi-positivi senza pagare di nuovo la chiamata AI e senza rischiare un verdetto diverso alla seconda passata.

Commenti e foto profilo

  • Commenti — stesso pattern: scritti in pending, valutati, promossi o eliminati. La notifica al destinatario parte solo dopo la promozione (moderateComment rimanda esplicitamente il trigger sendCommentNotification).
  • Foto profilo — endpoint HTTP chiamato dal client subito dopo l’upload su Storage. Le funzioni scaricano i byte direttamente dal bucket e li passano a Gemini come inlineData, senza URL pubblici intermedi. Esiste anche un trigger moderateLegacyProfilePhoto su users/{uid} per intercettare cambi diretti al campo photoURL da client vecchi.

Revisione umana

L’admin panel e le segnalazioni utente si innestano nella stessa pipeline:

  • Ogni evento di moderazione scrive un log consultabile dai moderatori.
  • notifyModerationOutcome avvisa l’autore in-app quando il verdetto cambia (es. riabilitazione di contenuto prima bloccato).
  • notifyModeratorsNewReport manda un alert ai moderatori quando arriva una segnalazione utente.

Il risultato è un sistema in cui Gemini filtra il volume, e gli umani vedono solo i casi ambigui o segnalati.