Nota (PT-BR): este documento é uma referência normativa de engenharia e permanece temporariamente em inglês para preservar precisão técnica. A visão geral em português está em Decision Engine. A tradução completa está no backlog de documentação.
Decisões de arquitetura de decisioning¶
Status geral: aceito. Data: 2026-08-20. Estas decisões fecham os pontos que precisam estar estáveis antes das migrations e do motor de decisão. Mudanças exigem nova decisão versionada; não se edita a decisão histórica.
Índice¶
| ID | Decisão | Status |
|---|---|---|
| ADR-D001 | evidence IDs e citações da IA | Aceita |
| ADR-D002 | CRUD e aprovação de cenários | Aceita |
| ADR-D003 | fase de campanha separada do status | Aceita |
| ADR-D004 | retenção e minimização de PII | Aceita, sujeita a revisão jurídica |
| ADR-D005 | credenciais Meta do piloto read-only | Aceita |
| ADR-D006 | cutover additive-only do legado | Aceita |
Princípios compartilhados¶
- novas tabelas físicas ficam em
brandvelia; bridges públicas sãopublic.bv_*explícitas, mínimas esecurity_invoker = true; - tabelas tenant-owned têm
workspace_id, FK tenant-aware, RLS habilitada e forçada; - browser não escreve result tables diretamente; usa RPC transacional com auth,
role check, fixed
search_pathe execução revogada depublic; - money usa
numericno banco e decimal string na fronteira TypeScript; - policies, assumptions, snapshots, prompts, modelos, recomendações e decisões são versões imutáveis;
- LLM não calcula, aprova nem executa ação financeira ou de advertising;
- não há modo automático. Delivery modes:
OFF,SHADOW,ADVISORY.
ADR-D001 - Evidence IDs e citações da IA¶
Contexto¶
Hoje existem raw ClickBank, transações e audit_log, mas não há uma identidade
imutável para o conjunto exato de fatos usado por uma métrica ou afirmação. Sem
isso, uma explicação pode apontar para uma linha mutável, misturar workspaces ou
tratar hipótese como fato.
Decisão¶
evidence_idserá UUID opaco gerado no servidor comgen_random_uuid(). Não carrega tipo, tenant, timestamp ou PII.- Evidência é append-only. Correção cria novo ID e liga
supersedes_evidence_id; IDs citados por decisões históricas permanecem. - O envelope mínimo é:
id, workspace_id, evidence_type, subject_type, subject_id,
snapshot_id/raw_record_id, as_of, content_hash,
quality_status, quality_reasons,
source_system, source_record_id,
formula_id, formula_version, input_evidence_ids,
created_at, supersedes_evidence_id
content_hashé SHA-256 de JSON canônico com nomes de campos ordenados, decimals como strings, instantes UTC e sem presentation fields.- Evidence types são
SOURCE_FACT,RECONCILIATION,DERIVED_METRIC,POLICY,SCENARIO_ASSUMPTIONeHUMAN_DECISION. - Evidência de métrica derivada aponta para todos os input evidence IDs e para
formula_version. Um link apenas para dashboard ou campanha não basta. - O servidor monta uma allowlist a partir do snapshot e envia à IA pares
evidence_id + redacted fact. O browser não fornece fatos nem IDs livres. - Structured output de IA exige em cada claim:
{
"claim": "texto",
"claim_type": "FACT|INFERENCE|HYPOTHESIS",
"evidence_ids": ["00000000-0000-0000-0000-000000000001"],
"confidence": "LOW|MEDIUM|HIGH"
}
FACTexige pelo menos um IDVALID/PROVISIONAL;INFERENCEexige IDs e explicação;HYPOTHESISpode não ter fato probatório, mas deve ser rotulada.- O validador rejeita a resposta inteira se houver ID fora da allowlist, cross-workspace, claim factual sem ID ou evidência expirada para o uso.
- PII nunca é copiada para evidence payload, prompt, citation label ou log.
Consequências¶
- Toda recomendação resolve até os fatos, versões e cutoff exatos.
- O mesmo fato pode participar de várias evidências derivadas sem duplicar raw.
- UUID aleatório é suportado hoje e evita introduzir extensão/gerador novo.
audit_logcontinua sendo trilha de ações; não substitui evidence store.
Gate de aceite¶
- teste cross-workspace falha mesmo com UUID conhecido;
- alterar input ou formula version altera o hash/novo evidence ID;
- replay idempotente reutiliza a evidência de mesmo content hash no mesmo subject/as_of, sem duplicar;
- 100% das claims factuais de IA passam no validador de allowlist.
ADR-D002 - CRUD e aprovação de cenários¶
Contexto¶
Experimentos atuais não representam cenários de unit economics. Decisioning precisa de LOW/BASE/HIGH comparáveis, com assumptions sourced e aprovação humana, sem transformar a operação solo em um workflow inviável.
Decisão¶
O agregado é scenario_set; cada versão contém exatamente três
scenario_variants: LOW, BASE, HIGH.
Estados¶
DRAFT -> IN_REVIEW -> APPROVED -> SUPERSEDED
| | |
+-> ARCHIVED <- REJECTED+
DRAFT: creator pode criar, ler, editar e excluir logicamente se nunca foi referenciado.IN_REVIEW: conteúdo congelado; reviewer pode aprovar, rejeitar ou devolver criando nova draft version.APPROVED: imutável e elegível atévalid_until/expiração de input.REJECTED,SUPERSEDED,ARCHIVED: somente leitura.- hard delete só é permitido para draft nunca submetido, sem referência e via RPC auditada. Nos demais casos, archive.
Conteúdo e validação¶
- LOW, BASE e HIGH têm o mesmo schema, moeda e horizonte.
- Cada input tem
value + unit/currency + evidence_idouassumption_id + rationale + owner + source document/URL + observed_at + expires_at. - O servidor valida relações monotônicas declaradas pela policy. Exemplo:
payout líquido e CVR seguem
LOW <= BASE <= HIGH; refund rate e custo seguemLOW <= BASE <= HIGHem severidade, sem depender do nome para ordenar sinal. - Valores derivados são calculados em SQL após persistir inputs; usuário não
grava break-even, lucro ou
Cacalculado. - Submit congela conteúdo, gera
content_hashe roda pre-flight. Falha hard impedeIN_REVIEW; missingness permitido permanece visível e pode limitar a fase. - Approve registra
approver_user_id,decided_at, rationale obrigatória, hash, gate status e evidence ID da decisão. - Aprovação não promove
SCENARIO_ASSUMPTIONaSOURCE_FACT.
Permissões pragmáticas¶
member: cria/edita suas drafts e lê versões da workspace.admin/owner: submete, revisa, aprova, rejeita e arquiva.- Com dois ou mais approvers ativos, maker-checker é obrigatório: autor não aprova a própria versão.
- Com apenas um approver ativo, self-approval é permitida para não bloquear a
operação solo, mas exige reautenticação/segunda confirmação, rationale,
self_approved = truee finding visível. Não é aprovação automática.
Uso e expiração¶
Uma recommendation referencia scenario_set_id + version. Se um input expira,
a versão permanece histórica, mas fica inelegível para novo run (UNKNOWN ou
PROVISIONAL conforme fonte). Nova aprovação cria nova versão; nunca se edita a
aprovada.
Consequências¶
- CRUD é simples em draft e auditável depois do submit.
- A operação solo funciona sem fingir segregação de função inexistente.
- Cenários antigos reproduzem decisões históricas.
Gate de aceite¶
- exatamente um LOW/BASE/HIGH por version;
- update/delete de versão submetida falha no banco;
- cenário expirado não inicia run novo;
- self-approval só funciona com um approver elegível e fica auditada;
- cálculo derivado enviado pelo browser é ignorado/rejeitado.
ADR-D003 - Fase de campanha separada do status¶
Contexto¶
O enum atual draft|active|paused|archived descreve operação, não a força da
evidência. Uma campanha pode estar pausada durante validação ou ativa em teste;
inferir fase por status ou spend mistura conceitos.
Decisão¶
Manter dois eixos:
operational_status = DRAFT | ACTIVE | PAUSED | ARCHIVED
campaign_phase = PREFLIGHT | TEST | VALIDATE | SCALE |
HOLD | BLOCKED | CLOSED
operational_statusespelha o estado local/externo observado.campaign_phaseexpressa o uso de capital e evidência.- recommendation action usa
TEST|VALIDATE|SCALE|HOLD|BLOCKe propõe, mas não executa, transição.
Transições permitidas:
PREFLIGHT -> TEST | HOLD | BLOCKED | CLOSED
TEST -> VALIDATE | HOLD | BLOCKED | CLOSED
VALIDATE -> TEST | SCALE | HOLD | BLOCKED | CLOSED
SCALE -> VALIDATE | HOLD | BLOCKED | CLOSED
HOLD -> PREFLIGHT | TEST | VALIDATE | SCALE | BLOCKED | CLOSED
BLOCKED -> PREFLIGHT | HOLD | CLOSED
CLOSED -> (terminal)
Regras:
- transição exige decision snapshot, recommendation e human decision, exceto
criação inicial
PREFLIGHTe fechamento manual; - retorno para fase anterior é permitido quando evidência piora;
BLOCKEDexige reason code (COMPLIANCE,DATA_INVALID,RISK_LIMIT,MANUAL) e só sai com nova aprovação;HOLDé retenção deliberada, não erro nem pausa automática;- fase não muda budget nem chama Meta. A ação externa é outro workflow e está fora do piloto read-only;
- toda transição é append-only em
campaign_phase_historycom from/to, decision ID, actor e timestamp; - o estado corrente é projeção determinística do último evento válido.
Consequências¶
- UI pode mostrar operação e evidência sem ambiguidade.
- Não é preciso deformar o enum legado imediatamente.
- Campaign phase pode regredir sem apagar aprendizado.
Gate de aceite¶
- transição fora do grafo falha;
- recommendation sem decisão humana não altera phase;
- phase change não dispara chamada Meta;
- histórico reproduz o estado corrente.
ADR-D004 - Retenção e minimização de PII¶
Contexto¶
O INS raw e cb_conversions atuais armazenam nome/email e IP sem expiração. A
trilha financeira precisa sobreviver, mas decisioning, evidência e IA não
precisam de identidade do comprador. LGPD exige finalidade, minimização,
retenção e atendimento de direitos. Esta decisão é baseline técnica e deve ser
validada juridicamente antes de produção ampliada.
Decisão¶
Classificar e separar PII de fatos financeiros. Retenção padrão:
| Classe | Exemplos | Retenção | Ação |
|---|---|---|---|
| raw ClickBank com PII | payload INS descriptografado | 30 dias após reconciliação; máximo 90 dias para erro aberto | purge do payload claro; manter hash, envelope técnico e campos financeiros não PII |
| segurança/rede | source IP de webhook, logs de segurança | 30 dias; até 90 dias com incidente | delete ou truncamento irreversível |
| PII duplicada de conversão | customer email/name em conversion | 90 dias | null/anonymize; manter lead_id pseudônimo quando lícito |
| tracking pseudônimo | click/session/ad click IDs e device/country | 13 meses | delete/anonymize dimensões raras; manter agregados |
| lead/CRM | email, nome, consent e eventos de marketing | enquanto houver finalidade/consentimento; revisar com 24 meses de inatividade | delete/anonymize mediante policy e direitos; manter suppression token quando necessário |
| unsubscribe/complaint | prova e suppression | 5 anos após evento | HMAC/suppression sem conteúdo de marketing |
| financeiro não PII | ledger, receipt tokenizado, payout, FX | 5 anos após fim do exercício, ou prazo legal maior aprovado | archive protegido e depois delete |
| consentimento | versão, fonte, grant/revoke | 5 anos após revogação | archive protegido e depois delete |
| evidência/audit | hashes, decisões, versions, actor interno | 5 anos, sem PII de comprador | archive; PII acidental é incidente e deve ser redigida |
Controles:
- armazenar raw criptografado em repouso, com acesso somente ao integration service e named break-glass principals;
- não copiar endereço, telefone, nome ou email do INS para evidence, AI prompt, audit payload, logs ou métricas;
- usar token HMAC workspace-scoped para dedupe/suppression quando o email claro não for mais necessário. A chave fica em secret manager e é rotacionável;
- lead e financial subject são separados. Pedido pode preservar referência pseudônima/receipt sem preservar contato;
- legal hold é registro explícito com owner, razão e expiração; não é flag permanente informal;
- job diário aplica retention policy versionada, em lotes, registra contagens e nunca toca schemas externos no Supabase compartilhado;
- pedidos de acesso/correção/exclusão usam workflow autenticado. Exclusão anonimiza referências protegidas por obrigação legal em vez de corromper o ledger;
- backups expiram conforme a política do provedor e não são restaurados para reintroduzir PII purgada sem replay do tombstone log.
Consequências¶
- Preserva auditabilidade financeira sem retenção indefinida de comprador.
- Exige separar raw protegido do fato normalizado e criar purge/anonymization jobs antes de ampliar o INS.
REDACTEDé reason code válido; não rebaixa um fato financeiro cujo valor e proveniência não PII continuem verificáveis.
Gate de aceite¶
- inventário de campos PII e base legal aprovado;
- teste com payload sintético prova purge sem quebrar ledger/reconciliação;
- evidence/AI exports têm zero PII;
- job é tenant-safe, idempotente, observável e respeita legal hold;
- alerta se raw passa de 90 dias sem hold válido.
ADR-D005 - Credenciais Meta do piloto read-only¶
Contexto¶
Meta está selecionada como primeiro canal oficial, mas hoje só existe importação CSV. Tokens não podem ir para Client Components ou plaintext no banco, e o piloto não precisa de permissão de escrita.
Decisão¶
- Criar Meta App/Business integration controlada pela organização e conceder
somente
ads_readàs ad accounts allowlisted.ads_managementnão é pedido nem aceito pelo runtime. business_managementpode ser usado por um administrador durante provisionamento somente se a topologia do Business exigir; não pertence à credencial de coleta em runtime.- Para o piloto single-workspace, o token fica no secret store da plataforma
de deploy, referenciado por
META_ACCESS_TOKEN. A allowlist fica emMETA_AD_ACCOUNT_IDS; IDs não são secrets, mas são validados server-side. - O banco armazena apenas:
workspace_id, provider=meta, credential_ref, app_id,
business_id, allowed_account_ids, scopes, issued_at, expires_at,
last_validated_at, status, rotated_at
- Multi-workspace com credenciais distintas é gate para migrar tokens a um secret manager com referências por workspace. Não guardar token cifrado por chave da própria aplicação no mesmo banco.
- Boot/health check valida token presente, expiração,
ads_read, app/business esperado e contas permitidas. O token nunca aparece em URL, logs, audit, telemetry ou mensagem de erro. - Rotação ocorre no máximo 7 dias antes de
expires_at, imediatamente em mudança de pessoal/escopo ou suspeita de exposição. O credential record é versionado; runs preservamcredential_ref, não o secret. - Adapter usa uma API version fixada/configurada (
META_API_VERSION), timeout, paginação completa, backoff e request correlation ID sem token. - A interface do piloto expõe somente métodos GET de metadata/insights. Não implementa generic request, POST, batch write, budget, status, creative ou audience methods.
- Testes de contrato falham se path/method não estiver em allowlist read-only.
Cutoff de segurança¶
Se scope, app, business, account, currency ou timezone divergir do cadastro, o
run falha fechado, não persiste fatos parciais como válidos e não atualiza o
watermark. Uma indisponibilidade Meta nunca bloqueia tracking/ClickBank, mas
torna spend UNKNOWN e bloqueia nova alocação dependente.
Consequências¶
- Menor blast radius e nenhum caminho acidental de automação.
- Env secret é pragmático para uma workspace; não é a arquitetura SaaS final.
- Rotação e expiração tornam-se observáveis sem armazenar token.
Gate de aceite¶
- secret scan e client bundle não encontram token;
- permissions check confirma
ads_reade ausência deads_management; - ad account fora da allowlist falha;
- mock server prova que nenhum método de escrita pode ser chamado;
- token expirado produz
UNKNOWN/SOURCE_UNAVAILABLEe alerta, sem leak.
ADR-D006 - Cutover do legado¶
Contexto¶
bv_visits, bv_transactions, bv_cb_*, dashboards e CSV já operam, mas não
possuem todos os clocks, reason codes, balance snapshots, evidence IDs ou
maturidades. O banco Supabase é compartilhado; big-bang, destructive migration
e reinterpretação retroativa seriam riscos maiores que manter o legado durante
uma transição controlada.
Decisão¶
Adotar cutover additive-only, por workspace, source e data de corte.
Fase 1 - Inventário e congelamento semântico¶
- documentar fórmulas e chaves atuais;
- atribuir
legacy_source_id/hash aos rows elegíveis sem alterar seu significado; - proibir novas dependências diretas das views legadas no decisioning;
- corrigir primeiro riscos de duplicação e tenant boundary.
Fase 2 - Backfill provisional¶
- copiar para fatos canônicos por jobs idempotentes e com checkpoint;
- preservar IDs externos, raw links e timestamps existentes;
- campo inexistente recebe
null + LEGACY_MISSING, nunca timestamp/value inventado; - linhas manuais sem arquivo/hash são
PROVISIONAL; - períodos ClickBank reconciliados podem promover componentes verificáveis a
VALID, sem fabricar clocks ausentes; - demo/sample data é marcado e excluído.
Fase 3 - Dual-read shadow¶
- escrita nova usa o pipeline canônico; não criar dual-write best-effort;
- quando o legado ainda precisar de projeção, uma única transação/RPC ou outbox produz a projeção derivada;
- dashboards legados continuam; decisioning gera snapshots
SHADOW; - comparar por dia/campaign/source: counts, spend, revenue, refund, chargeback, fee, currency e attribution coverage;
- diferenças ficam em reconciliation findings com evidence IDs.
Fase 4 - Gate de cutover¶
Cutover de leitura só ocorre com:
- pelo menos 14 dias consecutivos e dois fechamentos de payout cobertos, ou janela maior definida pela Finance;
- zero duplicata financeira e zero finding High/Critical aberto;
- diferença monetária dentro de USD 0.01 por tipo/dia ou policy aprovada;
- 100% de source runs com paginação/watermark íntegros no período;
- caixa, compromissos e reserve policy
VALID; - RLS/grants, replay, null, currency e clock tests verdes;
- aprovação explícita de Finance owner e Workspace owner;
- feature flag/cutover registry com
effective_at, scope e rollback target.
Fase 5 - Advisory e retenção¶
- views/RPCs de leitura passam ao canônico no cutoff; recomendações continuam
SHADOWaté aceite específico paraADVISORY; - tabelas legadas ficam read-only por 90 dias para a aplicação, sem
DROP; - após 90 dias, retirar grants/código somente com auditoria de dependências;
- objetos legados permanecem arquivados conforme retenção. Qualquer remoção futura é decisão separada e nunca toca outros schemas.
Rollback¶
Rollback muda a versão da read projection/feature flag para o último target aprovado. Não apaga fatos novos, não reverte migrations e não reabre escrita legada automaticamente. Eventos recebidos durante rollback continuam no raw e são replayáveis.
Precedência no período de transição¶
| Situação | Fonte escolhida |
|---|---|
fato canônico VALID após cutoff |
canônico |
canônico PROVISIONAL, legado reconciliado |
decisioning permanece shadow; ambos visíveis, sem merge oportunista |
| legado sem clocks/proveniência | legado PROVISIONAL apenas para comparação |
| conflito financeiro | nenhum “winner” automático; snapshot afetado INVALID até finding resolvido |
| ausência canônica antes do cutoff | legado informativo; não libera escala |
Consequências¶
- Evita big-bang e preserva rollback sem DDL destrutivo.
- Exige operar duas projeções temporariamente, mas uma única fonte de escrita canônica depois do adapter.
- Histórico incompleto permanece honestamente provisional.
Gate de aceite¶
- backfill pode ser executado duas vezes sem diferença;
- cutoff por workspace/source não afeta co-tenants;
- rollback troca leitura sem perda/reprocessamento manual de eventos;
- nenhuma linha
LEGACY_MISSINGé promovida porcoalesce; - decisão histórica continua resolvendo as versões/evidências usadas antes e depois do cutoff.
Registro de lacunas para implementação¶
Estas ADRs fecham a decisão, mas o código ainda não a implementa. Ordem mínima:
- evidence store, quality/reason enums e snapshots imutáveis;
- tornar operacionais os schemas de balance snapshots, commitments e reserve
inputs da foundation migration e implementar o cálculo
S0/Cp/L/Ca/I; - pipeline ClickBank atômico/replayable e maturidades independentes;
- scenarios LOW/BASE/HIGH com RPCs de lifecycle/approval;
- campaign phase history;
- retention/pseudonymization jobs;
- Meta read-only em shadow;
- backfill e cutover registry.
Nenhum item dessa ordem autoriza migration remota ou ação em produção sem gate humano separado.