O preflight é desativado por padrão. Campanhas existentes são lançadas exatamente como antes, a menos que os metadados de uma campanha explicitamente ativem o opt-in via
launchSafety.requireSafetyPreflight.O que uma execução de preflight faz
runCampaignSafetyPreflightInSupabase executa uma única passagem determinística sobre os destinatários enfileirados de um batch de campanhas:
- Resolve as campanhas dentro do escopo — ou todas as campanhas que compartilham uma
batchKey(uma “família” de campanhas, correlacionada viacampaigns.metadata->>'batchKey'), ou uma lista explícita de ids de campanha. - Carrega os
campaign_recipientsenfileirados dessas campanhas, unidos (joined) às colunas de contato relevantes para segurança (consent_state,subscriber_status,deleted_at,deletion_requested_at,metadata). - Abre uma linha de audit (
campaign_safety_audits, statusrunning). - Executa todos os gates determinísticos registrados sobre o conjunto de destinatários e coleta os findings.
- Persiste os findings (
campaign_safety_audit_findings) e completa o audit — gravando umrecipient_set_hashsobre os ids de destinatários ordenados e as contagens de severidade consolidadas, então muda o status paracompleted. - Em caso de qualquer erro após a abertura da linha de audit, o audit é marcado como
failedcom o motivo do erro, e o erro é relançado (re-thrown) — um preflight nunca reporta sucesso silenciosamente em caso de falha (crash).
Gate disponível: DNC
O único gate ativo hoje é odncGate — uma verificação autocontida, baseada apenas em dados do Switchbord. Ele marca um destinatário como do-not-contact (severidade dnc, tipo de finding dnc_opted_out) quando qualquer uma destas condições é verdadeira, e registra quais delas corresponderam como evidência:
consent_state === "opted_out"subscriber_status === "unsubscribed"- o contato tem
deleted_atdefinido - o contato tem
deletion_requested_atdefinido
O adapter tem um ponto de extensão documentado para gates de identidade expandida (Travio/Neo4j/UDB de contato prévio, hits de status de viagem, correspondência fuzzy de pax — rastreados como BORD-702/703/704/705). Esses gates precisam de contexto de identidade resolvida mais rico do que apenas o conjunto de destinatários fornece, e estão intencionalmente fora do escopo deste preflight inicial.
Escada de severidade
Os findings carregam uma de sete severidades, da mais para a menos grave:A trava de lançamento
Uma campanha ativa o opt-in para a trava via seus próprios metadados (launchSafety.requireSafetyPreflight: true) ou por quem chama a função passando explicitamente requireSafetyPreflight: true na chamada de lançamento. Quando habilitada, launchCampaignInSupabase se recusa a lançar a menos que todas as condições abaixo sejam válidas:
- Existe um audit completado para a
batchKeydesta campanha (ou id da campanha, se não houver batch key). - O
hard_block_countdesse audit é exatamente zero. - O
recipient_set_hashdo audit corresponde a um hash em tempo real (live) recalculado sobre os destinatários enfileirados atuais da campanha no momento do lançamento.
launchCampaign lança uma exceção com um motivo específico (no completed campaign safety preflight audit found, ... has N hard-block finding(s), ou recipient set changed since audit ...; rerun preflight) e marca a tentativa de lançamento como falha — ela nunca lança parcialmente.
Por que a verificação de hash existe
Orecipient_set_hash é um digest prefixado com sha256: dos ids de campaign_recipient enfileirados e ordenados que o audit efetivamente cobriu. Recalcular e comparar esse hash no momento do lançamento fecha uma lacuna óbvia: se destinatários forem adicionados ou alterados depois que o preflight foi executado (uma audiência maior, um batch rematerializado, uma edição manual de destinatário), o audit é evidência obsoleta e não deve ser confiado. A trava força uma nova execução de preflight em vez de lançar contra uma audiência que ninguém de fato verificou.
Para um audit de
batchKey, o hash é calculado sobre a união de todos os destinatários enfileirados em toda a família de batch — correspondendo à população que o próprio preflight hasheou. Um audit por id de campanha hasheia apenas os destinatários enfileirados daquela campanha específica. A trava resolve a mesma população que o audit usou antes de comparar, de modo que os dois hashes são sempre comparáveis entre si (apples-to-apples).Fail-closed por design
A trava de lançamento foi reforçada contra alguns caminhos de contorno específicos durante a revisão adversarial:- A flag de habilitação
requireSafetyPreflighté lida a partir do JSON bruto de metadados da campanha, não através do esquema analisado/validado — de modo que um campo de metadados malformado e não relacionado em outro lugar da campanha nunca pode desabilitar silenciosamente o gate. - O hash em tempo real de um audit de
batchKeyé recalculado sobre a família de batch inteira, não sobre uma única campanha, de modo que nunca pode corresponder trivialmente a uma população mais estreita. - Um audit vazio ou um conjunto de destinatários em tempo real vazio é recusado diretamente, em vez de passar “vacuamente” — um preflight de nada não é evidência de que algo foi verificado.
Modelo de dados
Duas tabelas com escopo de workspace, protegidas por RLS (viaapp.has_workspace_access):
campaign_safety_audits — uma linha por execução de preflight.
campaign_safety_audit_findings — N linhas por audit, uma por hit de (destinatário, tipo de finding).
Executando um preflight
Ainda não há uma superfície dedicada na UI —runCampaignSafetyPreflightInSupabase(admin, { workspaceId, batchKey | campaignIds, mode?, config? }) é chamado diretamente a partir de ferramentas operacionais antes de um lançamento protegido pela trava. Passe uma batchKey (preferível para uma família com múltiplas campanhas) ou um array explícito de campaignIds; a chamada lança uma exceção se nenhum dos dois for utilizável, de modo que um audit nunca pode ser iniciado contra uma população não delimitada ou ambígua.
Triagem
Veja também
- Broadcast Engine — onde o preflight se encaixa no ciclo de vida de lançamento de uma campanha
- Teste de Go-Live de Campanha Futura — o checklist manual de preflight existente que este sistema foi projetado para eventualmente formalizar
- Platform → Modelo de Dados — convenções de tabelas com escopo de workspace