Skip to main content
Prima della v0.18.16, i controlli di sanità delle campagne erano ad-hoc — export CSV e script una tantum eseguiti manualmente prima di un invio rischioso. Il Campaign safety preflight (BORD-699/700/701) sostituisce questo approccio con un record di database durevole e auditabile di cosa è stato controllato, quando, e su quale esatto insieme di destinatari — e una protezione al lancio opt-in che può rifiutare di lanciare una campagna il cui preflight non è passato.
Il preflight è disattivato per impostazione predefinita. Le campagne esistenti vengono lanciate esattamente come prima, a meno che i metadati di una campagna non abilitino esplicitamente l’opt-in tramite launchSafety.requireSafetyPreflight.

Cosa fa un’esecuzione di preflight

runCampaignSafetyPreflightInSupabase esegue un unico passaggio deterministico sui destinatari in coda (queued) di un batch di campagna:
  1. Risolve le campagne nell’ambito — sia ogni campagna che condivide una batchKey (una “famiglia” di campagne, associata tramite campaigns.metadata->>'batchKey'), sia un elenco esplicito di id di campagna.
  2. Carica i campaign_recipients in coda per quelle campagne, uniti alle colonne dei contatti rilevanti per la sicurezza (consent_state, subscriber_status, deleted_at, deletion_requested_at, metadata).
  3. Apre una riga di audit (campaign_safety_audits, stato running).
  4. Esegue ogni gate deterministico registrato sull’insieme di destinatari e raccoglie i finding.
  5. Persiste i finding (campaign_safety_audit_findings) e completa l’audit — scrivendo un recipient_set_hash sugli id ordinati dei destinatari e i conteggi di severità aggregati, quindi porta lo stato a completed.
  6. In caso di qualsiasi errore dopo l’apertura della riga di audit, l’audit viene marcato failed con il motivo dell’errore, e l’errore viene ri-lanciato — un preflight non riporta mai silenziosamente successo in caso di crash.

Gate rilasciato: DNC

L’unico gate attivo oggi è dncGate — un controllo autonomo basato esclusivamente sui dati Switchbord. Segnala un destinatario come do-not-contact (severità dnc, tipo di finding dnc_opted_out) quando una qualsiasi delle seguenti condizioni è vera, registrando quali di esse hanno avuto riscontro come evidenza:
  • consent_state === "opted_out"
  • subscriber_status === "unsubscribed"
  • il contatto ha deleted_at impostato
  • il contatto ha deletion_requested_at impostato
L’adapter ha un punto di estensione documentato per gate con identità espansa (Travio/Neo4j/UDB prior-outreach, riscontri sullo status di viaggio, fuzzy matching sui pax — tracciati come BORD-702/703/704/705). Questi gate necessitano di un contesto risolto per identità più ricco di quello fornito dal solo insieme di destinatari e sono intenzionalmente fuori ambito per questo preflight iniziale.

Scala di severità

I finding hanno una delle sette severità, dalla più alla meno grave:
Solo i finding hard_block bloccano il lancio oggi. Il gate DNC rilasciato usa deliberatamente dnc, non hard_block — un audit completato con solo finding DNC supera comunque la protezione al lancio. Trattare i finding DNC come un segnale di revisione per l’operatore, non come un blocco al lancio, finché BORD-706 (modalità apply-withholds) non viene rilasciato.

La protezione al lancio

Una campagna abilita l’opt-in alla protezione tramite i propri metadati (launchSafety.requireSafetyPreflight: true) oppure facendo passare esplicitamente requireSafetyPreflight: true alla chiamata di lancio da parte del chiamante. Quando abilitata, launchCampaignInSupabase rifiuta di lanciare a meno che tutte le seguenti condizioni non siano vere:
  1. Esiste un audit completato per la batchKey di questa campagna (o per l’id di campagna, se non c’è una batch key).
  2. Il hard_block_count di quell’audit è esattamente zero.
  3. Il recipient_set_hash dell’audit corrisponde a un hash live ricalcolato sui destinatari in coda attuali della campagna al momento del lancio.
Se qualsiasi controllo fallisce, launchCampaign lancia un’eccezione con un motivo specifico (no completed campaign safety preflight audit found, ... has N hard-block finding(s), oppure recipient set changed since audit ...; rerun preflight) e segna il tentativo di lancio come fallito — non lancia mai parzialmente.

Perché esiste il controllo hash

Il recipient_set_hash è un digest con prefisso sha256: degli id ordinati dei campaign_recipient in coda effettivamente coperti dall’audit. Ricalcolarlo e confrontarlo al momento del lancio chiude una lacuna evidente: se i destinatari vengono aggiunti o modificati dopo l’esecuzione del preflight (un’audience più ampia, un batch rimaterializzato, una modifica manuale di un destinatario), l’audit è una prova obsoleta e non deve essere considerata attendibile. La protezione forza una nuova esecuzione del preflight piuttosto che lanciare contro un’audience che in realtà nessuno ha controllato.
Per un audit su batchKey, l’hash viene calcolato sull’unione di tutti i destinatari in coda dell’intera famiglia di batch — corrispondendo alla popolazione che il preflight stesso ha sottoposto a hash. Un audit su id di campagna calcola l’hash solo sui destinatari in coda di quella specifica campagna. La protezione risolve la stessa popolazione usata dall’audit prima di effettuare il confronto, così i due hash sono sempre comparabili tra loro.

Progettato per fallire in modo sicuro (fail-closed)

La protezione al lancio è stata irrobustita contro alcuni specifici percorsi di elusione durante una revisione adversariale:
  • Il flag di abilitazione requireSafetyPreflight viene letto dai metadati JSON grezzi della campagna, non tramite lo schema analizzato/validato — così un campo di metadati malformato e non correlato altrove nella campagna non può mai disabilitare silenziosamente il gate.
  • L’hash live di un audit su batchKey viene ricalcolato sull’intera famiglia di batch, non su una singola campagna, quindi non può mai corrispondere banalmente a una popolazione più ristretta.
  • Un audit vuoto o un insieme di destinatari live vuoto viene rifiutato apertamente, piuttosto che essere considerato passato “in modo vacuo” — un preflight su nulla non è prova che qualcosa sia stato controllato.

Modello dei dati

Due tabelle con ambito workspace, protette da RLS (tramite app.has_workspace_access): campaign_safety_audits — una riga per ogni esecuzione di preflight. campaign_safety_audit_findings — N righe per audit, una per ogni riscontro (destinatario, tipo di finding).

Eseguire un preflight

Non esiste ancora una superficie UI dedicata — runCampaignSafetyPreflightInSupabase(admin, { workspaceId, batchKey | campaignIds, mode?, config? }) viene chiamato direttamente dal tooling operativo prima di un lancio protetto. Passa una batchKey (preferibile per una famiglia multi-campagna) oppure un array esplicito campaignIds; la chiamata lancia un’eccezione se nessuno dei due è utilizzabile, così un audit non può mai essere avviato contro una popolazione illimitata o ambigua.
Esegui nuovamente il preflight ogni volta che i destinatari cambiano dopo un’esecuzione precedente — la protezione al lancio rifiuterà comunque un audit obsoleto, ma ri-eseguirlo in modo proattivo evita un tentativo di lancio fallito al momento del go-live.

Triage

Vedi anche