Ir para o conteúdo

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ão public.bv_* explícitas, mínimas e security_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_path e execução revogada de public;
  • money usa numeric no 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

  1. evidence_id será UUID opaco gerado no servidor com gen_random_uuid(). Não carrega tipo, tenant, timestamp ou PII.
  2. Evidência é append-only. Correção cria novo ID e liga supersedes_evidence_id; IDs citados por decisões históricas permanecem.
  3. 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
  1. content_hash é SHA-256 de JSON canônico com nomes de campos ordenados, decimals como strings, instantes UTC e sem presentation fields.
  2. Evidence types são SOURCE_FACT, RECONCILIATION, DERIVED_METRIC, POLICY, SCENARIO_ASSUMPTION e HUMAN_DECISION.
  3. 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.
  4. 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.
  5. 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"
}
  1. FACT exige pelo menos um ID VALID/PROVISIONAL; INFERENCE exige IDs e explicação; HYPOTHESIS pode não ter fato probatório, mas deve ser rotulada.
  2. 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.
  3. 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_log continua 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

  1. LOW, BASE e HIGH têm o mesmo schema, moeda e horizonte.
  2. Cada input tem value + unit/currency + evidence_id ou assumption_id + rationale + owner + source document/URL + observed_at + expires_at.
  3. O servidor valida relações monotônicas declaradas pela policy. Exemplo: payout líquido e CVR seguem LOW <= BASE <= HIGH; refund rate e custo seguem LOW <= BASE <= HIGH em severidade, sem depender do nome para ordenar sinal.
  4. Valores derivados são calculados em SQL após persistir inputs; usuário não grava break-even, lucro ou Ca calculado.
  5. Submit congela conteúdo, gera content_hash e roda pre-flight. Falha hard impede IN_REVIEW; missingness permitido permanece visível e pode limitar a fase.
  6. Approve registra approver_user_id, decided_at, rationale obrigatória, hash, gate status e evidence ID da decisão.
  7. Aprovação não promove SCENARIO_ASSUMPTION a SOURCE_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 = true e 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_status espelha o estado local/externo observado.
  • campaign_phase expressa o uso de capital e evidência.
  • recommendation action usa TEST|VALIDATE|SCALE|HOLD|BLOCK e 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:

  1. transição exige decision snapshot, recommendation e human decision, exceto criação inicial PREFLIGHT e fechamento manual;
  2. retorno para fase anterior é permitido quando evidência piora;
  3. BLOCKED exige reason code (COMPLIANCE, DATA_INVALID, RISK_LIMIT, MANUAL) e só sai com nova aprovação;
  4. HOLD é retenção deliberada, não erro nem pausa automática;
  5. fase não muda budget nem chama Meta. A ação externa é outro workflow e está fora do piloto read-only;
  6. toda transição é append-only em campaign_phase_history com from/to, decision ID, actor e timestamp;
  7. 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:

  1. armazenar raw criptografado em repouso, com acesso somente ao integration service e named break-glass principals;
  2. não copiar endereço, telefone, nome ou email do INS para evidence, AI prompt, audit payload, logs ou métricas;
  3. 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;
  4. lead e financial subject são separados. Pedido pode preservar referência pseudônima/receipt sem preservar contato;
  5. legal hold é registro explícito com owner, razão e expiração; não é flag permanente informal;
  6. job diário aplica retention policy versionada, em lotes, registra contagens e nunca toca schemas externos no Supabase compartilhado;
  7. 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;
  8. 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

  1. Criar Meta App/Business integration controlada pela organização e conceder somente ads_read às ad accounts allowlisted. ads_management não é pedido nem aceito pelo runtime.
  2. business_management pode ser usado por um administrador durante provisionamento somente se a topologia do Business exigir; não pertence à credencial de coleta em runtime.
  3. Para o piloto single-workspace, o token fica no secret store da plataforma de deploy, referenciado por META_ACCESS_TOKEN. A allowlist fica em META_AD_ACCOUNT_IDS; IDs não são secrets, mas são validados server-side.
  4. 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
  1. 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.
  2. 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.
  3. 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 preservam credential_ref, não o secret.
  4. Adapter usa uma API version fixada/configurada (META_API_VERSION), timeout, paginação completa, backoff e request correlation ID sem token.
  5. 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.
  6. 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_read e ausência de ads_management;
  • ad account fora da allowlist falha;
  • mock server prova que nenhum método de escrita pode ser chamado;
  • token expirado produz UNKNOWN/SOURCE_UNAVAILABLE e 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:

  1. pelo menos 14 dias consecutivos e dois fechamentos de payout cobertos, ou janela maior definida pela Finance;
  2. zero duplicata financeira e zero finding High/Critical aberto;
  3. diferença monetária dentro de USD 0.01 por tipo/dia ou policy aprovada;
  4. 100% de source runs com paginação/watermark íntegros no período;
  5. caixa, compromissos e reserve policy VALID;
  6. RLS/grants, replay, null, currency e clock tests verdes;
  7. aprovação explícita de Finance owner e Workspace owner;
  8. 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 SHADOW até aceite específico para ADVISORY;
  • 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 por coalesce;
  • 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:

  1. evidence store, quality/reason enums e snapshots imutáveis;
  2. tornar operacionais os schemas de balance snapshots, commitments e reserve inputs da foundation migration e implementar o cálculo S0/Cp/L/Ca/I;
  3. pipeline ClickBank atômico/replayable e maturidades independentes;
  4. scenarios LOW/BASE/HIGH com RPCs de lifecycle/approval;
  5. campaign phase history;
  6. retention/pseudonymization jobs;
  7. Meta read-only em shadow;
  8. backfill e cutover registry.

Nenhum item dessa ordem autoriza migration remota ou ação em produção sem gate humano separado.