ModularReports
Companheiro do DRS
ModularReports: Roadmap de Implementação
| Campo | Valor |
|---|---|
| Produto | ModularReports |
| Documento | Roadmap de Implementação (companheiro do DRS) |
| Data de revisão | 2026-07-24 |
Documentos da família
- DRS (modularreports), o que o sistema faz.
- Inventário de Artefatos (modularreports-inventario), tudo que precisa existir e como cada item deve ser.
- Jornadas dos Atores (modularreports-jornadas), como cada papel opera o sistema.
- Plano de Testes (modularreports-testes), o que validar antes de entregar.
- Questionário de Elicitação (modularreports-questionario), decisões de produto em aberto que não travam a construção.
Este é o roteiro de construção do sistema, em ordem, com verificação primeiro: para cada tarefa, o observável que prova que ela está pronta é declarado antes do como construir. A ordem vem das ondas de entrega do DRS (bloco 9, J.1 e J.2) e de suas dependências. Cada tarefa cobre um ou mais requisitos funcionais (RF) do DRS.
Como usar
Cada tarefa é um cartão com um código curto e estável (ex.: F1-03), o RF que cobre, o que implementar, como verificar (o teste, declarado primeiro) e o critério de aceite H1. Marque a tarefa como feita quando o teste passar. As fases são sequenciais; dentro de uma fase, siga a ordem dos cartões. Ao fim de cada fase há uma Definição de Pronto: a fase só fecha quando todos os seus critérios estão verdes.
Mapa de cobertura
| Fase | Onda do DRS | Módulo do DRS | RFs cobertos |
|---|---|---|---|
| Planejamento | Preparação | Elicitação, especificação, companheiros, validação, aprovação | Pré-concluída na aprovação do DRS |
| M, Mockup Navegável | Preparação | Referência visual do sistema completo (bloco 6, F.3) | Concluída: mockup de 16 telas publicado e aprovado |
| F1, Protótipo do pipeline IA | Fatia 1 | reports-compose (CMP), reports-boundary (BND), reports-ingest (ING) | RF-BND-001, RF-CMP-005/006/008 a 016, RF-ING-003, RNF-SEG-002/003/004 |
| F1b, Schema de dados | Fatia 1b | Modelo de dados (E.1, E.2) | Cria as tabelas do schema reports (as 7 do E.2 mais report_goal), antes das fatias que as consomem; RN-009 (trigger report.company_id = company_id do Account) |
| F2, Contrato de bloco e snapshot | Fatia 2 | reports-render (REN), reports-publish (PUB), reports-ingest (ING) | RF-REN-001/002/003, RF-PUB-001, RF-ING-005, RN-001 a RN-006 |
| F3, Package renderizador | Fatia 3 | reports-render (REN) | RF-REN-004/005/007, RNF-MANUT-001, RNF-A11Y-001 |
| F4, Ingestão, quota e storage | Fatia 4 | reports-ingest (ING), reports-boundary (BND) | RF-ING-001/002/003/004/006, RF-BND-002, RNF-LGPD-001 |
| F5, Leitor público | Fatia 5 | reports-render (REN) | RF-REN-006/010, RNF-SEG-001 (caminho do leitor: role reports_reader, GRANT das 3 tabelas e policy PERMISSIVE publicado+não-expirado), RNF-SEG-005, RNF-PERF-001, RNF-DISP-001, RN-011 |
| F6, Role, entitlement e RBAC no Hub | Fatia 6 | reports-admin (ADM) | RF-ADM-001/002/012, RNF-SEG-001 (caminho do operador: RESTRICTIVE por company_id), RN-007 (log append-only) |
| F7, Chrome e shell do operador | Fatia 7 | reports-admin (ADM) | RF-ADM-003/004/014, RNF-MANUT-002 |
| F8, Wizard e compositor | Fatia 8 | reports-compose (CMP), reports-curate (CUR) | RF-CMP-001 a 004, RF-CUR-001 a 007, RNF-PERF-002 |
| F9, Publicação e link | Fatia 9 | reports-publish (PUB), reports-compose (CMP), reports-comment (COM) | RF-CMP-007, RF-PUB-002 a 010, RF-COM-004, RN-010 (imutabilidade do snapshot imposta pela função SECURITY DEFINER de publish) |
| F10, Templates | Fatia 10 | reports-admin (ADM) | RF-ADM-009/010 |
| F11, Importação de metas | Fatia 11 | reports-ingest (ING), reports-boundary (BND) | RF-BND-003, RF-ING-007, RN-008 (snapshot congela as metas vigentes no publish) |
| F12, Comentários | Fatia 12 | reports-comment (COM) | RF-COM-001 a 006 |
| F13, Analytics | Fatia 13 | reports-analytics (ANL), reports-admin (ADM) | RF-ANL-001 a 006, RF-ADM-015, RNF-LGPD-003 |
| F14, Domínio custom e white-label | Fatia 14 | reports-render (REN) | RNF-SEG-006 |
| F15, PDF condicional | Fatia 15 | reports-render (REN) | RF-REN-008/009 |
| F16, Painel do cliente | Fatia 16 | reports-admin (ADM), reports-render (REN) | RF-ADM-005/006/007/008/011/013, RF-REN-011 (portal logado do cliente) |
Estado global
O ModularReports está com o DRS aprovado e o mockup navegável (16 telas) publicado e aprovado: Planejamento 100%, Fase M 100%, Construção 0 de 101 cartões (16 fatias) = 0%, Testes 0 de 111 itens = 0% (peso 40/40/20 do progresso geral). O projeto nasce em 40%. Nenhuma fatia de construção foi iniciada. As decisões técnicas de construção foram resolvidas no DRS (bloco 9, J.4) e a descoberta de integração da plataforma-mãe já fixou os internos que o Reports reusa (Fase 0, {P0-29}). Quatro decisões de produto seguem em aberto no Questionário de Elicitação ({Q1} a {Q4}, ver J.5 do DRS) e não travam o início da Fatia 1.
Planejamento
Concluída na aprovação do DRS. Vale 40% do projeto (seção Planejamento). Estas etapas cobrem tudo que aconteceu antes de começar a construir o sistema.
0.1 Entendimento e Elicitação
0.2 Especificação (o DRS)
0.3 Documentos companheiros
0.4 Validação e Qualidade
0.5 Publicação e Aprovação
Fase M, Mockup Navegável (pós-Planejamento, gate de aprovação)
Dependência: DRS aprovado (Planejamento fechado). A construção (F1 em diante) não começa com esta fase aberta. Concluída: mockup completo publicado em modulareasy.com/mockups/modulareasy/modularreports e aprovado.
DRS (bloco 6, F.3) e Ap.6. As 16 telas do sistema (login, lista de relatórios, clientes, pasta do cliente, wizard de 4 passos, compositor, templates, página pública, modal de publicação, modo apresentar, analytics, identidade da agência, usuários, painel do Super Admin, painel do cliente) com o design system da plataforma-mãe no chrome do app e a linguagem visual das páginas de resultados no leitor público, base compartilhada e Mapa do mockup flutuante.
Como verificar (teste primeiro): todas as páginas respondem 200 na URL publicada; cada tela reflete a superfície correspondente do bloco 7 (F.1); smoke visual página a página em desktop e mobile.
Aceite (H1): Dado o mockup publicado, Quando o cliente navega pelo Mapa do mockup, Então alcança toda tela do sistema, E cada tela aparece com a identidade oficial da plataforma-mãe.
Gate. Apresentado e aprovado.
Como verificar (teste primeiro): aprovação registrada; DRS declara o mockup (bloco 6, F.3) REFERÊNCIA VISUAL APROVADA e GUIA DO DESENVOLVIMENTO.
Aceite (H1): Dado o mockup aprovado, Quando a construção de uma tela começa, Então a página correspondente do mockup é o guia, E divergência visual se resolve consultando o mockup.
Definição de Pronto M: mockup completo no ar, aprovado, e vinculado no DRS como guia do desenvolvimento. CUMPRIDA.
Fase F1, Protótipo do pipeline IA (Fatia 1, gates HARD de I.1)
Dependência: nenhuma além do mockup aprovado (Fase M). Fatia cravada como risco número 1 do produto (I.1): entrega o package e as interfaces identity, storage e render, o sandbox de parse isolado e a composição in-process, agnóstico à decisão de módulo, mais os gates de qualidade e segurança que reprovam o protótipo se falharem.
DRS I.1, Ap.8; artefato ART-MIG-02 do Inventário. Montar o conjunto de referência da Fatia 1: converter relatórios reais do formato v2 para o contrato v3 (blocks[] com id, data_points com dp: e source), a partir dos relatórios reais já entregues e dos artefatos crus quando existirem; onde só há o valor formatado, marcar provenance: "legacy". Incluir os casos: os relatórios reais completos, o export adversarial com número inflado (fail-loud), e casos fora do nicho.
Como verificar (teste primeiro): o script de conversão v2 para v3 roda sobre os relatórios reais e produz documentos v3 válidos com id por bloco e dp: por número; o caso adversarial fica marcado como tal; o conjunto tem os relatórios reais, o adversarial e ao menos dois fora do nicho.
Aceite (H1): Dado o acervo de relatórios reais em v2, Quando o script de conversão roda, Então cada relatório vira um documento v3 com bloco id estável e todo número com dp: (ou provenance: legacy quando só há o formatado), E o golden set inclui os reais, o caso adversarial de dado inflado e casos fora do nicho.
RF-BND-001. Na Fatia 1 a fronteira é declaração de princípio no código do package e da composição: nenhuma função escreve no domínio de metas contínuas do módulo results; o Reports não modela input semanal nem série temporal viva de KPI. A varredura de rotas que prova a fronteira na aplicação real roda em F6/F7, quando as rotas do Reports existem.
Como verificar (teste primeiro): o código do package e da composição não importa nem escreve nenhum domínio de metas contínuas do results; nenhuma estrutura de dado do protótipo modela input semanal.
Aceite (H1): Dado um pedido para acompanhar uma métrica ao longo do tempo com metas semanais, Quando o operador está no ModularReports, Então o sistema não oferece input semanal nem série temporal viva de KPI, E nenhuma tela do Reports grava ou atualiza registro no domínio de metas contínuas do módulo results.
RF-ING-003, RF-CMP-009 (parte do parse). Container efêmero sem rede de saída que recebe os bytes do artefato e devolve JSON estruturado saneado; defesas de arquivo malicioso (magic bytes, XXE desligado, limite de razão de descompressão, SVG sanitizado, xlsm rejeitado); falha de parse reprova o job (fail-closed), nunca degrada aberto. É o único processo de fato isolado do pipeline.
Como verificar (teste primeiro): o container de parse não resolve DNS externo; um xlsm é rejeitado, um SVG com script é sanitizado, um zip bomb acima da razão limite é recusado; uma falha de parse reprova o job em vez de seguir.
Aceite (H1): Dado um arquivo submetido ao parse, Quando o parser roda, Então ele executa sem acesso de rede de saída e devolve JSON saneado, E um xlsm é rejeitado, um SVG com script é sanitizado, um zip bomb acima do limite é recusado, e uma falha de parse reprova o job (fail-closed).
RF-CMP-005. Extrator por código lê célula/campo e injeta o valor; a IA escreve só narrativa.
Como verificar (teste primeiro): para um artefato com valores numéricos, todo número exibido é rastreável ao extrator, nenhum vem do texto gerado pelo modelo.
Aceite (H1): Dado um artefato com valores numéricos, Quando o pipeline compõe, Então todo número exibido no relatório provém do extrator determinístico ligado a uma célula ou campo, E nenhum número exibido é gerado pelo texto do modelo de linguagem.
RF-CMP-006. Cada número referencia dp:<id> com origem (artefato mais localizador, ou fonte externa da allowlist); número sem dp: é sinalizado como pendência.
Como verificar (teste primeiro): todo número colocado num bloco carrega dp:<id> resolvível; um número sem dp: aparece na lista de pendências.
Aceite (H1): Dado um relatório em composição, Quando um número é colocado num bloco, Então ele carrega um dp:<id> cuja origem aponta para um artefato e localizador ou uma fonte da allowlist, E um número sem dp: é sinalizado como pendência.
RF-CMP-008. Check determinístico confere a direção afirmada (melhorou/piorou) contra a comparação numérica de origem.
Como verificar (teste primeiro): um caso com narrativa invertida (diz "melhorou" com número que piorou) é sinalizado pelo check.
Aceite (H1): Dado um número cuja narrativa afirma uma direção (subiu, caiu, melhorou, piorou), Quando o check de polaridade roda, Então a direção afirmada é conferida contra a comparação numérica de origem, E uma inversão é sinalizada.
RF-CMP-010. Wizard é UI sobre a API; a separação compor versus publicar é por capability (reports.compose versus reports.publish), não por escopo de token; nenhum ator de serviço carrega reports.publish. A composição interna roda com claims de sistema, com reports.compose e jamais reports.publish.
Como verificar (teste primeiro): um ator com reports.compose mas sem reports.publish que tenta publicar é recusado; publicar só funciona numa sessão humana com reports.publish.
Aceite (H1): Dado um ator com a capability reports.compose mas sem reports.publish, Quando ele tenta publicar um relatório, Então a publicação é recusada, E publicar só conclui a partir de uma sessão humana com reports.publish.
RF-CMP-011. A composição reusa o gateway de IA da plataforma-mãe (proibido client de IA próprio) com a cadeia gratuita (dois provedores gratuitos encadeados, primário e fallback); sem upgrade automático para modelo pago.
Como verificar (teste primeiro): inspeção do código mostra a composição chamando o gateway da plataforma-mãe, sem client de IA próprio; a configuração usa só a cadeia gratuita, sem fallback automático para modelo pago.
Aceite (H1): Dado o pipeline de composição, Quando ele roda em produção, Então usa apenas o gateway de IA da plataforma-mãe com a cadeia gratuita configurada, E nenhum upgrade para modelo pago acontece automaticamente sem decisão registrada.
RF-CMP-012. Cada composição registra tokens e tempo de processamento.
Como verificar (teste primeiro): ao final de uma composição, o registro de tokens e tempo existe e está disponível para acompanhamento operacional.
Aceite (H1): Dado uma composição concluída, Quando ela termina, Então o sistema registra tokens consumidos e tempo de processamento daquele job, E esses valores ficam disponíveis para o acompanhamento operacional.
RF-CMP-013. Tom de voz da ficha de identidade do cliente é injetado como parâmetro direto da narrativa.
Como verificar (teste primeiro): dois clientes com tons de voz diferentes ("técnico e sóbrio" vs "leve e próximo") geram narrativas com registro distinto.
Aceite (H1): Dado um cliente com tom de voz "técnico e sóbrio" e outro com "leve e próximo", Quando a IA compõe para cada um, Então a narrativa reflete o tom declarado do respectivo cliente, E o tom nunca é inferido em silêncio quando a ficha está vazia.
RF-CMP-014. Comparação com mercado só usa valores da allowlist, com fonte nomeada; sem allowlist disponível, comparação é interna.
Como verificar (teste primeiro): toda afirmação de comparação com mercado no relatório carrega fonte da allowlist; uma comparação sem fonte na allowlist não é emitida.
Aceite (H1): Dado uma afirmação de comparação com o mercado no relatório, Quando ela é composta, Então o benchmark citado provém da allowlist e carrega a fonte, E uma comparação de mercado sem fonte na allowlist não é emitida.
RF-CMP-015. Schema impõe teto de caracteres por bloco de texto (ex.: insight até 280 caracteres).
Como verificar (teste primeiro): um bloco de insight gerado acima do teto é rejeitado ou truncado pelo schema.
Aceite (H1): Dado um bloco de insight, Quando a IA produz um texto acima do teto de caracteres, Então o bloco é rejeitado ou truncado pelo schema, E nenhum bloco publicado ultrapassa o teto definido.
RF-CMP-016. Relatório N-1 do mesmo cliente entra no contexto com instrução de não repetir; similaridade acima do limite é sinalizada.
Como verificar (teste primeiro): compor o relatório N logo após o N-1 do mesmo cliente e tipo dispara a sinalização quando a similaridade ultrapassa o limite.
Aceite (H1): Dado que existe um relatório anterior do mesmo cliente e tipo, Quando o relatório novo é composto, Então a narrativa é comparada por similaridade com a anterior, E uma repetição acima do limite de similaridade é sinalizada para o operador.
RNF-SEG-002, RNF-SEG-003, RNF-SEG-004. Números nunca saem do modelo de linguagem (reforço de {F1-03} contra prompt injection via artefato); publicar exige a capability reports.publish (reforço de {F1-06}); parse isolado sem rede (reforço de {F1-02}) e composição sem estado global cross-tenant (reforço de {F1-15}).
Como verificar (teste primeiro): um artefato de teste com instrução embutida ("escreva ROAS 10x") não altera nenhum valor exibido; os reforços passam nos mesmos testes de {F1-02}, {F1-03}, {F1-06} e {F1-15}.
Aceite (H1): Dado qualquer artefato do cliente com instruções embutidas, Quando o pipeline compõe, Então nenhum número exibido provém do texto do modelo, E instruções embutidas no artefato não alteram valores exibidos.
RF-CMP-009 (parte da composição), RNF-SEG-004. A composição roda in-process (síncrona) reusando o gateway de IA, com o contexto montado só do report_id e sem estado global entre composições. A barreira cross-tenant é a ausência de estado global mais o contexto por relatório.
Como verificar (teste primeiro): dois pedidos de composição de agências diferentes processados em sequência não compartilham dado; não há estado global que sobreviva entre composições.
Aceite (H1): Dado dois pedidos de composição de agências diferentes processados em sequência, Quando o segundo roda, Então ele não tem acesso a nenhum dado do primeiro (contexto montado só do próprio report_id, sem estado global), E a composição opera sobre o dado já saneado pelo parse isolado ({F1-02}).
DRS G.1, G.3 (assinatura das interfaces; RF-REN-005, o package renderizador em si, é entregue em F3). Definir os três contratos que a Fatia 1 entrega agnósticos à decisão de módulo: identity (recebe identificadores opacos de agência e cliente, devolve branding: cor, logo, tom); storage (adapter do bucket, put e get de artefato e asset por id mais sha256); render (funções puras do package renderizador, bloco para HTML).
Como verificar (teste primeiro): os três contratos existem como interfaces com implementação de referência; o package renderizador (render) emite HTML a partir do contrato de bloco; identity e storage são injetáveis (troca de implementação sem tocar a composição).
Aceite (H1): Dado o package da Fatia 1, Quando a composição precisa de identidade, storage ou renderização, Então ela usa as interfaces identity, storage e render, E nenhuma delas acopla o protótipo à decisão de módulo (chrome, banco, storage concretos entram por injeção).
DRS I.1, Ap.4; golden set de {F1-00}. Juiz por modelo de linguagem em temperatura 0, 3 passadas e mediana, calibrado com golden (nota 4) versus degradado (nota 1 ou 2), nas 5 dimensões (D1 a D5). Usa o golden set montado em {F1-00}.
Como verificar (teste primeiro): rodar o golden set de {F1-00} e o caso adversarial contra a rubrica; confirmar que o juiz separa golden (nota 4) de degradado (nota 1 ou 2); nenhuma composição publicável tem número sem proveniência ({F1-04}), isolamento cross-tenant quebrado ({F1-15}) ou publicação fora de sessão humana com reports.publish ({F1-06}).
Aceite (H1): Dado o golden set e o caso adversarial, Quando a rubrica roda, Então a média é maior ou igual a 3,0 com nenhuma dimensão igual a 1 e D2/D3 maiores ou iguais a 3, E os quatro gates HARD (zero alucinação numérica, tríade de segurança, isolamento cross-tenant, qualidade da narrativa) passam. Os indicadores de custo, latência e taxa de composição sem edição são termômetro NÃO bloqueante enquanto o conjunto de referência for pequeno (DRS J.4); os números de referência da Fatia 1 (não são gate) são: custo por página abaixo de US$ 0,50 em tokens, latência na faixa de 10 a 30 segundos (RNF-PERF-002) e ao menos metade dos blocos aceitos sem reescrita manual. Ficar muito fora desses números é sinal de revisar o pipeline, não de reprovar a fatia.
Definição de Pronto F1: os 4 gates HARD de I.1 verdes (zero alucinação numérica, tríade de segurança, isolamento cross-tenant, qualidade da narrativa pela rubrica 5D); package com as interfaces identity, storage e render, o sandbox de parse isolado e a composição in-process prontos para as fatias seguintes.
Fase F1b, Schema de dados (Fatia 1b)
Dependência: nenhuma (a migration não depende do chrome do Hub). Cria as tabelas do schema reports ANTES das fatias que as consomem: F2 mexe em snapshot e report_draft, F4 grava artifact, F5 lê snapshot. A migration de RLS, entitlement, RBAC e auditoria (que dependem do catálogo do Hub) fica em F6.
DRS E.1, E.2; artefatos ART-ENT-01 a 08 do Inventário. Migration que cria o schema reports com as 7 tabelas do E.2 (report, report_draft, snapshot, artifact, comment, link_event, template) mais report_goal (metas por cliente). Só a criação das tabelas; sem RLS, entitlement nem RBAC (esses vêm em F6). A report_job (evolução) e a agency_domain (Fatia 14) nascem nas suas fatias.
A ordem de criação respeita as FKs: template primeiro (referenciado por report.template_id, nullable), depois report, e por fim as que apontam para report (report_draft, snapshot, artifact, comment, link_event); report_goal referencia hub.accounts/hub.companies e não depende das outras. A migration cria também o trigger de integridade RN-009: um BEFORE INSERT/UPDATE em report que confere report.company_id contra o company_id do Account referenciado (hub.accounts.company_id) e rejeita gravar um relatório que cruze a agência de uma Company com o Account de outra.
Como verificar (teste primeiro): migration aplicada cria as 8 tabelas com as colunas do E.2; rollback remove tudo sem resíduo; rodar de novo é idempotente; inserir um report com company_id diferente do company_id do Account referenciado é rejeitado pelo trigger RN-009 (o par que bate é aceito).
Aceite (H1): Dado o banco do Hub sem o schema reports, Quando a migration de dados roda, Então as 8 tabelas (as 7 do E.2 mais report_goal) existem com as colunas, chaves e índices do bloco E.2 e das fichas do Inventário, E a migration é idempotente (rodar de novo não falha nem duplica).
Definição de Pronto F1b: as tabelas de dados do schema reports existem e as fatias F2 a F5 têm onde ler e escrever, sem esperar o chrome do Hub (F6/F7).
Fase F2, Contrato de bloco e snapshot (Fatia 2)
Dependência: {F1b-01} (tabelas do schema) e {F1-15} (a composição in-process, que produz o formato de blocos e pontos de dado). Esta fatia entrega o coração de dados que serve IA, curadoria, leitor e analytics.
RF-REN-001, Ap.2. blocks[], cada bloco com id estável, type, hidden?, tags?, props. id nunca regenerado (âncora de comentário e analytics). Publicar remove blocos ocultos do snapshot.
Como verificar (teste primeiro): republicar um relatório mantém o mesmo id de cada bloco não removido; um bloco marcado oculto some do snapshot.
Aceite (H1): Dado um documento de relatório, Quando ele é serializado, Então cada bloco tem um id estável que não muda entre republicações, E um bloco marcado oculto não aparece no snapshot publicado.
RF-REN-002. Tipos de bloco num registro declarativo (componente, schema, defaults); leitor ignora tipo desconhecido em silêncio, compositor sinaliza.
Como verificar (teste primeiro): um snapshot com um tipo de bloco fora do registro renderiza os demais blocos sem erro no leitor, e aparece sinalizado no compositor.
Aceite (H1): Dado um snapshot com um tipo de bloco que o leitor não conhece, Quando o cliente abre o link, Então a página renderiza os demais blocos sem erro e ignora o desconhecido, E no compositor o mesmo tipo desconhecido é sinalizado ao operador.
RF-REN-003. Payload carrega schema_version; leitor converte versões antigas por funções puras, sem reescrever o armazenado.
Como verificar (teste primeiro): um snapshot fixture em versão antiga renderiza correto após conversão em memória; o registro armazenado permanece na versão original após a leitura.
Aceite (H1): Dado um snapshot em versão de schema antiga, Quando o leitor o renderiza, Então ele o converte em memória para a versão corrente e exibe corretamente, E o registro armazenado permanece na versão original.
RF-PUB-001, RN-001, RN-002, RN-010. Publicar congela snapshot (blocos, metas vigentes, branding); editar depois não muda o já servido. A imutabilidade é imposta no schema (RN-010): a role de aplicação não recebe UPDATE direto em snapshot; a escrita acontece apenas pela função SECURITY DEFINER de publish/republish.
Como verificar (teste primeiro): alterar a identidade visual ou as metas do cliente após um publish não muda o relatório já publicado; editar o rascunho não altera o snapshot até novo publish; um UPDATE direto em snapshot pela role de aplicação é recusado (só a função de publish escreve).
Aceite (H1): Dado um relatório publicado, Quando a identidade visual do cliente ou as metas são alteradas depois, Então o relatório publicado continua exibindo os valores copiados no momento do publish, E editar o rascunho não muda o snapshot já servido até um novo publish, E um UPDATE direto em snapshot fora da função de publish é recusado (RN-010).
RF-ING-005, RN-006. Criativos e imagens referenciados por asset_id + sha256, nunca URL crua ou base64 no payload.
Como verificar (teste primeiro): inspecionar o payload de um bloco de mídia; confirmar ausência de URL crua e de base64 embutido.
Aceite (H1): Dado um criativo inserido num relatório, Quando o payload do bloco é gravado, Então o bloco referencia asset_id e sha256 do asset, E o payload não contém a URL crua nem o conteúdo base64 da imagem.
RN-001 a RN-006 (DRS E.4). Integridade cruzada: public_token vive no report (RN-001); snapshot.payload copia metas/branding (RN-002); todo número referencia dp: (RN-003); artifact respeita purge_after sem quebrar o publicado (RN-004); comment.block_id vira orphaned quando o bloco some (RN-005); asset por asset_id+sha256 (RN-006, reforço de {F2-05}).
Como verificar (teste primeiro): suíte de integridade cobre as 6 regras com um caso positivo e um caso de violação para cada.
Aceite (H1): Dado o schema reports povoado, Quando a suíte de integridade roda, Então as 6 regras de negócio de dados passam, E nenhuma violação passa despercebida.
Definição de Pronto F2: contrato de bloco v3 estável, snapshot imutável provado, e as 6 regras de negócio de dados verdes. Serve de base para F3 (renderizador), F8 (compositor) e F13 (analytics).
Fase F3, Package renderizador (Fatia 3)
Dependência: {F2-01} (contrato de bloco). Package de funções puras que serve leitor, preview e PDF a partir do mesmo contrato.
RF-REN-004. Markdown sanitizado com allowlist em todos os consumidores (leitor, preview, PDF).
Como verificar (teste primeiro): um bloco com script embutido no corpo não executa em nenhum dos três consumidores.
Aceite (H1): Dado um bloco cujo corpo contém um script embutido, Quando qualquer consumidor renderiza o bloco, Então o script é removido pela sanitização, E nenhum HTML executável do corpo chega ao navegador do cliente.
RF-REN-005, ADR-008, RNF-MANUT-001. Funções puras que emitem HTML, versionado como package, sem segunda implementação de blocos no React ou na composição.
Como verificar (teste primeiro): um mesmo documento renderizado pelo leitor, pelo preview e pelo PDF usa as mesmas funções (mesma saída de HTML para os mesmos dados); busca no código não encontra uma segunda implementação de bloco.
Aceite (H1): Dado um mesmo documento de relatório, Quando ele é renderizado pelo leitor, pelo preview e pelo gerador de PDF, Então os três usam as mesmas funções do package renderizador, E não existe uma segunda implementação dos blocos fora do package.
RF-REN-007. Conjunto de tokens --r-* por cliente, aplicando as cores da ficha de identidade.
Como verificar (teste primeiro): dois clientes com paletas diferentes renderizam com os tokens do respectivo cliente; trocar a paleta de um não afeta a página já publicada do outro.
Aceite (H1): Dado dois clientes com paletas diferentes, Quando seus relatórios são renderizados, Então cada página aplica os tokens --r-* do respectivo cliente, E trocar a paleta de um cliente não afeta a página já publicada do outro.
RNF-A11Y-001. As duas superfícies de leitura atendem contraste AA nos tokens claro e escuro.
Como verificar (teste primeiro): auditoria de contraste automatizada (axe ou equivalente) nas duas superfícies com os dois temas de tokens.
Aceite (H1): Dado o leitor e o PDF renderizados com os tokens do cliente, Quando a auditoria de contraste roda, Então ambos atendem AA nos dois conjuntos de tokens (claro e escuro).
RNF-MANUT-001. O renderizador vive como package próprio com versão, não como código espalhado pelos consumidores.
Como verificar (teste primeiro): o package tem número de versão próprio e é importado (não copiado) pelos três consumidores.
Aceite (H1): Dado o package renderizador, Quando um consumidor precisa dele, Então ele importa a dependência versionada, E nenhum consumidor mantém cópia própria do código de renderização.
Definição de Pronto F3: os três consumidores (leitor, preview, PDF) renderizam do mesmo package, sanitizados, com tokens por cliente e contraste AA.
Fase F4, Ingestão, quota e storage (Fatia 4)
Dependência: {F1-02} (sandbox de parse isolado), {F1-16} (interface de storage), {F1b-01} (tabela artifact). Entrada de dados do sistema.
RF-ING-001. Aceita planilha, CSV, PDF, Markdown, JSON, imagem; recusa tipos fora da lista.
Como verificar (teste primeiro): subir um arquivo de cada tipo suportado é aceito; um tipo fora da lista é recusado com mensagem clara.
Aceite (H1): Dado o passo de dados do wizard, Quando o operador sobe um arquivo de tipo planilha, CSV, PDF, Markdown, JSON ou imagem dentro da quota, Então o arquivo é aceito e associado à composição, E um tipo fora dessa lista é recusado com mensagem clara.
RF-ING-002. Quota (nº de artefatos, MB, páginas de PDF) validada antes de tokenizar; limites admin-editáveis.
Como verificar (teste primeiro): subir um conjunto acima da quota é recusado antes de qualquer conteúdo chegar ao parse ou à composição; a mensagem aponta o limite ultrapassado.
Aceite (H1): Dado a quota configurada em N artefatos e M megabytes, Quando o operador sobe um conjunto que ultrapassa N ou M, Então o sistema recusa antes de enviar qualquer conteúdo à composição de IA, E exibe qual limite foi ultrapassado.
RF-ING-003. Ligar o upload ao sandbox de parse já construído em {F1-02} (container sem rede de saída, magic bytes, XXE desligado, limite de razão de descompressão, SVG sanitizado, xlsm rejeitado, fail-closed); o resultado saneado alimenta a composição.
Como verificar (teste primeiro): um arquivo subido pela ingestão passa pelo sandbox de {F1-02}; o container de parse não resolve DNS externo; um xlsm é rejeitado, um SVG com script é sanitizado, um zip bomb acima da razão limite é recusado.
Aceite (H1): Dado um arquivo submetido ao parse, Quando o parser roda, Então ele executa sem acesso de rede de saída, E um xlsm é rejeitado, um SVG com script é sanitizado, e um arquivo compactado com razão de descompressão acima do limite é recusado.
RF-ING-004, RNF-LGPD-001. Artefato apagado do storage após TTL curto; relatório publicado permanece íntegro.
Como verificar (teste primeiro): simular expiração do TTL de um artefato de um relatório já publicado; confirmar remoção do storage e integridade do relatório.
Aceite (H1): Dado um relatório publicado, Quando o TTL do artefato cru expira, Então o artefato cru é apagado do storage, E o relatório publicado continua íntegro sem depender do artefato.
RF-ING-006. Etapa opcional, roda sem os artefatos do cliente no mesmo contexto, só fontes da allowlist, achados com fonte e URL nomeadas.
Como verificar (teste primeiro): ligar a pesquisa de mercado num relatório de teste; confirmar isolamento de contexto e que cada achado usado carrega fonte e URL.
Aceite (H1): Dado que o operador liga a pesquisa de mercado para um relatório, Quando a etapa roda, Então ela executa isolada dos artefatos do cliente e consulta apenas fontes da allowlist, E cada achado usado no relatório carrega a fonte e a URL de origem nomeadas.
RF-BND-002. Sem gestão, CRUD, biblioteca persistente nem histórico de artefatos crus; nenhuma tela de acervo.
Como verificar (teste primeiro): varredura de rotas confirma ausência de tela de listagem/edição/reabertura de artefatos crus como acervo.
Aceite (H1): Dado um artefato subido para compor um relatório, Quando a composição termina e o relatório é publicado, Então o artefato cru é descartado após o TTL configurado, E não existe nenhuma tela no sistema que liste, edite ou reabra artefatos crus como acervo.
Definição de Pronto F4: upload, quota, parse isolado e TTL funcionando ponta a ponta com um conjunto de artefatos real; nenhuma tela de acervo de artefatos.
Fase F5, Leitor público (Fatia 5)
Dependência: {F2-04} (snapshot imutável), {F3-02} (package renderizador). Serviço isolado somente leitura que serve o snapshot ao cliente final.
RF-REN-006, ADR-003, RNF-SEG-001 (caminho do leitor), RN-011. Cria a role reports_reader com GRANT de SELECT apenas em report, snapshot e comment (não no schema inteiro, que exporia rascunho, artefato e metas), sem alcance a outra tabela do schema nem a outro schema, sem query pesada. Habilita ROW LEVEL SECURITY nessas três tabelas e cria a policy PERMISSIVE do reports_reader que libera SÓ linhas de relatório com status publicado e não expirado (e o snapshot e os comment ligados a elas), resolvidas pelo public_token; essa PERMISSIVE do leitor convive com a RESTRICTIVE por company_id do operador ({F6-04}), porque cada policy é escopada pela sua role.
Como verificar (teste primeiro): a role reports_reader lê report, snapshot e comment de um relatório PUBLICADO e não expirado (permitido); tentar ler um relatório em rascunho, um não publicado, um expirado e um de outra agência retorna ZERO linhas (a PERMISSIVE só libera publicado+vigente); tentar um INSERT/UPDATE (recusado), consultar outro schema (recusado) e ler uma quarta tabela do próprio schema como report_draft, artifact ou report_goal (recusado).
Aceite (H1): Dado o serviço do leitor público, Quando ele resolve um link, Então usa a role reports_reader com SELECT apenas em report, snapshot e comment e uma policy PERMISSIVE que só devolve linhas de relatório publicado e não expirado, E não devolve rascunho, relatório não publicado, expirado ou de outra agência, E não tem permissão para escrever nem para ler report_draft/artifact/report_goal ou qualquer outro schema do Hub.
RF-REN-010. Link revogado ou expirado mostra página brandada com a marca da agência, sem conteúdo do relatório.
Como verificar (teste primeiro): abrir um link revogado e um expirado; confirmar página brandada e ausência total do conteúdo do relatório.
Aceite (H1): Dado um link revogado ou expirado, Quando o cliente o abre, Então vê uma página brandada com a identidade da agência e a mensagem para pedir novo link, E nenhum conteúdo do relatório é exibido.
RNF-SEG-005. Referrer-Policy: no-referrer, assets same-origin, token mascarado em log, resolução token para snapshot no-store.
Como verificar (teste primeiro): inspecionar os headers de resposta do leitor; conferir ausência do token cru em log de acesso.
Aceite (H1): Dado o leitor público, Quando ele serve uma página, Então envia Referrer-Policy: no-referrer e não registra o token cru em log, E os assets são servidos same-origin.
RNF-PERF-001. Página servida do cache; resolução token para snapshot sem varredura custosa.
Como verificar (teste primeiro): medir tempo de resposta de um link já publicado com cache quente; confirmar ausência de query pesada no caminho.
Aceite (H1): Dado um link já publicado, Quando o cliente o abre, Então a página responde a partir do cache sem query pesada, E a resolução token para snapshot não faz varredura custosa.
RNF-DISP-001. Leitor público e container de parse isolado têm deploy independente da fila de release do Hub; o chrome e a composição in-process acompanham a fila.
Como verificar (teste primeiro): publicar uma correção no leitor ou no container de parse sem passar pelo pipeline de release do chrome do Hub.
Aceite (H1): Dado uma janela em que a fila de release do Hub está congelada, Quando é preciso subir uma correção no leitor público ou no container de parse, Então ela sobe sem depender do deploy do Hub. O leitor tem disponibilidade maior que o painel e não há SLA contratual por ser ferramenta interna (DRS J.4).
Definição de Pronto F5: leitor público no ar, isolado, rápido, com estados brandados e deploy próprio.
Fase F6, Role, entitlement e RBAC no Hub (Fatia 6)
Dependência: {F1b-01} (tabelas de dados já criadas). Provisiona o registro do módulo, o entitlement, o RBAC, a RLS e a auditoria que as fatias de chrome (F7, F8) e publicação (F9) vão usar. As tabelas de dados nasceram em F1b; aqui entram as camadas que dependem do catálogo do Hub.
RF-ADM-001; artefato ART-MIG-01. Cunhar o module_key novo reports: migration que adiciona o literal reports à restrição CHECK da tabela de entitlements de módulo do Hub, mais a entrada reports (rótulo "Relatórios", não core) no catálogo de módulos em código. Não reusa o módulo results. As tabelas de dados já existem (F1b).
Como verificar (teste primeiro): após a migration, uma Company pode receber o entitlement reports; nenhuma Company existente recebe o módulo ligado automaticamente; o catálogo de módulos em código lista reports.
Aceite (H1): Dado o Hub sem o módulo reports, Quando a migration de registro roda, Então o literal reports está na CHECK de módulos válidos e no catálogo de módulos em código, E nenhuma Company recebe o entitlement ligado por default.
RF-ADM-001. Entitlement reports per-Company, desligado por default, ligado pelo Super Admin.
Como verificar (teste primeiro): criar uma Company nova e confirmar que nenhuma rota/menu do Reports aparece; ligar o entitlement pelo painel do Super Admin e confirmar que a entrada aparece sem novo deploy.
Aceite (H1): Dado uma Company recém-criada, Quando ninguém ligou o entitlement reports, Então nenhuma rota, menu ou atalho do ModularReports aparece para os usuários dessa Company, E ligar o entitlement no painel do Super Admin passa a exibir a entrada do Reports sem novo deploy.
RF-ADM-002; artefato ART-ACS-01. Sem papel novo; slugs de permissão reports.* registrados no catálogo de permissões em código do Hub (defaults por papel mais metadados de capability), não numa tabela nova; override por usuário reusa a concessão de capability existente.
Como verificar (teste primeiro): o catálogo em código lista todas as permissões do Reports com prefixo reports.; nenhum papel novo aparece na lista de papéis do sistema; nenhuma tabela nova de permissões foi criada.
Aceite (H1): Dado o catálogo de permissões do Hub, Quando o módulo reports é instalado, Então todas as permissões do Reports têm slug com prefixo reports. registrado no catálogo, E não existe nenhum papel novo criado exclusivamente para o Reports.
RNF-SEG-001 (caminho do operador). Todas as tabelas do schema com RLS RESTRICTIVE por company_id para os usuários de agência autenticados, REVOKE ALL, helpers SECURITY DEFINER com search_path fixado. Esta RESTRICTIVE por company_id é escopada às roles dos usuários de agência e NÃO afeta a policy PERMISSIVE do reports_reader criada em {F5-01} (o leitor público não tem company_id de sessão e é liberado por token, não por company): as duas convivem, cada uma na sua role.
Como verificar (teste primeiro): um usuário da agência A consulta as tabelas do Reports e só recebe linhas de A; tentar contornar por FK cross-schema falha; confirmar que habilitar a RESTRICTIVE do operador não zera a leitura do reports_reader de um relatório publicado (a PERMISSIVE do leitor continua valendo).
Aceite (H1): Dado um usuário da agência A, Quando ele consulta qualquer dado do Reports, Então só recebe registros com company_id de A, E nenhuma FK cross-schema contorna esse escopo, E a leitura do reports_reader de um relatório publicado segue funcionando após a RESTRICTIVE do operador entrar.
RF-ADM-012; artefato ART-AUD-01. Toda ação relevante grava evento no log de auditoria do Hub com event_type prefixado reports., por um ponto único (o helper de auditoria do Hub), gravando valor anterior e valor novo quando altera estado; append-only por RLS; o log de republicação (RF-PUB-010) deriva desses eventos.
Como verificar (teste primeiro): publicar, republicar, revogar e encerrar cliente geram evento no log com antes e depois quando aplicável; buscar no código confirma um único ponto de escrita de auditoria; nenhum evento contém dado pessoal cru.
Aceite (H1): Dado qualquer ação de publicar, republicar, revogar ou encerrar cliente, Quando ela é executada, Então um evento com event_type iniciado por reports. é gravado no log do Hub com valor anterior e novo quando aplicável, E não há caminho de escrita de auditoria fora do ponto único.
Definição de Pronto F6: schema provisionado, entitlement desligado por default, RBAC sem papel novo, RLS provado com dois tenants, auditoria por ponto único.
Fase F7, Chrome e shell do operador (Fatia 7)
Dependência: {F6-02} (entitlement), {F6-03} (RBAC). Grupo de rotas isolado que o operador usa.
RF-ADM-014, ADR-002. Menu e chrome próprios, não usa o menu lateral do Hub; entrada por item de menu do Hub, atalhos e endereço direto; mesma sessão.
Como verificar (teste primeiro): logado no Hub com o módulo ligado, clicar na entrada do Reports abre chrome próprio sem novo login; acessar o endereço direto também resolve na mesma sessão.
Aceite (H1): Dado um usuário logado no Hub com o módulo reports ligado, Quando ele clica na entrada do Reports, Então abre o chrome próprio do Reports (menu próprio, não o menu do Hub) na mesma sessão sem novo login, E o endereço direto do Reports também resolve na mesma sessão.
RNF-MANUT-002. Chrome do Reports carrega sob demanda; o white-label runtime do Hub não pinta o chrome do Reports.
Como verificar (teste primeiro): medir o bundle do Hub sem o Reports carregado; confirmar que o chrome do Reports só entra na rede ao navegar para ele.
Aceite (H1): Dado o bundle do Hub, Quando o usuário não navega para o Reports, Então o código do chrome do Reports não é carregado, E navegar para o Reports carrega o grupo de rotas sob demanda.
RF-ADM-003. Admin da agência gerencia (criar, editar, desativar) só os usuários da própria agência.
Como verificar (teste primeiro): um admin da agência A cria, edita e desativa usuários de A; tentar listar ou alterar usuário de outra agência é recusado.
Aceite (H1): Dado um admin da agência A, Quando ele gerencia usuários, Então consegue criar, editar e desativar usuários apenas da agência A, E não enxerga nem altera usuários de qualquer outra agência.
RF-ADM-004. Logo e cor da ficha da Company brandizam chrome do operador e link público.
Como verificar (teste primeiro): agência A com logo e cor definidos; abrir o chrome e publicar um link mostram a marca de A; nenhuma outra marca aparece.
Aceite (H1): Dado a agência A com logo e cor definidos na ficha da Company, Quando um operador de A usa o Reports e publica um link, Então o chrome do operador e a página pública exibem o logo e a cor de A, E nenhuma marca de outra agência aparece.
Definição de Pronto F7: chrome isolado no ar, lazy, com self-service de agência e identidade da agência aplicada.
Fase F8, Wizard e compositor (Fatia 8)
Dependência: {F1} (pipeline IA), {F2} (contrato de bloco), {F3} (package renderizador, o preview do compositor usa as mesmas funções puras), {F6}/{F7} (chrome, RBAC). Onde o operador compõe e cura.
RF-CMP-001. Coleta cliente, período e tipo (template); não avança sem os dois primeiros.
Como verificar (teste primeiro): selecionar cliente, período e tipo e avançar cria o rascunho vinculado; tentar avançar sem cliente ou período é bloqueado.
Aceite (H1): Dado o wizard aberto, Quando o operador seleciona cliente, período e tipo e avança, Então o rascunho é criado vinculado a esse cliente e período com o template escolhido, E não é possível avançar sem cliente e período preenchidos.
RF-CMP-002, Ap.1. Coleta KPI principal, intenção, destaques, identidade; KPI e intenção obrigatórios.
Como verificar (teste primeiro): preencher os 4 campos e avançar grava o briefing no rascunho; tentar avançar sem KPI principal ou intenção é bloqueado.
Aceite (H1): Dado o passo de briefing, Quando o operador preenche KPI principal, intenção, destaques e identidade e avança, Então esses parâmetros ficam gravados no rascunho e disponíveis ao pipeline de IA, E o KPI principal e a intenção são obrigatórios para avançar.
RF-CMP-003. Preview do que a IA entendeu mais "o que não achei" em destaque.
Como verificar (teste primeiro): artefato com um dado esperado pela intenção mas ausente faz esse dado aparecer na lista "o que não achei", não some.
Aceite (H1): Dado artefatos processados no passo de dados, Quando a leitura da IA termina, Então o operador vê um preview do que a IA entendeu e uma lista explícita "o que não achei", E um dado esperado pela intenção mas ausente nos artefatos aparece nessa lista, não some em silêncio.
RF-CMP-004. Composição proposta bloco a bloco com origem de cada número; confirmar abre no compositor.
Como verificar (teste primeiro): cada bloco proposto exibe a origem de seus números; confirmar abre o rascunho no compositor com esses blocos.
Aceite (H1): Dado a leitura concluída, Quando o wizard propõe a composição, Então cada bloco proposto exibe a origem de cada número (artefato e célula ou campo), E ao confirmar, o rascunho abre no compositor com esses blocos.
RF-CUR-001. Editar texto, reordenar e esconder blocos; bloco escondido não vai ao snapshot.
Como verificar (teste primeiro): editar, reordenar e esconder um bloco persiste; o bloco escondido não aparece no preview nem no snapshot publicado depois.
Aceite (H1): Dado um rascunho aberto no compositor, Quando o operador edita o texto de um bloco, reordena e esconde outro, Então as mudanças persistem no rascunho, E um bloco escondido não aparece no preview nem irá para o snapshot.
RF-CUR-002. Regenerar um bloco com instrução nova mantém proveniência e não altera os demais.
Como verificar (teste primeiro): pedir regenerar um bloco com instrução ("foco no público 25 a 34") recompõe só aquele bloco mantendo dp: dos números; os demais blocos permanecem intactos.
Aceite (H1): Dado um bloco no compositor, Quando o operador pede regenerar com uma instrução, Então só aquele bloco é recomposto pela IA mantendo a proveniência dos números, E os demais blocos permanecem intactos. Regenerar um bloco é resposta imediata, síncrona (DRS J.4).
RF-CUR-003. Inserir imagem de anúncio como bloco de mídia, referenciando o asset por asset_id+sha256.
Como verificar (teste primeiro): inserir um criativo adiciona um bloco de mídia ao rascunho e aparece no preview; o payload não guarda URL crua nem base64.
Aceite (H1): Dado o compositor, Quando o operador insere um criativo, Então um bloco de mídia referenciando o asset por identificador e hash é adicionado ao rascunho, E o criativo aparece no preview.
RF-CUR-004. No compositor, dentro da whitelist de campos editáveis; refletido no preview e copiado no snapshot.
Como verificar (teste primeiro): trocar cor primária ou logo reflete no preview imediatamente; publicar copia o ajuste para o snapshot.
Aceite (H1): Dado o compositor, Quando o operador troca a cor primária ou o logo do cliente, Então o preview reflete a mudança imediatamente, E o ajuste é gravado para ser copiado no snapshot na publicação.
RF-CUR-005. Cada tipo de bloco declara no schema os campos editáveis; compositor só expõe esses.
Como verificar (teste primeiro): abrir a edição de um bloco só mostra os campos da whitelist; um valor numérico com proveniência não aparece como editável.
Aceite (H1): Dado um tipo de bloco com whitelist de campos editáveis, Quando o operador abre a edição desse bloco, Então só os campos da whitelist são editáveis, E campos fora da whitelist (por exemplo, o valor numérico com proveniência) não são editáveis à mão.
RF-CUR-006. Modo apresentação (tela cheia, navegação por passos), rótulo "rascunho", sem pins de comentário.
Como verificar (teste primeiro): entrar no modo apresentar abre tela cheia com o rótulo "rascunho"; sair volta ao compositor sem ter publicado nada.
Aceite (H1): Dado um rascunho no compositor, Quando o operador entra no modo apresentar, Então a apresentação abre em tela cheia com o rótulo "rascunho" e sem os pins de comentário, E sair do modo apresentar volta ao compositor sem ter publicado nada.
RF-CUR-007. Publicar exige sessão humana com a capability reports.publish; ator de serviço é recusado (reforça {F1-06}).
Como verificar (teste primeiro): tentar publicar via chamada de API com um ator sem reports.publish é recusado; publicar via sessão humana com reports.publish conclui.
Aceite (H1): Dado um rascunho pronto, Quando a publicação é acionada, Então ela só conclui a partir de uma sessão humana com a capability reports.publish, E qualquer tentativa de publicação por ator de serviço é recusada.
RNF-PERF-002. A composição é síncrona in-process (10 a 30 s); o wizard mostra o wait com fail-loud enquanto processa. Uma fila assíncrona com estado persistido (report_job) é evolução, não V1.
Como verificar (teste primeiro): disparar uma composição mostra o wait no wizard e conclui de forma síncrona; uma falha da composição aparece explícita (fail-loud), não em silêncio.
Aceite (H1): Dado uma composição disparada no wizard, Quando ela roda, Então o wizard mostra o wait e devolve o resultado de forma síncrona, E uma falha aparece explícita ao operador. A fila assíncrona (report_job) fica reservada para evolução (DRS RNF-PERF-002).
Definição de Pronto F8: wizard completo dos 4 passos e compositor com curadoria, regenerar, criativos, whitelist e apresentar, todos com proveniência preservada.
Fase F9, Publicação e link (Fatia 9, o gate de adoção mede-se aqui)
Dependência: {F2-04} (snapshot), {F8} (compositor). Onde o link nasce.
RF-CMP-007. Publicação bloqueada se qualquer número não tiver dp: (gate HARD, reforça {F1-04}).
Como verificar (teste primeiro): tentar publicar um relatório com um número sem dp: é recusado, apontando exatamente qual número está sem origem.
Aceite (H1): Dado um relatório com ao menos um número sem dp: de origem, Quando o operador tenta publicar, Então a publicação é recusada, E o sistema aponta exatamente quais números estão sem origem.
RF-PUB-002. Token de no mínimo 128 bits, noindex, rate-limit.
Como verificar (teste primeiro): medir o tamanho do token gerado; confirmar header noindex; disparar requisições acima do limite é barrado.
Aceite (H1): Dado um relatório publicado, Quando o link é gerado, Então o token tem no mínimo 128 bits, a página responde com noindex, E requisições acima do rate-limit ao caminho do link são barradas.
RF-PUB-003. Senha de 4 palavras opcional; default sem senha.
Como verificar (teste primeiro): um link com senha pede a senha antes de exibir conteúdo; um link sem senha abre direto.
Aceite (H1): Dado um link com senha definida, Quando alguém abre o link, Então a página pede a senha antes de exibir o conteúdo, E um link sem senha definida abre direto.
RF-PUB-004. Data de expiração opcional; default permanente.
Como verificar (teste primeiro): um link com expiração no passado responde como expirado ({F5-02}); um link sem expiração continua servindo.
Aceite (H1): Dado um link com data de expiração no passado, Quando alguém o abre, Então a página responde como expirada com o estado brandado, E um link sem expiração continua servindo indefinidamente.
RF-PUB-005. Modal de publicação gera QR code do link.
Como verificar (teste primeiro): ler o QR code exibido leva à mesma página do link publicado.
Aceite (H1): Dado um link publicado, Quando o operador abre o modal de publicação, Então um QR code que resolve para o link é exibido e copiável, E ler o QR leva à mesma página do link.
RF-PUB-006, Ap.7. Editar og:title, og:description e capa, com preview de como fica no WhatsApp.
Como verificar (teste primeiro): editar os 3 campos e publicar; compartilhar o link mostra exatamente o card revisado.
Aceite (H1): Dado o modal de publicação, Quando o operador edita og:title, og:description e a capa e publica, Então o link compartilhado exibe exatamente esses metadados no card, E o operador viu o preview do card antes de publicar.
RF-PUB-007. Sistema não envia o link automaticamente; oferece copiar link e QR.
Como verificar (teste primeiro): publicar um relatório não dispara nenhuma mensagem automática ao cliente; o link e o QR ficam disponíveis para copiar.
Aceite (H1): Dado um relatório recém-publicado, Quando a publicação conclui, Então o sistema oferece copiar o link e o QR mas não dispara nenhuma mensagem ao cliente automaticamente, E o envio depende de uma ação manual do operador.
RF-PUB-008. Revogar purga o cache e passa a responder 410 com página brandada.
Como verificar (teste primeiro): revogar um link em cache; confirmar 410 e ausência do conteúdo antigo em qualquer camada de cache.
Aceite (H1): Dado um link ativo em cache, Quando o operador o revoga, Então o cache é purgado e o link passa a responder 410 com a página brandada de link revogado, E o conteúdo anterior não é mais servido de nenhum cache.
RF-PUB-009. Republicar sobrescreve o snapshot; sem galeria de versões.
Como verificar (teste primeiro): editar o rascunho e republicar mantém o mesmo link, servindo o snapshot novo; nenhuma segunda URL ou galeria surge.
Aceite (H1): Dado um relatório já publicado, Quando o operador republica após editar o rascunho, Então o mesmo link passa a servir o snapshot novo, E não é criada nenhuma versão navegável separada da página.
RF-PUB-010. Republicar grava log humano (quem, o quê, onde, quando), visível só para a agência.
Como verificar (teste primeiro): republicar gera entrada no histórico de alterações visível na agência; nenhuma superfície do leitor público expõe esse histórico.
Aceite (H1): Dado um relatório republicado, Quando alguém da agência abre o histórico de alterações, Então vê a lista de alterações em linguagem humana, E nenhuma superfície do leitor público expõe esse histórico ao cliente.
RF-COM-004, RN-005. No fluxo de republicação, varrer os comentários ancorados: quando o bloco ancorado sumiu do novo snapshot, marcar o comentário orphaned e preservar o excerpt, sem apagar nem versionar a página. É o ponto onde o orphaned é setado (a dependência F12 para F9).
Como verificar (teste primeiro): republicar um relatório em que um bloco com comentário foi removido marca aquele comentário como orphaned com o excerpt preservado; comentários de blocos que sobreviveram continuam ancorados.
Aceite (H1): Dado um relatório com comentários ancorados, Quando ele é republicado sem um dos blocos comentados, Então o comentário daquele bloco fica orphaned com o excerpt preservado, E os comentários dos blocos que permaneceram seguem ancorados ao id estável.
Definição de Pronto F9: publicar, revogar, republicar e o modal completo (senha, expiração, QR, card) funcionando ponta a ponta, com a republicação setando orphaned nos comentários dos blocos removidos; este é o marco que o critério de adoção (A.7) mede.
Fase F10, Templates (Fatia 10)
Dependência: {F1b-01} (tabela template), {F8} (wizard usa o template no passo 1).
RF-ADM-009. CRUD completo pelo painel; blocos padrão editáveis sem tocar em código.
Como verificar (teste primeiro): iniciar um relatório com um template gera o rascunho com exatamente os blocos padrão na ordem definida; editar, duplicar e excluir o template funciona pelo painel.
Aceite (H1): Dado um template "resultado de campanha" com blocos padrão definidos, Quando o operador inicia um relatório escolhendo esse template, Então o rascunho nasce com exatamente os blocos padrão do template na ordem definida, E editar, duplicar ou excluir o template é possível pelo painel sem tocar em código.
RF-ADM-010. Salvar um relatório curado como template novo, sem copiar dados do cliente.
Como verificar (teste primeiro): salvar como template a partir de um relatório curado cria um template na biblioteca com a estrutura de blocos, sem números nem textos específicos do cliente original.
Aceite (H1): Dado um relatório curado, Quando o operador escolhe "salvar como template" e dá um nome, Então um template novo com a estrutura de blocos daquele relatório passa a aparecer na biblioteca de templates da agência, E o conteúdo específico do cliente (números, textos) não é copiado para o template.
Definição de Pronto F10: biblioteca de templates com CRUD completo e "salvar como template" funcionando.
Fase F11, Importação de metas (Fatia 11)
Dependência: {F2-04} (snapshot copia metas), {F9} (publish). A Fatia 9 já nasce com o recorte mínimo de leitura de metas para o bloco planejado versus realizado; esta fatia é a importação estruturada completa (DRS J.4).
RF-BND-003. Metas aparecem só como comparação planejado vs realizado dentro de um relatório; sem alerta, recálculo ou notificação fora dele.
Como verificar (teste primeiro): importar uma meta e compor um relatório mostra a comparação nos blocos; nenhum cron ou notificação dispara a partir dessa meta fora do relatório.
Aceite (H1): Dado um conjunto de metas importado para um cliente, Quando o operador compõe um relatório, Então as metas aparecem apenas como comparação planejado versus realizado nos blocos daquele relatório, E o sistema não dispara alerta, recalculo periódico nem notificação baseada nessas metas fora do relatório.
RF-ING-007; artefato ART-ENT-08 (report_goal). Operador importa metas, público-alvo e planejamento (JSON, Markdown ou planilha) por cliente, gravando em report_goal (criada em F1b); o snapshot copia as metas vigentes no publish (RN-008).
Como verificar (teste primeiro): importar um arquivo de metas para um cliente disponibiliza os valores planejados nos blocos de comparação do próximo relatório composto.
Aceite (H1): Dado um arquivo de metas e planejamento importado para um cliente, Quando o operador compõe um relatório, Então os valores planejados ficam disponíveis para os blocos de comparação planejado versus realizado, E a importação não cria monitoramento contínuo.
Definição de Pronto F11: importar metas e ver a comparação planejado vs realizado num relatório publicado, sem qualquer tracking contínuo criado.
Fase F12, Comentários (Fatia 12)
Dependência: {F5} (leitor público), {F2-01} (id estável do bloco), {F9-11} (quem seta orphaned na republicação é o fluxo de F9). Comentário logado ancorado.
RF-COM-001. Comentar exige login (Contact do Hub); nenhum comentário anônimo é gravado.
Como verificar (teste primeiro): um visitante não autenticado que tenta comentar é levado ao atalho de acesso do Hub; nenhum comentário sem autor autenticado aparece no banco.
Aceite (H1): Dado um visitante não autenticado numa página pública, Quando ele tenta comentar, Então o sistema pede login (via atalho de acesso do Hub) antes de aceitar o comentário, E nenhum comentário anônimo é gravado.
RF-COM-002. Escrita por endpoint separado autenticado; role do leitor segue sem permissão de escrita.
Como verificar (teste primeiro): enviar um comentário passa por um endpoint distinto do leitor; a role do leitor público ({F5-01}) continua sem qualquer INSERT/UPDATE possível.
Aceite (H1): Dado o serviço do leitor público, Quando um comentário é enviado, Então a escrita acontece por um endpoint separado autenticado, E a role do leitor público segue sem qualquer permissão de escrita.
RF-COM-003. Cada comentário carrega o id do bloco e o trecho citado (excerpt obrigatório).
Como verificar (teste primeiro): criar um comentário sobre um bloco grava o id do bloco e o excerpt; o comentário aparece ancorado ao bloco correto na leitura.
Aceite (H1): Dado um comentário criado sobre um bloco, Quando ele é gravado, Então carrega o id do bloco e o trecho citado, E o comentário aparece ancorado ao bloco correto na leitura.
RF-COM-004. O orphaned é setado no fluxo de republicação ({F9-11}); aqui a leitura exibe o comentário órfão com o excerpt e a marca "referente a versão anterior", sem versionar a página.
Como verificar (teste primeiro): comentar um bloco, remover esse bloco numa republicação ({F9-11}); o comentário permanece visível na leitura com a marca de versão anterior, sem versionar a página.
Aceite (H1): Dado um comentário ancorado num bloco, Quando esse bloco é removido numa republicação, Então o comentário permanece visível com o trecho citado e a marca "versão anterior", E o sistema não cria uma versão navegável da página para preservá-lo.
RF-COM-005. Resolver fecha o loop: notifica o autor e marca "resolvido".
Como verificar (teste primeiro): marcar um comentário como resolvido dispara notificação ao autor e muda o estado exibido.
Aceite (H1): Dado um comentário aberto, Quando o operador o marca como resolvido, Então o autor do comentário é notificado, E o comentário passa a exibir estado "resolvido".
RF-COM-006. Cliente sem sessão pede acesso e recebe magic link do Hub; comentário habilita após autenticar.
Como verificar (teste primeiro): um Contact provisionado sem sessão pede acesso pela página, recebe o magic link, autentica, e o campo de comentário fica habilitado.
Aceite (H1): Dado um cliente com Contact provisionado mas sem sessão, Quando ele pede acesso pela página, Então recebe um magic link do Hub que abre a sessão, E o campo de comentário fica habilitado após a autenticação.
Definição de Pronto F12: comentário logado, ancorado, sobrevivente a republicação e com resolução notificando o autor, funcionando ponta a ponta.
Fase F13, Analytics (Fatia 13)
Dependência: {F5} (leitor público emite os acessos), {F9} (link publicado). Medição de audiência.
RF-ANL-001. Evento de visualização ingerido por pipeline separado do render; não bloqueia o caminho somente-leitura.
Como verificar (teste primeiro): abrir uma página pública gera evento sem que o tempo de resposta do leitor dependa da escrita do evento.
Aceite (H1): Dado a leitura de uma página pública, Quando um evento de visualização é registrado, Então ele é ingerido por um pipeline separado do render, E a coleta de evento não bloqueia nem depende do caminho somente-leitura do leitor.
RF-ANL-002. Analytics começa com número de visualizações e último acesso por link.
Como verificar (teste primeiro): acessar um link várias vezes e conferir no painel do relatório o número de visualizações e a data/hora do último acesso.
Aceite (H1): Dado um link acessado várias vezes, Quando o operador abre o analytics do relatório, Então vê o número de visualizações e a data e hora do último acesso, E esses valores refletem os acessos reais registrados.
RF-ANL-003. Acessos logados da própria agência e o modo apresentar não entram na contagem.
Como verificar (teste primeiro): um operador da agência abre o link ou apresenta em reunião; a contagem de visualizações do cliente não sobe.
Aceite (H1): Dado que um operador da agência abre o link ou apresenta em reunião, Quando as métricas são contadas, Então esses acessos não entram na contagem de visualizações do cliente, E só acessos externos ao link contam.
RF-ANL-004. Visitante deslogado aparece agregado como "visitante" com identificador opaco; sem email cru no HTML.
Como verificar (teste primeiro): inspecionar o HTML público e o payload de analytics; confirmar ausência de email cru e presença de identificador opaco.
Aceite (H1): Dado um visitante deslogado, Quando ele é contabilizado, Então aparece agregado como "visitante" com identificador opaco, E nenhum email cru é exposto no HTML público.
RF-ANL-005. Origem inferida pelo referrer; ausência de referrer vira "origem desconhecida".
Como verificar (teste primeiro): acessos com e sem referrer disponível são registrados corretamente, sem inventar canal quando ausente.
Aceite (H1): Dado um acesso com referrer disponível, Quando a origem é registrada, Então ela reflete o referrer, E a ausência de referrer é registrada como origem desconhecida, sem inventar um canal.
RF-ANL-006. Admin vê agregados (visualizações, último acesso, comentários não lidos, duração de produção) só da própria agência.
Como verificar (teste primeiro): admin da agência A abre o painel e vê só os agregados de A; nenhum dado de outra agência aparece.
Aceite (H1): Dado um admin da agência A, Quando ele abre o painel de analytics, Então vê os agregados apenas dos relatórios de A, E não enxerga dados de nenhuma outra agência.
RF-ADM-015. Duração decorrida entre início da composição e publicação, disponível no painel de analytics.
Como verificar (teste primeiro): iniciar e publicar um relatório e conferir a duração registrada no painel (alimenta o critério de adoção A.7).
Aceite (H1): Dado um relatório iniciado no wizard, Quando ele é publicado, Então o sistema registra a duração decorrida entre o início da composição e a publicação, E essa duração fica disponível no painel de analytics da agência.
RF-ANL-001; artefato ART-INFRA-04. Container próprio que ingere os eventos de audiência e escreve em link_event, separado do leitor público (que é só-SELECT, RF-REN-006). O leitor emite o evento, o ingestor grava; a coleta não bloqueia o caminho somente-leitura.
Como verificar (teste primeiro): o ingestor é um processo distinto do leitor; a role do leitor segue sem INSERT; um pico de eventos não degrada o tempo de resposta do leitor.
Aceite (H1): Dado a leitura de uma página pública, Quando um evento é registrado, Então ele é gravado em link_event pelo ingestor separado, E a role do leitor público não tem permissão de escrita e o caminho de leitura não depende da escrita do evento.
Definição de Pronto F13: painel de analytics por agência com visualizações, último acesso, referrer, exclusão de sessão interna e duração de produção, isolado por tenant, alimentado por um ingestor de eventos separado do leitor.
Fase F14, Domínio custom e white-label (Fatia 14)
Dependência: {F7} (chrome), {F9} (link). Última camada de branding por agência.
RNF-SEG-006, ADR-015; artefato ART-ENT-10 (agency_domain). Criar a entidade agency_domain (company_id, domain, txt_token, status) e a validação: o vínculo de domínio custom por agência só ativa após validação de posse por registro TXT; desvínculo remove o CNAME. O proxy e o certificado on-demand são trabalho novo se a plataforma-mãe não tiver mecanismo próprio (confirmar antes desta fatia, ADR-015).
Como verificar (teste primeiro): a tabela agency_domain existe; tentar ativar um domínio custom sem o registro TXT correto falha; ativar com o TXT correto liga o CNAME e emite o certificado; desvincular remove o CNAME.
Aceite (H1): Dado uma agência que quer um domínio custom, Quando ela não tem o registro TXT de posse configurado, Então o vínculo não ativa, E configurar o TXT correto ativa o CNAME com certificado, E desvincular remove o CNAME.
RNF-SEG-006. A gestão de domínio custom já é uma seção da tela de identidade da agência no mockup (agencia-branding.html): o admin cadastra o domínio, vê o token TXT a configurar e acompanha o status (pendente, verificado). O que falta no mockup é o estado "pendente de validação TXT" antes do "propagado", a incluir numa iteração; a construção segue essa seção como referência.
Como verificar (teste primeiro): o admin cadastra um domínio, vê o token TXT, e a tela mostra o status mudar de pendente para verificado após a posse; a tela é escopada por agência.
Aceite (H1): Dado um admin de agência na tela de domínio custom, Quando ele cadastra um domínio, Então vê o token TXT a configurar e o status pendente, E após a validação de posse o status vira verificado, E a tela só mostra os domínios da própria agência.
Definição de Pronto F14: domínio custom por agência funcionando com validação de posse obrigatória, entidade agency_domain e tela de gestão; a tela nova entra numa iteração do mockup.
Fase F15, PDF condicional (Fatia 15)
Dependência: {F3-02} (package renderizador). Reuso do gerador da plataforma-mãe.
RF-REN-008. PDF gerado pelo package renderizador, tema claro com contraste AA; se o gerador não estiver disponível, a opção não é oferecida.
Como verificar (teste primeiro): com o gerador disponível, pedir o PDF produz um arquivo pelo mesmo package do leitor; com o gerador indisponível, a opção de PDF não aparece.
Aceite (H1): Dado um relatório publicado e o gerador de PDF disponível, Quando o operador pede o PDF, Então o PDF é gerado pelo package renderizador em tema claro com contraste AA e assets embutidos, E se o gerador não estiver disponível, a opção de PDF não é oferecida.
RF-REN-009. Leitor usa URL assinada de curta duração; PDF recebe assets em base64 pré-buscado, sem fetch de rede na geração.
Como verificar (teste primeiro): inspecionar as imagens do leitor (URL assinada com expiração curta) e do PDF gerado (base64 embutido, sem chamada de rede durante a geração).
Aceite (H1): Dado um relatório com criativos, Quando o leitor o exibe, Então as imagens carregam por URL assinada de curta duração, E o PDF do mesmo relatório embute os assets em base64 pré-buscado, sem buscar em rede durante a geração.
Definição de Pronto F15: PDF gerado pelo mesmo package do leitor, condicional à disponibilidade do gerador, sem fetch de rede na geração.
Fase F16, Painel do cliente (Fatia 16, última fatia)
Dependência: {F1b-01} (as tabelas de dados já nascem no schema do dia 1, incluindo o modelo de identidade do cliente). Fecha o ciclo de administração do cliente final.
RF-ADM-005, Ap.6. Reports lê e popula a ficha existente account_identidade_visual do Hub; tom de voz alimenta a narrativa (consumido desde {F1-09}).
Como verificar (teste primeiro): preencher a ficha de um cliente (cores, tom de voz) e confirmar que a composição e o publish seguinte usam esses valores; nenhuma segunda tabela de identidade é criada.
Aceite (H1): Dado um cliente com ficha de identidade visual preenchida (cores válidas e tom de voz), Quando um relatório é composto e publicado para ele, Então os tokens visuais da página usam as cores da ficha, E o tom de voz da ficha é injetado como parâmetro da narrativa da IA.
RF-ADM-006. Ficha vazia bloqueia a publicação ou usa default declarado da agência com aviso explícito.
Como verificar (teste primeiro): tentar publicar para um cliente sem ficha preenchida mostra o bloqueio ou o aviso "usando identidade padrão da agência"; nenhum caso publica com identidade inferida em silêncio.
Aceite (H1): Dado um cliente sem ficha de identidade preenchida, Quando o operador tenta publicar, Então o sistema bloqueia e pede o preenchimento, ou aplica o default declarado da agência exibindo o aviso "usando identidade padrão da agência", E em nenhum caso o sistema inventa cor ou tom sem aviso.
RF-ADM-007. Operador cria Account do Hub sem sair do chrome do Reports.
Como verificar (teste primeiro): criar um cliente novo pelo Reports grava um Account vinculado à Company da agência; o operador continua no chrome do Reports.
Aceite (H1): Dado um operador no ModularReports, Quando ele cria um cliente novo, Então o registro é gravado como Account do Hub vinculado à Company da agência, E o operador continua no chrome do Reports sem ser jogado para o chrome do Hub.
RF-ADM-008. Atalho "ativar acesso" aciona a função base do Hub; sem convite/senha próprios.
Como verificar (teste primeiro): clicar em "ativar acesso" na ficha de um Contact sem acesso aciona a ativação nativa do Hub; nenhum fluxo de convite próprio do Reports existe.
Aceite (H1): Dado um Contact sem acesso ativado, Quando o operador clica em "ativar acesso" na ficha do cliente, Então o sistema aciona a ativação de acesso do Hub para aquele Contact, E o Reports não cria fluxo de convite ou senha próprio.
RF-ADM-011, RNF-LGPD-002. Encerrar cliente revoga todos os links, comentários e analytics; retenção admin-editável; processo de eliminação explícito.
Como verificar (teste primeiro): encerrar um cliente com links publicados faz todos responderem como revogados ({F9-08}); o processo de eliminação apaga snapshots e anonimiza a origem (comentários, watermark, Contact) conforme a retenção, e as referências do log deixam de resolver sem o log append-only ser mutado (RN-007).
Aceite (H1): Dado um cliente com links publicados, Quando o admin encerra esse cliente, Então todos os links do cliente passam a responder como revogados, E o processo de eliminação apaga os snapshots e anonimiza os comentários na origem conforme a retenção, E a pseudonimização na origem faz as referências do log de auditoria daquele cliente deixarem de resolver sem mutar o log append-only (RN-007).
RF-ADM-013. Ficha do cliente mostra relatórios organizados por ano, sem KPI de mídia contínuo.
Como verificar (teste primeiro): abrir a ficha de um cliente com relatórios em anos diferentes mostra o agrupamento por ano; nenhum dashboard de métrica contínua aparece na ficha.
Aceite (H1): Dado um cliente com relatórios publicados em anos diferentes, Quando o operador abre a ficha do cliente, Então os relatórios aparecem agrupados por ano, E a ficha não exibe nenhum KPI de mídia contínuo.
RF-REN-011. Portal de leitura para o Contact autenticado pela sessão do Hub (tela cliente-painel do mockup): lista os relatórios publicados dos Accounts a que ele pertence, com atalho para abrir cada um e ver comentários recentes. Só leitura, reusando o login base do Hub; sem composição, publicação ou administração.
Como verificar (teste primeiro): um Contact autenticado abre o portal e vê só os relatórios dos seus Accounts; um Contact de outro cliente não vê esses relatórios; nenhuma ação de compor/publicar/administrar aparece para o Contact.
Aceite (H1): Dado um Contact autenticado pela sessão do Hub, Quando ele abre o portal do cliente, Então vê a lista dos relatórios publicados para os seus Accounts com atalho para abrir cada um, E não vê relatório de cliente a que não pertence, E nenhuma ação de composição, publicação ou administração é oferecida.
Definição de Pronto F16: painel do cliente completo (identidade, criação, ativação de acesso, offboarding LGPD, pasta por ano) e o portal logado de leitura do cliente final. Com F16 fechado, as 16 fatias do bloco 9 do DRS estão entregues.
Encerramento
Com as 16 fatias fechadas, a seção Construção chega a 100% e resta a seção Testes (Plano de Testes, Alfa e Beta) para o projeto chegar a 100% geral. Os ciclos reais de adoção (J.3 do DRS) começam assim que a Fatia 9 (publicação) estiver no ar, em paralelo às fatias seguintes.