Pular para o conteúdo principal
Modular Development Style · DRS

ModularReports

Documento de Requisitos de Software

Publicado em 24 de julho de 2026 Modulareasy · modulareasy.com/mds
Progresso da implementação ...
Planejamento 0%
Construção 0%
Testes 0%
Progresso geral: 0%
Progresso consolidado do projeto. Detalhes no Roadmap de Implementação e no Plano de Testes.

ModularReports: Documento de Requisitos de Software

CampoValor
ProdutoModularReports
EstágioRascunho para validação
Última atualização2026-08-08
ResponsávelModulareasy
Plataforma-mãeModularHub (módulo reports)
Este documento descreve o ModularReports por inteiro: o que o sistema é, para quem serve, o que faz e o que não faz, como se comporta, como seus dados são organizados e em que ordem ele é construído. O ModularReports é um módulo da plataforma ModularHub, com aplicativo e chrome próprios. É a referência única sobre o sistema: quando houver dúvida sobre o que o ModularReports deve fazer, a resposta está aqui.
Documentos companheiros (a família MDS deste sistema): o Roadmap de Implementação (em que ordem construir e como verificar), o Inventário de Artefatos (o que precisa existir e como deve ser), as Jornadas dos Atores (como cada papel opera) e o Plano de Testes (Alfa e Beta). Um protótipo navegável aprovado guia o desenvolvimento visual (bloco 6). Duas decisões de produto seguem em aberto e vivem no Questionário de Elicitação; nenhuma delas trava a construção das ondas iniciais.

O que é o ModularReports em 1 minuto

ModularReports é um utilitário interno multi-agência de páginas de resultados para clientes. Um operador de agência joga os artefatos que tem em mãos (planilha, CSV, PDF, Markdown, JSON, imagem) num assistente de 4 passos, diz o que quer apresentar, e a inteligência artificial compõe a página em blocos. Os números vêm por extração determinística com proveniência (cada valor aponta para a célula ou o campo de origem); a IA escreve apenas a narrativa. O operador cura o rascunho no compositor (edita, reordena, esconde, regenera bloco, insere criativos), e só um humano publica. A publicação gera um link público secreto, brandado com a identidade da agência e do cliente, que serve um snapshot imutável. O cliente final lê sem login e comenta apenas se autenticado.

O ModularReports é um módulo do ModularHub: usa o mesmo banco, os mesmos usuários, as mesmas agências (Companies) e os mesmos clientes (Accounts e Contacts) da plataforma-mãe. Mas tem aplicativo e chrome próprios, não se mistura ao menu do Hub. É o padrão "aplicativo dentro da suíte" (mesma conta, apps com cara própria).

Para quem é o sistema

PúblicoO que faz no sistema
Operador Modulareasy (Super Admin)Liga o módulo reports por agência, cria a agência e o primeiro admin, audita uso, ajusta limites e retenção
Dono ou gestor de agência (Admin)Gerencia os usuários da própria agência, define a identidade visual dela e dos clientes, monta templates, acompanha o painel de analytics agregado
Operador da agência (Team Manager e Workforce)Compõe relatórios pelo wizard, cura no compositor, publica o link, apresenta em reunião, acompanha comentários
Cliente final (Contact do Hub)Recebe o link, lê a página de resultados sem login, e comenta ancorado em bloco se tiver acesso ativado

O que o sistema faz

  • Recebe artefatos heterogêneos (planilha, CSV, PDF, Markdown, JSON, imagem) como insumo do ato de compor, valida uma quota de entrada antes de processar, e faz o parse num ambiente isolado sem rede.
  • Compõe a página com IA a partir da direção do operador (objetivo, público, decisão que o leitor deve tomar, tom, o que enfatizar e evitar); o operador revisa e edita o prompt de composição exposto antes de enviar, e um compilador opcional transforma uma nota em linguagem humana nessa direção. Os números saem por extração determinística com proveniência e a IA escreve só a narrativa, com fail-loud sobre "o que não achei".
  • Cura no compositor: o operador edita texto, reordena, esconde e regenera blocos, insere criativos, ajusta cores e logo do cliente, faz ajuste fino de apresentação (espaçamento, tamanho de fonte, cor de botão) em passos dentro da escala de tokens, e pode apresentar o rascunho em reunião antes de publicar.
  • Protege a honestidade por construção: fato adverso aritmético (gasto sem retorno, meta estourada) é obrigatório na página, a métrica-âncora derivada do objetivo carrega um selo com o sinal calculado por código, e o publish reprova se um fato adverso foi suprimido ou o selo contradito.
  • Publica um link público secreto com token forte, noindex e rate-limit, servindo um snapshot imutável; senha e expiração são opcionais por relatório.
  • Serve o leitor público isolado (somente leitura) com o renderizador próprio, e gera PDF reusando o conversor de HTML da plataforma-mãe quando disponível.
  • Recebe comentários apenas de usuários logados (Contact do Hub), ancorados em bloco, com resolução que notifica o autor.
  • Mede a audiência do link (visualizações, último acesso, origem por referrer) e apresenta um painel de analytics agregado por agência.
  • Brandiza em dois níveis: a agência no chrome e no link, o cliente no artefato; o snapshot copia a identidade no momento da publicação.
  • Organiza os relatórios do cliente como uma pasta consultável por ano.
  • Administra tudo pelo painel (CRUD humano): agências, usuários, papéis, templates por tipo de relatório, identidade visual, limites, retenção e offboarding.

O que o sistema NÃO faz

  • NÃO gerencia campanhas. Sem gestão ou CRUD de campanhas de mídia, sem otimização, sem lançamento de anúncios. O artefato entra só como insumo do compositor.
  • NÃO é dashboard contínuo. Não há série temporal viva, não há re-sync de planilha, não há tracking de KPI ao longo do tempo. O KPI contínuo com metas é responsabilidade do módulo results do ModularHub (fronteira definida em RF-BND-001).
  • NÃO guarda histórico de dados. O relatório é uma foto do período; o artefato cru é descartado por TTL após a publicação.
  • NÃO faz prospecção nem pitch comercial. Só clientes reais já cadastrados; prospect está fora de escopo.
  • NÃO tem billing. Sem pricing, fatura, checkout ou cobrança. É ferramenta interna compartilhada com as agências parceiras.
  • NÃO cria conta própria de usuário. Login, cadastro e ativação de acesso do cliente final são função base do ModularHub; o Reports no máximo expõe o atalho "ativar acesso".
  • NÃO versiona páginas. Republicar reusa o mesmo link e sobrescreve o snapshot; não há biblioteca de versões da página, só um log de alterações interno.
  • NÃO substitui o site institucional nem o módulo results. Convive com ambos; nada migra por conta deste sistema.

Como ler este documento

O documento está organizado em blocos temáticos (1 a 13), seguidos de apêndices de referência. Os requisitos são identificados assim:

  • RF-MODULO-NNN, Requisito Funcional, numerado por módulo (exemplo: RF-CMP-006).
  • RN-NNN, Regra de Negócio que vale para todo o sistema.
  • RNF-CATEGORIA-NNN, Requisito Não-Funcional (segurança, desempenho, disponibilidade, etc).
  • Prioridade, cada requisito é classificado como Essencial, Alta ou Média.

Cada requisito funcional carrega o seu critério de aceitação binário (H1, no formato Dado, Quando, Então, E), suficiente para verificar pronto ou não pronto. Os mesmos critérios permitem medir, requisito a requisito, o que já foi entregue.


1. Visão Geral do Sistema

A.1 Nome do sistema

ModularReports (slug modularreports; módulo reports do ModularHub).

A.2 Tipo de sistema

Módulo da plataforma ModularHub (schema, role e entitlement próprios), com aplicativo web e chrome isolados para o operador, um leitor público estático e isolado para o cliente final, uma composição por inteligência artificial rodando in-process (síncrona) e um parse de arquivo isolado em container efêmero sem rede.

A.3 Problema que resolve

Montar uma página de resultados para o cliente hoje é um processo artesanal e lento: o operador coleta artefatos de fontes variadas (exportações de plataformas de mídia, planilhas, PDFs), lê os dados, decide a narrativa, escreve os blocos e publica manualmente. Cada relatório consome de horas a dias e depende de quem sabe montar o JSON ou operar a ferramenta certa. Não há um caminho único que receba qualquer artefato, componha com apoio de IA sob curadoria humana, garanta que todo número tenha origem verificável, e entregue um link público brandado com analytics. O ModularReports centraliza esse fluxo num único utilitário multi-agência.

A.4 Público-alvo

Agências do ecossistema Modulareasy (a própria Modulareasy e as agências parceiras) que já operam como Company no ModularHub e que entregam relatórios de resultados de mídia a seus clientes. Perfil de uso:

  • Operadores que montam relatórios de dois tipos de ocasião, que não se confundem: o propósito do relatório (fechamento de campanha, fechamento de período, pesquisa) e a cadência com que ele se repete (quinzenal, mensal, trimestral, semestral, anual, ou pontual quando não se repete). O propósito é o tipo do modelo; a cadência é atributo da instância e escolhe contra o que aquele relatório compara (RN-014).
  • Clientes finais que recebem a leitura por link e, eventualmente, comentam.
  • Sem necessidade de pipeline de KPI contínuo (esse papel é do módulo results).

A.5 Premissas

#Premissa
1O ModularReports é um módulo do ModularHub e compartilha banco, autenticação, agências (Companies), clientes (Accounts) e pessoas (Contacts, users_profile) da plataforma-mãe.
2As agências parceiras já existem como Company no ModularHub e seus operadores já têm login na plataforma-mãe.
3O cliente final que comenta é um Contact do ModularHub com acesso ativado; a gestão de acesso é função base do Hub.
4A identidade visual do cliente vive na ficha já existente do Hub (account_identidade_visual), tipada e com campo de tom de voz.
5O gerador de PDF (conversor de HTML) e o serviço de magic link já existem na plataforma-mãe e são reusados.
6O sistema não tem billing, não vende, e não gerencia campanhas.

A.6 Objetivos e não objetivos

Objetivos: transformar artefatos heterogêneos em uma página de resultados curada e brandada em minutos, com zero número sem origem verificável, entrega por link público seguro, e medição de audiência.

Não objetivos: substituir o módulo results, o site institucional ou qualquer outra ferramenta; gerenciar campanhas; guardar histórico de dados; cobrar; prospectar.

A.7 Critério de adoção

O sistema é considerado adotado quando 2 ciclos reais de relatório (um da Modulareasy e um de uma agência parceira) saem inteiramente pelo ModularReports com menos passos que o fluxo manual medido como baseline (aproximadamente 7 ações do operador humano e 10 passos macro do assistente, hoje distribuídos por várias sessões). O acompanhamento é um canário semanal: o percentual dos relatórios reais da semana que saíram pelo Reports.

Este critério é alcançável apenas se o sistema conseguir REPRODUZIR o relatório que a operação entrega hoje. Enquanto faltarem, no registro de blocos, tipos que os relatórios reais usam (medido em 2026-08-02: cinco dos dezesseis, RF-REN-012), o critério é inalcançável por construção, e não por falta de adoção: nenhum ciclo real sai inteiramente pelo Reports se o Reports não desenha as seções que aquele relatório tem. Por isso esses tipos são Essenciais e vêm antes de qualquer aposta de produto.


2. Atores e Jornadas

A visão de cada papel na cadeira do operador está detalhada no documento companheiro Jornadas dos Atores. Este bloco define o catálogo de atores e as capacidades; as jornadas descrevem o passo a passo.

B.1 Catálogo de atores

AtorPapel canônico do HubCapacidades no ModularReports
Super Adminsuper_adminLiga o entitlement reports por Company, cria a agência e o primeiro admin, edita limites, retenção e allowlist de fontes, marca e desmarca templates como Globais (gesto exclusivo dele, RN-013), audita uso
Admin de agênciaadminGerencia usuários da própria agência, define identidade visual da agência e dos clientes, cria e edita os templates individuais da própria agência (nunca um Global), vê analytics agregado, decide offboarding
Operadorteam_manager e workforceCompõe pelo wizard, cura no compositor, publica, apresenta, acompanha e resolve comentários, cria cliente novo de dentro do Reports
Cliente finalcontactLê a página pública sem login, comenta ancorado em bloco quando tem acesso ativado, e (opcional) acompanha os relatórios pelo portal logado

B.2 Fronteira com o cliente final

O cliente final nunca opera o ModularReports (não compõe, não publica, não administra). Ele recebe um link e lê. Para comentar, precisa de acesso ativado (login do Hub), obtido por magic link. O sistema não inventa fluxo de convite próprio; expõe apenas o atalho "ativar acesso", que aciona a função base do Hub. Com acesso ativado, ele ainda tem um portal logado opcional de LEITURA que lista os relatórios dos seus Accounts (RF-REN-011); continua sendo só leitura, coerente com "nunca opera".

B.3 Fluxos macro

Três fluxos macro atravessam vários requisitos e são descritos aqui antes dos RFs:

  1. Composição: operador abre o wizard, escolhe cliente e período, preenche o briefing, sobe os artefatos, a IA lê e mostra o que entendeu e o que não achou, propõe a composição bloco a bloco com a origem de cada número, e o operador abre o rascunho no compositor.
  2. Publicação: operador cura o rascunho, aciona o modal de publicação, define URL, senha e expiração opcionais, revisa o card de compartilhamento, e publica; o sistema congela o snapshot e emite o link secreto.
  3. Leitura e comentário: cliente abre o link, lê o snapshot imutável, e (se logado) comenta ancorado em bloco; a resolução do comentário notifica o autor.

3. Requisitos Funcionais

C.1 Visão dos módulos

MóduloSlugResponsabilidade
Fronteira e escoporeports-boundary (BND)Define a fronteira com results e o escopo negativo imutável
Administraçãoreports-admin (ADM)Entitlement, RBAC, agências, identidade, templates, cliente, perfil do cliente, agendas de geração, offboarding, chrome
Ingestãoreports-ingest (ING)Upload, quota, parse isolado, TTL, assets, scrap de mercado, importação de metas e de parâmetros de negócio
Composiçãoreports-compose (CMP)Wizard, pipeline IA in-process, parse isolado, proveniência, custo, qualidade
Curadoriareports-curate (CUR)Compositor, editar, reordenar, esconder, regenerar, criativos, apresentar, identidade do relatório e título
Publicaçãoreports-publish (PUB)Snapshot, link secreto, senha, expiração, QR, card, revogar, republicar, log
Renderizaçãoreports-render (REN)Contrato de bloco, package renderizador, leitor público, PDF, estados
Comentáriosreports-comment (COM)Comentário logado, ancoragem, órfão, resolução, magic link
Analyticsreports-analytics (ANL)Eventos, views, referrer, exclusões, painel por agência

C.2 Convenções do RF

Cada RF segue o formato: nome, Descrição, Prioridade (Essencial, Alta, Média), Atores, H1 (critério binário Dado, Quando, Então, E) e Notas. O H1 é o teste de aceitação: o requisito só está pronto quando o H1 passa.

C.3 Módulo reports-boundary

RF-BND-001: Fronteira entre reports e results

  • Descrição: o ModularReports produz composição curada e snapshot publicado pontual; o módulo results do Hub produz KPI contínuo, input semanal e monitoramento de metas ao longo do tempo. Um relatório do Reports é uma foto de um período fechado, não uma série temporal viva.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se qualquer tela do Reports escrever tracking contínuo de KPI. O Reports lê metas apenas como referência de planejado versus realizado (RF-ING-007). A fronteira é concreta: o módulo results da plataforma-mãe já existe e é o dono de KPI contínuo, input semanal e metas (domínios results.input, results.kpi, results.metas); o Reports é um module_key novo e distinto e nunca escreve nesses domínios.

RF-BND-002: Artefato existe só como insumo

  • Descrição: os artefatos que o operador sobe existem apenas como insumo do ato de compor; não há gestão, CRUD, biblioteca persistente nem histórico de dados dos artefatos no sistema.
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se aparecer qualquer tela de "meus arquivos" ou acervo de dados brutos. O que persiste do dado é o snapshot do relatório, não o artefato.

RF-BND-003: Metas importadas são referência, não tracking

  • Descrição: metas e planejamento importados existem só como referência de planejado versus realizado dentro de um relatório; não são objeto de monitoramento contínuo.
  • Prioridade: Alta
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se uma meta importada gerar qualquer comportamento recorrente (alerta, cron, recomputo). Referência estática, não gatilho.

C.4 Módulo reports-admin

RF-ADM-001: Entitlement do módulo por agência, desligado por default

  • Descrição: reports é um module_key NOVO da plataforma-mãe (não existe hoje e não se confunde com o módulo results, que é o dono de KPI contínuo e metas). Cunhar o módulo tem três passos: a migration adiciona o literal reports à restrição CHECK da tabela de entitlements de módulo da plataforma-mãe; o catálogo de módulos em código (a lista canônica de módulos) ganha a entrada reports (rótulo "Relatórios", não core); e cada agência recebe uma linha de entitlement booleana por par (agência, módulo), desligada por default, ligada pelo Super Admin no painel existente do Hub. O entitlement é um booleano por (Company, módulo), não um jsonb de configuração.
  • Prioridade: Essencial
  • Atores: Super Admin
  • 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 booleano no painel do Super Admin passa a exibir a entrada do Reports sem novo deploy.
  • Notas: viola-se este requisito se o Reports aparecer para uma Company sem o entitlement ligado, ou se reports for tratado como configuração dentro de outro módulo em vez de module_key próprio. Não reusa o módulo results.

RF-ADM-002: RBAC pelos papéis canônicos do Hub

  • Descrição: o ModularReports não inventa papel novo; usa os papéis canônicos do Hub (super_admin, admin, team_manager, workforce, contact). As permissões do Reports são slugs em código (o catálogo de permissões do Hub é declarado em código, não numa tabela): cada capability reports.* entra na lista de defaults por papel e no catálogo de metadados de capability (rótulo, descrição, módulo). O override por usuário reusa a infraestrutura existente de concessão de capability do Hub, sem migration de tabela.
  • Prioridade: Essencial
  • Atores: Super Admin, Admin
  • 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 em código, E não existe nenhum papel novo criado exclusivamente para o Reports.
  • Notas: viola-se este requisito se houver um papel fora do conjunto canônico do Hub, uma permissão do Reports sem prefixo reports., ou uma tabela nova de permissões (os slugs vivem no catálogo em código do Hub, não numa tabela).

RF-ADM-003: Self-service de agência

  • Descrição: o admin de uma agência gerencia os usuários da própria agência; o Super Admin só cria a agência e o primeiro admin.
  • Prioridade: Alta
  • Atores: Admin, Super Admin
  • 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.
  • Notas: viola-se este requisito se um admin listar ou alterar usuário de outra Company.

RF-ADM-004: Identidade da agência no chrome e no link

  • Descrição: a identidade da agência (logo, cor primária, nome) vem da ficha da Company e brandiza o chrome do operador e o link público.
  • Prioridade: Alta
  • Atores: Admin
  • 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.
  • Notas: viola-se este requisito se a página pública de um cliente da agência A exibir marca de outra agência ou marca genérica quando A tem identidade definida. A página pública tem dois portadores de marca com donos distintos, e o da agência é a barra superior: quem define isso é RN-020 item 4, escrita para que a sobreposição de identidade por relatório (RF-CUR-004) nunca seja construída de um jeito que apague a marca da agência da página.

RF-ADM-005: Identidade visual do cliente e tom de voz

  • Descrição: a identidade visual do cliente (logo, cores, fontes, tom de voz) vem da ficha já existente account_identidade_visual do Hub; o Reports lê e popula essa ficha, não cria tabela própria. O tom de voz alimenta a narrativa da IA.
  • Prioridade: Alta
  • Atores: Admin, Operador
  • 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.
  • Notas: viola-se este requisito se o Reports criar uma segunda tabela de identidade do cliente, ou ignorar o tom de voz da ficha na composição. A ficha já existe na plataforma-mãe com um campo de dados livre (jsonb) que comporta logos, cores, fontes, tom de voz e restrições, então o Reports lê e popula sem nenhuma migration de ALTER; a forma é validada na camada da rota (Ap.6).

RF-ADM-006: Ficha de identidade vazia bloqueia ou usa default declarado

  • Descrição: se a ficha de identidade do cliente estiver vazia, o sistema bloqueia a publicação ou usa um default declarado da agência com aviso explícito; nunca infere tom ou cor em silêncio.
  • Prioridade: Alta
  • Atores: Operador, Admin
  • 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.
  • Notas: o gate de publicação passou a exigir tom de voz (decisão de rollout de 2026-08-02). Como a tabela de configuração de identidade da agência é nova, nenhuma agência tinha tom declarado no dia da entrega, e o gate travaria todas de uma vez. A saída foi semear um tom NEUTRO por agência, editável na tela, e não afrouxar o requisito: apagar o default volta a travar a publicação, que é a prova de que a exigência segue viva e não virou letra morta. Semente não é exceção: o valor semeado é um default declarado, exatamente a segunda alternativa que este H1 já previa, e o relatório registra que a origem do tom foi o padrão da AGÊNCIA e não a ficha do CLIENTE. Essa distinção de procedência é a razão de o estado de identidade existir; sem ela, "tem tom" e "herdou tom" viram a mesma coisa e o aviso perde sentido.
  • Notas: viola-se este requisito se um relatório for publicado com identidade inferida sem que o operador tenha visto o aviso.

RF-ADM-007: Criar cliente novo de dentro do Reports

  • Descrição: o operador cria um cliente (Account do Hub) sem sair do chrome do Reports.
  • Prioridade: Alta
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se criar cliente exigir sair do Reports.

RF-ADM-008: Atalho para ativar acesso do cliente

  • Descrição: na ficha do cliente, o Reports expõe um atalho "ativar acesso" que aciona a função base do Hub para provisionar o login do Contact; o Reports não implementa convite próprio.
  • Prioridade: Média
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se o Reports gerar credencial ou convite fora da função base do Hub.

RF-ADM-009: Template como tipo de relatório

  • Descrição: um template é um tipo de relatório por PROPÓSITO, com os blocos padrão para aquele propósito; é o gabarito que a IA preenche. O propósito é um valor fechado de três, campanha, fechamento e pesquisa (RN-014); a cadência com que o relatório se repete (quinzenal, mensal, trimestral, semestral, anual) NÃO é tipo de template, é atributo da instância do relatório, gravado em report.cadence e escolhido no passo 1 do wizard (RF-CMP-001). CRUD completo pelo painel. Todo template nasce individual, pertencente à agência de quem o criou, e pode depois ser marcado como Global pelo Super Admin (RN-013, RF-ADM-016). O template é consumido no nascimento do relatório: os blocos padrão são copiados para o rascunho naquele instante, e o relatório não fica atrelado ao template depois disso.
  • Prioridade: Alta
  • Atores: Admin, Operador
  • 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 o template criado tem escopo individual da agência do criador, E editar, duplicar ou excluir o template é possível pelo painel sem tocar em código, E editar ou excluir o template depois não altera os blocos de nenhum relatório já criado (rascunho ou publicado).
  • Notas: viola-se este requisito se a lista de blocos padrão de um template não puder ser alterada pelo painel, se um template recém-criado nascer com escopo Global, ou se editar ou excluir um template mudar os blocos de um relatório que já nasceu dele.

RF-ADM-010: Salvar relatório como template

  • Descrição: o operador salva um relatório existente como um template novo, nomeado, para reuso. Salvar como template também guarda o perfil de direção (campos de RF-CMP-002 sem dados do cliente: objetivo, decisao_alvo, tom, blocos_preferidos, pesos) para o próximo relatório reusar, alimentando o anti-fadiga (RF-CMP-016). O template gerado nasce individual da agência do operador, mesmo quando o relatório de origem foi montado a partir de um template Global; marcar como Global é gesto exclusivo do Super Admin (RN-013).
  • Prioridade: Média
  • Atores: Operador, Admin
  • 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 com escopo individual, E o conteúdo específico do cliente (números, textos) não é copiado para o template, E o perfil de direção (sem números nem textos do cliente) é salvo no template e pré-carregado no próximo relatório que o usar.
  • Notas: viola-se este requisito se o template salvo carregar dados do cliente original, se o template salvar qualquer valor de dp:, número ou texto específico do cliente no perfil de direção, ou se o template gerado nascer com escopo Global.

RF-ADM-011: Offboarding e LGPD

  • Descrição: encerrar um cliente revoga todos os links, comentários e analytics dele de uma vez; a retenção é configurável pelo admin. O processo de eliminação é compatível com o log append-only (RF-ADM-012, RN-007): o log nunca é mutado; a pseudonimização acontece nas tabelas de ORIGEM, apagando ou anonimizando o dado pessoal onde ele vive de verdade (o corpo dos comentários do titular, o watermark do snapshot, o Contact do Hub). Como o log de auditoria só guarda evento e identificadores de ator (nunca email ou nome cru), apagar a ponta identificável na origem faz as referências por identificador no log deixarem de resolver para a pessoa (pseudonimização por remoção da ponta). Arquivo também é origem: o criativo, o logo do cliente, o PDF gerado e o artefato cru ainda dentro do prazo vivem no armazenamento de arquivos e não numa tabela, e a eliminação os alcança pelas regras de RF-ADM-024 (posse registrada, ensaio, relato, falha fechada e lápide). O retrato já publicado do cliente eliminado não é editado: ele é eliminado inteiro, e o link passa a responder com a lápide (decisão registrada em RF-ADM-024).
  • Prioridade: Alta
  • Atores: Admin
  • 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, anonimiza os comentários e apaga a ponta identificável na origem daquele cliente conforme a retenção configurada, sem mutar o log de auditoria.
  • Notas: viola-se este requisito se, após o encerramento, algum link do cliente continuar servindo conteúdo, algum comentário mantiver o dado pessoal identificável, ou o processo tentar reescrever o log append-only em vez de agir na origem. A parte dos arquivos tem requisito próprio, RF-ADM-024, com H1 próprio: este H1 continua valendo para as tabelas, e a eliminação só está completa quando os dois passam.

RF-ADM-012: Log de auditoria por ponto único

  • Descrição: toda ação relevante do Reports grava um evento no log de auditoria existente do Hub com event_type prefixado reports., por um ponto único de escrita (o helper de auditoria do Hub). O log guarda o diff (valor anterior e valor novo) quando a ação altera estado, e é append-only por RLS (as policies só permitem INSERT e SELECT; não há UPDATE nem DELETE por política, embora o service_role ainda alcance o storage: é append-only por RLS, não imutável a nível físico). O log de republicação (RF-PUB-010) DERIVA desses eventos com valor anterior e novo, sem tabela nova.
  • Prioridade: Alta
  • Atores: todos
  • 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 de auditoria do Hub com valor anterior e valor novo quando aplicável, E não há caminho de escrita de auditoria fora do ponto único.
  • Notas: viola-se este requisito se existir uma ação de publicação sem evento de auditoria correspondente, ou se um evento de alteração não registrar o antes e o depois. O log não contém dado pessoal cru, só evento e identificadores (RN-007).

RF-ADM-013: Pasta de relatórios do cliente

  • Descrição: a ficha do cliente mostra os relatórios dele organizados como uma pasta consultável por ano (dados básicos, logo e os relatórios como arquivos), sem KPIs de mídia.
  • Prioridade: Média
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se a ficha do cliente exibir dashboard de métricas contínuas (isso é results, RF-BND-001).

RF-ADM-014: Chrome próprio isolado com entrada pelo Hub

  • Descrição: o ModularReports tem chrome e menu próprios, não usa o menu lateral do Hub. A entrada é um item no menu de navegação do Hub, atalhos onde fizer sentido (painel, ficha do cliente) e um endereço direto próprio; a sessão é a mesma do Hub.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se o Reports aparecer como mais um item dentro do menu lateral do Hub, ou exigir novo login.

RF-ADM-015: Duração medida por relatório

  • Descrição: o sistema mede nativamente a duração de produção de cada relatório (tempo real gasto), dado que essa medida nunca existiu no fluxo manual.
  • Prioridade: Média
  • Atores: sistema
  • 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.
  • Notas: alimenta o critério de adoção (A.7). Viola-se este requisito se a duração não for gravada.

RF-ADM-016: Promoção e despromoção de template pelo Super Admin

  • Descrição: marcar um template como Global e desmarcá-lo é gesto exclusivo do Super Admin (RN-013), verificado como gate de papel e não como permissão concedível: não existe slug de permissão de promoção no catálogo do módulo, e o sistema confere o papel Super Admin diretamente. A razão fica registrada aqui para não ser lida como esquecimento e trocada por permissão mais adiante: a concessão individual de permissão do Hub alcança qualquer permissão e qualquer usuário, então uma promoção modelada como permissão seria delegável a quem não é Super Admin, e exclusividade delegável não é exclusividade. A escrita da tabela é a segunda camada, porque só aceita a linha marcada como Global quando quem grava é o Super Admin. A promoção marca o próprio registro, nunca cria cópia: o identificador do template não muda, a agência de origem continua registrada e os relatórios que já apontam para ele seguem apontando. Enquanto Global, o template só é editado, despromovido ou excluído pelo Super Admin, inclusive contra a agência que o criou. Despromover devolve o template ao escopo individual da agência de origem. Um Global sem agência de origem (semeado pela operação da plataforma) não tem despromoção: a forma de retirá-lo é a exclusão. Promoção e despromoção gravam evento no log de auditoria (RF-ADM-012), que é onde ficam quem fez e quando.
  • Prioridade: Alta
  • Atores: Super Admin
  • H1: Dado um template individual da agência A, Quando o Super Admin o marca como Global, Então o registro segue sendo o mesmo (mesmo identificador, agência de origem preservada) e passa a aparecer na biblioteca de todas as agências, E o admin e o operador de A deixam de conseguir editar, despromover ou excluir esse template, E despromover devolve o template ao escopo individual da agência A, E a tentativa de promover ou despromover feita por qualquer papel fora do Super Admin é recusada, inclusive quando esse usuário recebeu concessão individual de qualquer permissão do módulo.
  • Notas: viola-se este requisito se um papel fora do Super Admin promover ou despromover, se a promoção criar um segundo registro em vez de marcar o existente, se a agência de origem se perder na promoção (o que inviabiliza a despromoção), se uma promoção ou despromoção não gerar evento de auditoria, ou se promover e despromover deixarem de ser gate de papel e passarem a depender de uma permissão concedível, porque assim o gesto vira delegável e a exclusividade cai.

RF-ADM-017: Biblioteca de templates por escopo e duplicação de um Global

  • Descrição: a biblioteca que o operador e o admin de uma agência enxergam é a união dos templates Globais com os individuais da própria agência, e nada mais; individual de outra agência nunca aparece. O Super Admin, e só ele, enxerga também os individuais de todas as agências, porque é dele o gesto de promover (RF-ADM-016). Para variar um Global sem depender do Super Admin, qualquer usuário com a capability de gerir template duplica o Global para dentro da própria agência: a cópia nasce individual, editável, e sem vínculo com o original.
  • Prioridade: Alta
  • Atores: Operador, Admin, Super Admin
  • H1: Dado um template Global, um template individual da agência A e um template individual da agência B, Quando um operador de A abre a biblioteca, Então ele vê o Global e o individual de A e não vê o individual de B, E ao duplicar o Global a cópia nasce individual de A e editável por ele, E alterar os blocos padrão do Global depois não altera a cópia, E alterar a cópia não altera o Global.
  • Notas: viola-se este requisito se um template individual de uma agência aparecer na biblioteca de outra, se um template Global deixar de aparecer para alguma agência com o módulo ligado, se a duplicação de um Global gerar cópia ainda atrelada ao original (edição de um propagando para o outro), ou se duplicar um Global exigir papel de Super Admin.

RF-ADM-018: Sazonalidade é atributo do cliente, definido no onboarding

  • Descrição: se o negócio de um cliente é sazonal, a comparação primária dos relatórios dele é contra o MESMO PERÍODO DO ANO ANTERIOR, e não contra o período imediatamente anterior. Isso é atributo do cliente, definido no cadastro dele e armazenado, não escolha por relatório: escolher a base de comparação relatório a relatório é exatamente como o viés entra na entrega, porque quem monta escolhe (mesmo sem má fé) a base que conta a história melhor. E viés não aparece dentro de um relatório, aparece na comparação entre relatórios: cada peça isolada parece defensável, e só o conjunto revela que a régua mudou. O campo é booleano com o mês de pico opcional, editável pelo admin da agência, e a mudança dele NÃO altera relatórios já publicados (snapshot imutável, RF-PUB-001).
  • Prioridade: Alta
  • Atores: Admin, sistema
  • H1: Dado um cliente marcado como sazonal, Quando um relatório dele é composto, Então a base de comparação primária escolhida pelo sistema é o mesmo período do ano anterior e não o período imediatamente anterior, E a base aparece por extenso dentro da frase de cada número comparativo (RF-CMP-024), E o mesmo relatório para um cliente não sazonal usa o período imediatamente anterior, E trocar o atributo depois não altera nenhum relatório já publicado.
  • Notas: viola-se este requisito quando a base de comparação de um cliente sazonal é escolhida no relatório em vez de vir do cadastro, quando um relatório de cliente sazonal compara contra o período imediatamente anterior sem que ninguém tenha decidido isso no cadastro, ou quando a base fica implícita e o leitor precisa deduzir contra o que o número foi comparado.

RF-ADM-019: Agenda de geração por cliente e por tipo

  • Descrição: o operador cria uma agenda que faz o sistema gerar sozinho o relatório de um cliente a cada período. É a peça que torna a cadência um agendamento de verdade e não um rótulo. A agenda existe para o par (cliente, propósito) e carrega: a cadência (valor fechado de seis, RN-014), a âncora de calendário (o dia do ciclo em que a edição nasce), o modelo usado para a edição nascer, o operador responsável por revisar e o estado (ativa ou pausada). Um par (cliente, propósito) tem no máximo UMA agenda: duas agendas para o mesmo par produziriam duas edições concorrentes do mesmo relatório no mesmo ciclo, e a segunda entraria no acervo competindo com a primeira pela base de comparação do selo e pelo pool do anti-fadiga. A agenda não substitui a criação manual: o operador continua abrindo o assistente e compondo um relatório fora de qualquer agenda, e essa edição avulsa entra no acervo normalmente.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • H1: Dado um cliente sem agenda para o propósito fechamento, Quando o operador cria uma agenda mensal para esse par, Então a agenda é gravada com cadência, âncora de calendário, modelo, responsável e estado ativa, E uma segunda tentativa de criar agenda para o MESMO par é recusada com o motivo, E uma agenda de outro propósito do mesmo cliente é aceita, E o operador continua conseguindo compor um relatório avulso desse cliente fora da agenda.
  • Notas: viola-se este requisito quando um par (cliente, propósito) aceita duas agendas, quando a agenda é gravada sem cadência declarada, quando a existência de uma agenda impede a composição avulsa, ou quando a agenda de um cliente de uma agência é visível ou editável por outra agência (RN-009).

RF-ADM-020: A agenda é editável sem recriar, e a identidade dela sobrevive à edição

  • Descrição: mudar a cadência, a âncora de calendário, o modelo ou o responsável de uma agenda é uma EDIÇÃO da mesma agenda, nunca a criação de uma nova. A distinção não é cosmética: a agenda é a chave que liga as edições geradas umas às outras, e recriar a agenda a cada mudança partiria o acervo do cliente em dois exatamente como a fragmentação de type por cadência partiria (RN-014 item 4), esvaziando a base de comparação do selo e o pool do anti-fadiga justamente do cliente mais maduro. A edição vale do próximo ciclo em diante e nunca reescreve edição já gerada nem já publicada (snapshot imutável, RF-PUB-001). Toda edição da agenda entra no log de alterações em linguagem humana (RF-ADM-012, RN-007) com o valor anterior e o novo.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • H1: Dada uma agenda mensal com três edições já geradas, Quando o operador a muda para trimestral, Então a agenda continua sendo a mesma (mesmo identificador) e as três edições anteriores seguem ligadas a ela, E a mudança vale do próximo ciclo em diante, E nenhuma edição já publicada é alterada, E o log registra a cadência anterior e a nova com quem mudou e quando.
  • Notas: viola-se este requisito quando editar a agenda gera um segundo registro, quando a edição altera retroativamente uma edição já gerada, quando as edições anteriores deixam de ser alcançáveis a partir da agenda, ou quando a mudança não aparece no log de alterações.

RF-ADM-021: Pausar interrompe a geração e não apaga nada

  • Descrição: pausar uma agenda tem exatamente um efeito: nenhuma edição nova nasce dela enquanto ela estiver pausada. O que a pausa NÃO faz está listado nominalmente, porque é aqui que a implementação erra: não apaga a agenda, não apaga nem altera a configuração dela (cadência, âncora, modelo, responsável), não apaga nem oculta as edições já geradas, não revoga nenhum link já publicado, e não apaga o histórico de alterações. Pausar é um estado da agenda, não uma exclusão com outro nome. Um rascunho que já tinha nascido antes da pausa continua existindo, editável e publicável pelo operador: a pausa age sobre o nascimento das edições futuras, não sobre o trabalho em curso.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • H1: Dada uma agenda mensal ativa com quatro edições geradas, uma delas em rascunho, Quando o operador a pausa e o ciclo seguinte vence, Então nenhuma edição nova nasce, E a agenda continua existindo com a mesma cadência, âncora, modelo e responsável, E as quatro edições continuam alcançáveis, E os links já publicados continuam respondendo, E o rascunho em curso continua editável e publicável.
  • Notas: viola-se este requisito quando pausar remove a agenda, quando pausar zera ou esquece qualquer campo da configuração, quando pausar oculta ou apaga edição já gerada, quando pausar revoga link publicado, ou quando pausar bloqueia a publicação de um rascunho que já existia.

RF-ADM-022: Retomar volta do ciclo à frente, nunca em lote retroativo

  • Descrição: retomar uma agenda pausada devolve o estado ativa, e a próxima edição nasce no próximo ciclo que vencer a partir daquele instante. O sistema NÃO gera, no ato de retomar, as edições dos ciclos que passaram durante a pausa. Isso não é simplificação: um lote de edições retroativas nascendo de uma vez produziria vários rascunhos do mesmo cliente disputando a mesma base de comparação e o mesmo pool de anti-fadiga no mesmo instante, e o operador receberia uma pilha de relatórios de períodos vencidos que ninguém pediu, exatamente quando ele acabou de sinalizar que quer voltar a operar. Os ciclos vencidos durante a pausa ficam registrados na agenda como ciclos sem edição, com as janelas deles, visíveis ao operador, e ele compõe à mão o que quiser recuperar, uma edição de cada vez.
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • H1: Dada uma agenda mensal pausada por três ciclos, Quando o operador a retoma, Então nenhuma edição nasce no ato de retomar, E a próxima edição nasce só quando o próximo ciclo vencer, E a agenda mostra os três ciclos vencidos durante a pausa com as janelas deles, E cada um deles só vira relatório se o operador o compuser à mão.
  • Notas: viola-se este requisito quando retomar dispara qualquer geração imediata, quando retomar gera as edições atrasadas em lote, quando os ciclos vencidos durante a pausa desaparecem sem registro, ou quando o sistema compõe sozinho uma edição de período vencido sem gesto do operador.

RF-ADM-023: O modelo de venda é atributo do cliente e troca o vocabulário do funil

  • Descrição: cada cliente tem um modelo de venda declarado no cadastro dele, valor fechado de dois: consultiva (Venda Consultiva) e direta (Venda Direta). O modelo de venda NÃO é tipo de relatório, não é cadência e não é modelo separado: é atributo do cliente, do mesmo modo que a sazonalidade é (RF-ADM-018). O que ele muda é uma coisa e apenas uma: o vocabulário das quatro etapas do funil canônico (RF-REN-016). A estrutura do funil, a ordem das etapas, a tripla que cada etapa carrega e o bloco de resultado são os mesmos nos dois modelos. Modelar isto como dois tipos de relatório separados é o erro que este requisito impede: partiria o acervo do cliente que mudasse de modelo, esvaziaria a base de comparação do selo e o pool do anti-fadiga (RN-014 item 4), e criaria duas estruturas para manter onde existe uma. O campo é editável pelo admin da agência e mudá-lo NÃO altera relatório já publicado (RF-PUB-001). Cliente sem modelo de venda declarado é estado válido: os relatórios dele usam o funil livre de funnel_simple como sempre usaram, e nada quebra.
  • Prioridade: Essencial
  • Atores: Admin, Operador, sistema
  • H1: Dado um cliente com modelo de venda consultiva, Quando o funil canônico dele renderiza, Então as quatro etapas aparecem com o vocabulário consultivo do Ap.10, E o mesmo funil para um cliente direta aparece com o vocabulário direto do Ap.10 nas MESMAS quatro etapas e na mesma ordem, E o conjunto de métricas do bloco de resultado é idêntico nos dois, E trocar o modelo de venda de um cliente não altera nenhum relatório já publicado, E um cliente sem modelo declarado continua renderizando o funil livre sem erro.
  • Notas: viola-se este requisito quando o modelo de venda vira tipo de relatório, cadência ou modelo no catálogo; quando ele muda a quantidade, a ordem ou o significado das etapas; quando ele muda o conjunto de métricas do bloco de resultado; quando um valor fora dos dois é gravado; ou quando um cliente sem modelo declarado deixa de renderizar.

RF-ADM-024: A eliminação de dados pessoais alcança os arquivos e relata o que apagou

  • Descrição: a eliminação de RF-ADM-011 age nas tabelas de origem, e arquivo também é origem. O criativo e o logo que o operador enviou, o PDF gerado do relatório e o artefato cru ainda dentro do prazo (RF-ING-004) vivem no armazenamento de arquivos, referenciados por identificador e hash dentro dos blocos (RF-ING-005, RN-006), e não numa tabela que a eliminação percorra; por isso ela relatava sucesso e os arquivos ficavam. Este requisito estende a eliminação a eles, em cinco regras. Posse registrada: todo arquivo gravado nasce com o dono registrado (agência e cliente) no mesmo ato da gravação, e é esse registro, nunca o caminho no armazenamento nem um palpite pelo nome ou pelo conteúdo do bloco, que decide o que pertence ao cliente eliminado; se a posse não puder ser gravada, a gravação do arquivo falha. Ensaio antes da execução: a eliminação tem duas modalidades, o ensaio (dry-run), que varre e lista o que apagaria com hash e tamanho sem apagar nada, e a execução, que apaga exatamente o conjunto que a varredura daquela execução provou pertencer ao cliente. Preservação do que não é dele: arquivo usado também por outro cliente ou pela própria agência (logo da agência, criativo compartilhado) não é apagado e aparece no relato como preservado, com o motivo. Falha fechada: quando a varredura não pode rodar (armazenamento indisponível ou ilegível), a eliminação responde falha e não apaga nada; o relato sempre distingue "varri e não havia nada" de "não varri". Lápide no lugar do vazio: no lugar de cada arquivo apagado fica o registro de que aquele identificador existiu e foi eliminado, com data e motivo e sem nenhum byte do conteúdo, e quem pede o arquivo depois recebe "eliminado a pedido de eliminação de dados pessoais", nunca "não encontrado", que diria outra coisa (arquivo perdido, link quebrado, defeito).
  • Prioridade: Alta
  • Atores: Admin, sistema
  • Pré-condições: o cliente está encerrado e a retenção configurada já venceu, as mesmas condições de RF-ADM-011, conferidas pela própria rotina que elimina e não só pela tela.
  • Entrada: o cliente a eliminar e a modalidade (ensaio ou execução).
  • Saída: o relato da eliminação, com três partes separadas: os apagados (identificador, hash, tamanho e classe do arquivo), os preservados (identificador e motivo) e o estado da varredura (feita, com a contagem, ou não feita, com a razão).
  • H1: Dado um cliente encerrado com retenção vencida, dono de um criativo enviado, de um logo do cliente e de um PDF gerado, e dado como sentinelas um criativo compartilhado com outro cliente da mesma agência e um arquivo de outra agência, Quando o admin roda o ensaio, Então o relato lista os três arquivos do cliente com hash e tamanho, E todo arquivo do armazenamento continua presente com o mesmo hash depois do ensaio, E quando o admin roda a execução saem do armazenamento exatamente os arquivos que o ensaio listou, nem um a mais nem um a menos, E as duas sentinelas continuam presentes com o mesmo hash e aparecem no relato como preservadas, E um único evento de auditoria com prefixo reports registra a execução com a lista apagada (identificador, hash, tamanho, motivo) e nenhum dado pessoal cru, E uma segunda execução relata "nada havia" com a varredura feita, E com o armazenamento indisponível a resposta é falha, nenhum byte é apagado e nada é relatado como sucesso, E o pedido posterior de qualquer arquivo apagado, pelo leitor público, pelo preview, pelo PDF ou pela rota interna, recebe a resposta de eliminado (410, com a lápide) e nunca "não encontrado", E o link do relatório já publicado daquele cliente responde com a página brandada de conteúdo eliminado, sem nenhum bloco nem arquivo do retrato.
  • Notas: viola-se este requisito quando a eliminação responde sucesso sem ter varrido o armazenamento; quando o ensaio apaga ou altera qualquer byte; quando a execução apaga arquivo fora do conjunto que a varredura provou (de outro cliente, de outra agência, compartilhado) ou deixa presente arquivo que o conjunto apontou; quando a posse é deduzida do caminho, do nome ou do conteúdo do bloco em vez de lida do registro de posse; quando um arquivo é gravado sem posse e a gravação conclui assim mesmo; quando o relato não separa "nada havia" de "não olhei"; quando o evento de auditoria carrega nome, email ou conteúdo de arquivo (RN-007); ou quando arquivo eliminado responde "não encontrado". A ordem importa e é parte do requisito: a varredura de arquivos acontece ANTES de a eliminação das tabelas trocar o retrato pela lápide, porque depois disso não há mais como saber que arquivo o retrato citava, e varrer depois seria varrer nada e relatar sucesso. O custo da falha fechada está declarado e é desejado: com o armazenamento fora do ar, a eliminação passa a recusar em vez de concluir; sucesso sem ter olhado é exatamente o defeito que este requisito fecha. Arquivo anterior ao registro de posse recebe dono por reconciliação única; o que não puder ser atribuído com prova fica fora da execução automática e aparece no relato como "sem dono provado", nunca apagado por palpite. Decisão do retrato já publicado (o cliente final que recebeu o link): a resposta é uma só, e por isso não há cenário a escolher. O RF-ADM-011 já manda apagar os snapshots do cliente eliminado e revogar os links dele; a imutabilidade de RF-PUB-001 protege o retrato contra edição do rascunho, nunca contra a eliminação; e a lei de proteção de dados só admite guardar o dado depois do fim do tratamento em hipóteses (obrigação legal, estudo, uso exclusivo do controlador anonimizado) que uma página de resultados de mídia não preenche. Portanto o retrato não é editado nem mantido com buracos: ele é eliminado inteiro, os arquivos que ele citava saem do armazenamento, e o link passa a mostrar a página brandada de conteúdo eliminado (410), distinta da página de link revogado (RF-PUB-008) para que o cliente final entenda que houve eliminação e não falha. O limite também é declarado: cópia que já saiu do sistema (o PDF que o cliente final baixou, a página salva no aparelho dele) não é alcançável por sistema nenhum, e este requisito não promete alcançá-la. Cruza com RF-ADM-011, RF-ADM-012, RF-ING-004, RF-ING-005, RF-PUB-001, RF-PUB-008, RN-006, RN-007 e RNF-LGPD-002. Ver o Roadmap, card {F8-20}.

C.5 Módulo reports-ingest

RF-ING-001: Upload de artefatos heterogêneos

  • Descrição: o operador sobe artefatos de tipos variados (planilha, CSV, PDF, Markdown, JSON, imagem) para uma composição. O suporte a imagem como fonte de dados é condicionado ao protótipo (RF-CMP-005).
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se um tipo suportado for recusado, ou um tipo não suportado for aceito.

RF-ING-002: Quota de entrada validada antes de processar

  • Descrição: uma quota de entrada (número de artefatos, tamanho total em MB, número de páginas de PDF) é validada antes de tokenizar qualquer conteúdo; ultrapassar a quota falha alto no passo de dados. Os limites são editáveis pelo admin.
  • Prioridade: Alta
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se um conjunto acima da quota chegar a ser tokenizado. Os limites default vivem no bloco 11 e são admin-editáveis.

RF-ING-003: Parse isolado sem rede, fail-closed por arquivo

  • Descrição: o parse de arquivos acontece num ambiente sem rede de saída, com defesas de arquivo malicioso: verificação de magic bytes, XXE desligado, limite de razão de descompressão (zip bomb), SVG sanitizado e arquivos de macro (xlsm) rejeitados. A falha é POR ARQUIVO: um arquivo que falha ou é rejeitado fica de fora com o motivo visível ao operador (qual arquivo, por quê) e vira lacuna declarada na composição (RF-CMP-003); nenhum byte do arquivo falho entra (fail-closed por arquivo), e os demais arquivos da mesma composição seguem normalmente.
  • Prioridade: Essencial
  • Atores: sistema
  • 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, E quando um arquivo de um lote falha, ele é excluído com o motivo apontado ao operador e vira lacuna, sem derrubar o parse dos demais arquivos do lote.
  • Notas: viola-se este requisito se o container de parse tiver rede de saída, se um dos vetores listados passar, se qualquer conteúdo de um arquivo falho entrar na composição, ou se a falha de um arquivo derrubar o processamento dos demais. Segurança HARD da Fatia 1.

RF-ING-004: TTL curto do artefato cru

  • Descrição: o artefato cru tem tempo de vida curto após a publicação (minimização LGPD), pois contém dados pessoais de leads.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um artefato cru persistir além do TTL, ou se apagá-lo quebrar o relatório publicado.

RF-ING-005: Asset referenciado por identificador e hash

  • Descrição: criativos e imagens usados no relatório são referenciados por asset_id mais sha256, nunca por URL crua ou base64 embutido no payload.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o payload guardar URL ou base64 de imagem. Revogar o link não apaga o asset (asset some só no processo LGPD).

RF-ING-006: Pesquisa de mercado em etapa isolada e opcional

  • Descrição: opcionalmente por relatório, uma etapa isolada de pesquisa de mercado (por exemplo, o que se fala no mercado sobre o resultado de uma campanha) roda sem os artefatos do cliente no mesmo contexto, consultando só fontes de uma allowlist; os achados voltam com fonte e URL nomeadas e viram dado citável, nunca instrução para a IA.
  • Prioridade: Média
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se conteúdo da web influenciar a narrativa sem fonte nomeada, ou se a pesquisa rodar no mesmo contexto dos artefatos do cliente. Conteúdo web é tratado como não confiável e sanitizado como artefato. Pesquisa é opcional, não obrigatória.

RF-ING-007: Importação de metas e planejamento

  • Descrição: o operador importa metas, público-alvo e planejamento de campanha (via JSON, Markdown ou planilha) para um cliente, como referência de planejado versus realizado.
  • Prioridade: Média
  • Atores: Operador, Admin
  • 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 (RF-BND-003).
  • Notas: viola-se este requisito se a importação disparar tracking. O snapshot copia as metas vigentes no publish (RF-PUB-001).

RF-ING-008: Parâmetros de negócio do cliente, distintos de meta

  • Descrição: parte da camada de resultado (Ap.10) depende de números que NÃO vêm da plataforma de anúncios nem do artefato de mídia, porque são do negócio do cliente e não da campanha: o fee da agência, a margem de contribuição e o multiplicador de valor do cliente ao longo do tempo. Eles entram pelo mesmo caminho de importação das metas (RF-ING-007), pelo mesmo sandbox de parse isolado, e nunca por digitação avulsa dentro do relatório. O que este requisito acrescenta a RF-ING-007 é a separação de conceito, e ela é necessária porque confundir os dois estraga os dois: uma META é um alvo a atingir, e a leitura certa dela é planejado contra realizado; um PARÂMETRO DE NEGÓCIO é um fato vigente da operação do cliente, e ele não tem realizado para comparar, ele é operando de fórmula. Gravar um fee como meta faria o sistema oferecer uma comparação planejado contra realizado que não significa nada. Cada parâmetro grava a métrica, o valor, a unidade e a vigência (a partir de quando vale), e o snapshot congela os parâmetros vigentes no publish do mesmo modo que congela as metas (RN-008).
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • H1: Dado um arquivo de parâmetros de negócio importado para um cliente com fee, margem de contribuição e multiplicador de valor do cliente vigentes, Quando o operador compõe um relatório desse cliente, Então os três ficam disponíveis como pontos de dado com proveniência de importação (Ap.8), E nenhum dos três aparece na leitura de planejado contra realizado das metas, E o snapshot congela os valores vigentes na data do publish, E importar um parâmetro não cria monitoramento contínuo (RF-BND-003).
  • Notas: viola-se este requisito quando um parâmetro de negócio é gravado como meta ou uma meta como parâmetro, quando um parâmetro é digitado dentro do relatório em vez de importado, quando um parâmetro entra sem vigência, ou quando mudar um parâmetro altera um relatório já publicado.

C.6 Módulo reports-compose

RF-CMP-001: Wizard passo 1, cliente, período e cadência

  • Descrição: o primeiro passo do assistente coleta o cliente, o período (as duas datas da janela), a cadência e o tipo (template) do relatório. A cadência é um valor fechado de seis, quinzenal, mensal, trimestral, semestral, anual e pontual, gravado em report.cadence; ela é declarada pelo operador e nunca inferida da largura do período, porque é justamente a divergência entre a cadência declarada e a largura real da janela que revela um período parcial (RN-014, RF-CMP-021). A cadência não é o tipo do relatório: o tipo (propósito) vem do template escolhido (RF-ADM-009, RN-014).
  • Prioridade: Essencial
  • Atores: Operador
  • H1: Dado o wizard aberto, Quando o operador seleciona cliente, período, cadência e tipo e avança, Então o rascunho é criado vinculado a esse cliente, período e cadência com o template escolhido, E não é possível avançar sem cliente, período e cadência preenchidos, E a cadência gravada é exatamente um dos seis valores fechados.
  • Notas: viola-se este requisito se o wizard avançar sem cliente, sem período ou sem cadência, se gravar uma cadência fora dos seis valores, ou se o sistema deduzir a cadência da largura do período em vez de recebê-la declarada.

RF-CMP-002: Wizard passo 2, briefing canônico

  • Descrição: o segundo passo coleta o briefing canônico: KPI principal, a direção (objeto estruturado, Ap.1), destaques (campanhas ou criativos estrela) e identidade do relatório. A direção é do operador (staff confiável), entra no canal de instrução do pipeline, separada do dado do artefato (não confiável, RNF-SEG-002); ela move ângulo, ênfase, ordem e seleção de bloco do contrato v3, nunca cria nem edita um dp: (o número segue determinístico, Ap.8), e nunca introduz tipo de bloco fora do Ap.3.
  • Prioridade: Essencial
  • Atores: Operador
  • H1: Dado o passo de briefing, Quando o operador preenche KPI principal, a direção (com o objetivo da campanha no enum, a tese e a decisão alvo) e a identidade e avança, Então o objeto de direção fica gravado no rascunho e disponível ao pipeline como instrução tipada, E o KPI principal, o objetivo_campanha, o objetivo (tese) e a decisão alvo são obrigatórios para avançar, E nenhum campo da direção grava ou altera um dp:.
  • Notas: viola-se este requisito se o pipeline compor sem KPI principal, objetivo_campanha, tese ou decisão alvo preenchidos, se um campo da direção criar ou reescrever um dp:, ou se a direção introduzir um tipo de bloco fora do Ap.3. O objetivo_campanha é o valor fechado que deriva a métrica âncora do selo de honestidade (RN-012 ii).

RF-CMP-017: Prompt de composição exposto e editável antes de enviar

  • Descrição: antes de disparar a composição, o sistema monta e mostra ao operador o prompt de instrução que será enviado à IA (a direção compilada em texto), e o operador revisa, edita e adiciona detalhe; só após confirmar, a composição dispara. O catálogo de pontos de dado (canal de dado) aparece somente leitura como contexto, referenciável apenas por metric_id, nunca digitável nem colável. O prompt é montado a partir do objeto de direção (RF-CMP-002); o compilador de pré-prompt (RF-CMP-019) preenche esse objeto a partir de uma nota solta do operador.
  • Prioridade: Essencial
  • Atores: Operador, sistema
  • H1: Dado a direção pronta, Quando o operador chega ao prompt, Então o sistema mostra o prompt de instrução montado e editável e o catálogo de pontos de dado em modo somente leitura, E a composição só dispara após o operador confirmar, E nenhuma edição do prompt cria um dp: nem torna o catálogo de pontos de dado editável.
  • Notas: viola-se este requisito se a composição disparar sem revisão do prompt, se o catálogo de pontos de dado ficar editável ou colável, ou se a edição do prompt introduzir um número sem dp: (o gate de proveniência RF-CMP-007 e o Ap.8 reprovam). O painel do prompt exibe o aviso fixo "editar aqui não muda nenhum número: números vêm só dos dados", para o operador não achar que reescrever um valor no texto altera o que a página exibe.

RF-CMP-003: Wizard passo 3, leitura da IA com fail-loud

  • Descrição: no terceiro passo, após o parse, a IA mostra o que entendeu dos dados (preview) e, com destaque, "o que não achei" (as lacunas). A honestidade estrutural sobre lacunas é feature de primeira classe.
  • Prioridade: Essencial
  • Atores: Operador, sistema
  • 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.
  • Notas: viola-se este requisito se uma lacuna esperada não for exibida ao operador. Padrão de fail-loud. O "esperado" é derivado deterministicamente, não é texto livre da IA: o conjunto esperado é o dos blocos do template escolhido (o tipo de relatório traz os blocos padrão, RF-ADM-009) mais o KPI principal do briefing (RF-CMP-002); a lacuna é a diferença entre esse esperado e os blocos que têm dado suficiente nos artefatos. Se o template pede um bloco de funil e falta o dado da etapa final, essa etapa vira gap.

RF-CMP-004: Wizard passo 4, composição proposta com origem

  • Descrição: o quarto passo mostra a composição proposta bloco a bloco, cada bloco exibindo a origem de seus números, e leva ao compositor.
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se um bloco proposto exibir número sem origem visível.

RF-CMP-005: A IA escreve só narrativa, números por extração determinística

  • Descrição: os números nunca saem do modelo de linguagem; um extrator determinístico por código lê a célula ou o campo e injeta o valor. A IA escreve apenas a narrativa.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um número exibido não tiver origem determinística. Antídoto de prompt injection via artefato (o arquivo do cliente não consegue mandar a IA "escrever ROAS 10x"). O mecanismo concreto (a IA propõe o locator como hipótese, o código lê e reverifica o valor no byte cru) e a granularidade de locator por tipo de artefato estão no Ap.8. A narrativa segue o Ap.5 (afirmação sempre ancorada, hero é a tese da direção); afirmação sem âncora reprova na rubrica 5D (Ap.4).

RF-CMP-006: Proveniência obrigatória por número

  • Descrição: cada número referencia um ponto de dado dp:<id> com origem (artefato mais localizador, por exemplo Planilha1!B12, ou fonte externa da allowlist). O extrator popula o registro de pontos de dado; a IA só narra.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um número entrar num bloco sem ponto de dado associado. Golden set legado entra com proveniência marcada como legada.

RF-CMP-007: Número sem proveniência reprova a publicação

  • Descrição: a publicação é bloqueada se qualquer número do relatório não tiver proveniência. É gate HARD.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • 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.
  • Notas: viola-se este requisito se um relatório com número órfão de origem chegar a ser publicado. Número-métrica no corpo sem token de dp existente também reprova (Ap.8). Zero alucinação numérica.

RF-CMP-008: Verificação de polaridade

  • Descrição: um check determinístico de polaridade valida a direção da leitura (um número certo pode ser interpretado ao contrário). O gate de proveniência valida presença, não direção; este valida direção.
  • Prioridade: Alta
  • Atores: sistema
  • 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 (narrativa diz "melhorou" e o número piorou) é sinalizada.
  • Notas: viola-se este requisito se uma inversão de leitura passar sem sinalização.

RF-CMP-018: Fato adverso suprimido ou selo contradito reprova a publicação

  • Descrição: gate HARD de honestidade, irmão do gate de proveniência (RF-CMP-007), determinístico (lê data_points e o selo, RN-012, não julga a prosa por NLP). Bloqueia o publish se um fato adverso obrigatório de RN-012 item 1 foi suprimido, se a métrica âncora não carrega o selo íntegro de RN-012 item 2, ou se existe um badge emitido pelo produto com valência oposta ao selo na âncora. Selo íntegro tem DOIS formatos válidos, nunca um terceiro: (a) número mais sinal, quando existe base de comparação (uma das 2 fontes de RN-012 item 2); (b) marcador explícito "sem base" (só o número, sem seta), quando nenhuma das 2 fontes resolveu; esse segundo formato é íntegro e LIBERA o publish (ausência de base de comparação nunca é desonestidade). O que reprova é a ausência TOTAL do campo seal (nem (a) nem (b), sinal de que o mecanismo de selagem não rodou) ou um selo corrompido (sinal/percentual forjados fora do cálculo por código).
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dado um relatório em que um fato adverso aritmético de RN-012 está presente nos dados mas ausente da composição, ou cuja métrica âncora publica sem o selo em nenhum dos 2 formatos íntegros (com sinal ou marcador explícito de sem-base), ou com um badge de valência oposta ao selo, Quando o operador tenta publicar, Então a publicação é recusada, E o sistema aponta exatamente o fato suprimido, o selo ausente/corrompido ou o badge contradito.
  • H2: Dado um relatório cuja métrica âncora não tem nenhuma base de comparação válida (nem no artefato, nem em relatório anterior do mesmo cliente e tipo), Quando o operador publica, Então a publicação é ACEITA com o selo no formato "sem base" (número sem seta), porque reprovar aqui tornaria o primeiro relatório de todo cliente impublicável, o que nunca foi a intenção do requisito.
  • Notas: viola-se este requisito se um relatório com fato adverso suprimido, selo ausente/corrompido (nos dois sentidos: nem sinal nem marcador de sem-base) ou badge contradito for publicado; NÃO se viola por publicar sem seta quando genuinamente não há base de comparação (H2). A checagem é estrutural (presença do fato, integridade do selo, ausência de badge oposto), não análise de sentimento da narrativa livre (esse é o limite declarado em RN-012). O feedback é ANTECIPADO no compositor, não só no publish: o bloco que carrega um fato adverso obrigatório aparece marcado como protegido, e tentar escondê-lo é recusado na hora com o motivo ("fato adverso obrigatório, RN-012"), para o operador não brigar com um erro tardio de publicação sem entender a causa. Reconciliado 2026-07-30 ({F9-12} fix): o cálculo do selo (RN-012 item 2) estava pronto desde {F1-18} mas nenhum caminho real de composição/publish o chamava, ou seja, todo relatório do wizard reprovava o gate por ausência ou corrupção do selo da âncora. A decisão do dono do produto fechou a precedência das 2 fontes de base de comparação (ver RN-012 item 2) e o formato "sem base" como estado íntegro. A resolução e a selagem da âncora passaram a ser chamadas no caminho real de publicação, antes de o gate rodar, e não apenas existir na biblioteca de guardas.

RF-CMP-009: Composição in-process com isolamento cross-tenant, parse isolado à parte

  • Descrição: a composição de IA roda in-process (síncrona), reusando o gateway de IA da plataforma-mãe (RF-CMP-011), com o contexto montado SÓ a partir do report_id e sem estado global entre composições. O que garante a barreira cross-tenant não é isolamento de processo, é a ausência de estado global mais o contexto por report_id: cada composição só enxerga o próprio relatório. O processo que fica de fato isolado é o PARSE de arquivo (RF-ING-003): container efêmero sem rede de saída, que é onde mora o risco de exploit de arquivo (XXE, zip bomb, SVG com script, PDF malicioso executam no parser, antes de qualquer IA); falha de parse rejeita aquele arquivo com motivo e vira lacuna (fail-closed por arquivo, RF-ING-003), nunca degrada aberto nem derruba os demais do lote. A composição opera sobre o dado já saneado pelo parse.
  • Prioridade: Essencial
  • Atores: sistema
  • 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 o parse dos artefatos roda num container isolado sem rede de saída, com falha de parse rejeitando aquele arquivo (fail-closed por arquivo, vira lacuna declarada) sem derrubar os demais do lote (RF-ING-003).
  • Notas: viola-se este requisito se dado de um tenant vazar para a composição de outro por estado global, ou se o parse de arquivo tiver rede de saída ou degradar aberto numa falha. Uma fila com worker assíncrono dedicado é evolução (disparada pelo primeiro arquivo que estoure o tempo de request), reusando o mesmo padrão de container efêmero do parse; no V1 a composição é in-process.

RF-CMP-010: API-first, publicar exige a capability de publicação numa sessão humana

  • Descrição: o wizard é uma UI sobre a API. A separação entre "compor" e "publicar" se dá por CAPABILITY, não por escopo de token (a plataforma-mãe autentica por sessão mais capability-slug, não tem sistema de escopo de token de serviço): compor exige a capability reports.compose; publicar exige a capability reports.publish, que só é concedida a papéis humanos, nunca a um ator de serviço. A composição interna que a própria casa dispara roda com as claims de sistema (bypass de RLS dentro do processo), com reports.compose e jamais reports.publish.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • 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 que carrega a capability reports.publish.
  • Notas: viola-se este requisito se um ator sem reports.publish conseguir publicar. Sem isso, a curadoria vira teatro. Um token de serviço externo do Reports (para agents da casa comporem via API de fora do processo) portaria a capability reports.compose e NUNCA reports.publish; esse token é trabalho futuro a declarar, não infraestrutura existente.

RF-CMP-011: Reuso do gateway de IA da plataforma-mãe, cadeia gratuita

  • Descrição: a composição REUSA o gateway de IA da plataforma-mãe (o orquestrador de modelos que o Hub já opera), proibido um client de IA próprio. A cadeia gratuita real do gateway encadeia N provedores/modelos gratuitos por prioridade (hoje 4: dois modelos Groq e dois modelos Google, cada um com cota diária própria e independente), avançando pro próximo assim que o atual esgota a cota ou falha, com os provedores pagos existindo só como configuração por agência via cofre, jamais acionados em silêncio. Se a qualidade não passar na rubrica, o caminho é voltar com números para decisão, nunca subir para modelo pago automaticamente.
  • Prioridade: Alta
  • Atores: sistema
  • 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, E quando nenhum provedor gratuito responde a composição FALHA de forma explícita (o rascunho fica sem narrativa, com a lacuna declarada ao operador), nunca entregando texto de modo de segurança local como se fosse narrativa composta.
  • Fail-loud da cadeia esgotada (medido em 2026-07-28, Fatia 1; stack ampliada e fail-loud estrutural corrigido em 2026-07-29, {F7-01}): a cota diária do provedor gratuito primário (100 mil tokens por dia) foi consumida pelo próprio protótipo, e com o segundo provedor gratuito fora do ar a cadeia caiu inteira. O comportamento observado era degradar em silêncio para o modo local de segurança e devolver o texto dele como narrativa: isso era defeito, não fallback, e foi corrigido na fonte: o modo local de segurança (echo) saiu da ordem de dispatch padrão do gateway e só é alcançável com opt-in explícito (allowStub: true), uso reservado a scripts de desenvolvimento e de integração contínua, nunca ao caminho de produção (nem a composição nem o juiz de rubrica passam essa opção). Quando todos os provedores reais da cadeia gratuita falham, o gateway devolve falha explícita (um payload estruturado com o marcador de falha total e o motivo, nunca texto de narrativa); uma narrativa produzida em modo de segurança NUNCA é publicável nem contável na rubrica de qualidade, porque esse caminho está estruturalmente fechado por padrão. Prova: a suíte automatizada FAIL-LOUD do gateway, mais uma verificação contra a rede real com a cadeia inteira indisponível.
  • Notas: viola-se este requisito se o Reports instanciar um client de IA próprio em vez de reusar o gateway, ou se o pipeline chamar um modelo pago sem decisão explícita. Estado medido ao vivo em 2026-07-29: o modelo de fallback antigo do Google (gemini-2.0-flash) está morto (HTTP 429, cota zerada/descontinuada no free tier), mas a MESMA credencial do provedor Google funciona em gemini-2.5-flash (troca de modelo, não de credencial) e num segundo modelo com cota separada da família Gemini (gemma-4-31b-it, família Gemma tem bucket próprio no Google AI Studio). O mesmo padrão foi aplicado ao Groq: a MESMA credencial do provedor tem bucket de cota separado por modelo (medido: llama-3.3-70b-versatile e llama-3.1-8b-instant respondem com limites de rate-limit distintos). Resultado: a cadeia gratuita cresceu de 2 para 4 slots reais e credenciados SEM nenhuma conta nova, usando dois modelos por provedor como redundância dentro do mesmo fornecedor antes de trocar de fornecedor. Candidatos gratuitos adicionais avaliados e NÃO incluídos por falta de credencial (decisão do dono pendente, 2026-07-29): OpenRouter (conta existe para outro projeto, o council de modelos, mas isolamento de projeto exige credencial dedicada, ainda não criada), Cerebras, Mistral e Together AI (nenhuma conta aberta), e Cloudflare Workers AI (registrado no catálogo interno de provedores de IA do Hub com prioridade baixa, mas sem a credencial do provedor configurada em lugar nenhum e sem dispatch implementado no próprio Hub, ou seja, nem lá está vivo). Repor esses provedores no gateway da plataforma-mãe (e aqui) é trabalho válido, mas depende de decisão do dono sobre criar conta/credencial nova; não é bloqueio deste requisito, que já está satisfeito com os 4 slots gratuitos reais.

RF-CMP-012: Custo e tempo medidos por composição

  • Descrição: cada composição registra custo (tokens) e tempo, mesmo sem billing, para virar o teto numérico de acompanhamento.
  • Prioridade: Média
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se uma composição não registrar custo e tempo. Com a composição síncrona in-process (RNF-PERF-002), o custo e o tempo gravam direto no report_draft (ou numa linha de métrica), sem depender da report_job, que só entra com a fila na evolução.

RF-CMP-013: Tom de voz alimenta a narrativa

  • Descrição: o tom de voz do cliente (da ficha de identidade) é injetado como parâmetro direto da narrativa da IA.
  • Prioridade: Alta
  • Atores: sistema
  • 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-ADM-006).
  • Notas: viola-se este requisito se a narrativa ignorar o tom declarado.

RF-CMP-014: Benchmark só da allowlist com fonte

  • Descrição: comparações com benchmark de mercado só usam valores da allowlist de fontes, com fonte nomeada; a IA não inventa "média de mercado". Sem benchmark disponível, a comparação é interna ou usa o ranking da própria plataforma de mídia.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se aparecer um benchmark de mercado sem fonte. Antídoto ao benchmark alucinado.

RF-CMP-015: Cap estrutural de verbosidade

  • Descrição: o schema impõe um teto de tamanho por bloco de texto (por exemplo, insight com no máximo 280 caracteres), rejeitando verbosidade estrutural.
  • Prioridade: Média
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um bloco publicado ultrapassar o cap.

RF-CMP-016: Anti-fadiga por variação do dado contra variação do texto

  • Descrição: a guarda compara a variação do DADO com a variação do TEXTO, e nunca texto com texto isolado. Comparar só texto com texto pune a repetição em si, e uma trava que pune repetição empurra a IA a inventar variação: o modelo aprende que reescrever a mesma leitura com palavras novas passa, o que produz novidade fabricada em cima de dado parado. Os quatro quadrantes, cada um com o seu desfecho nominal:
DadoLeituraDesfecho
igualigualLEGÍTIMO. Não é fadiga, é a verdade daquele período. O relatório deve DIZER isso ("sem mudança material desde a edição anterior"), porque estabilidade é informação para quem decide, e apagá-la é que seria desonesto
mudouigualFADIGA REAL, reprova. O dado se moveu e a leitura não acompanhou: ou o redator não olhou, ou o modelo repetiu o contexto anterior
igualnovaSUSPEITO, sinaliza para o operador. É a assinatura de novidade fabricada: leitura nova sobre dado parado costuma ser reescrita cosmética ou afirmação sem lastro. Não reprova sozinho (o ângulo pode ter mudado legitimamente por direção do briefing), mas exige olhar humano
mudounovaesperado, passa

A variação do dado é medida sobre os pontos de dado da âncora e das métricas destacadas na direção (RF-CMP-002), com o limiar de materialidade sendo parâmetro de agência do ART-CFG. A variação do texto continua sendo similaridade, agora como um dos dois eixos e nunca como critério único.

O acervo de comparação deste requisito continua SEM filtro de cadência nem de largura de janela, e isso é deliberado. A restrição de cadência e de largura vale só na base do selo da âncora (RN-012 item 2, RF-CMP-021), porque lá o objetivo é um número comparável. Aqui o objetivo é o maior acervo possível de narrativa anterior do mesmo cliente, para não repetir texto, e filtrar esse acervo por cadência o encolheria sem ganho nenhum: repetir a mesma frase é repetir a mesma frase, independentemente de a edição anterior ter sido quinzenal ou mensal. São duas leituras diferentes de "relatório anterior", com filtros diferentes de propósito, e igualá-las quebraria uma das duas.

Recomendação repetida não se reescreve, ESCALA. Quando a mesma recomendação aparece em edições consecutivas sem ter sido executada, a guarda não pede outra redação: ela promove o item, e a página passa a dizer há quantas edições consecutivas aquela recomendação está pendente ("terceira quinzena consecutiva com a mesma recomendação pendente"). Isso costuma ser o achado mais valioso do relatório, e a versão antiga desta guarda o destruía, porque tratava a repetição como defeito a ser mascarado.

  • Prioridade: Alta
  • Atores: sistema, Operador
  • H1: Dado um cliente com relatório anterior, Quando o relatório novo é composto, Então o sistema classifica a composição num dos quatro quadrantes de dado contra texto, E o quadrante "dado mudou e leitura igual" reprova a composição, E o quadrante "dado igual e leitura nova" é sinalizado ao operador sem reprovar, E o quadrante "dado igual e leitura igual" passa e a página declara que não houve mudança material desde a edição anterior, E uma recomendação repetida sai da página com a contagem de edições consecutivas em que está pendente.
  • Notas: viola-se este requisito quando a guarda decide só por similaridade de texto, quando dado igual mais leitura igual é tratado como defeito (isso é o que produz novidade fabricada), quando dado que se moveu convive com leitura idêntica sem reprovar, ou quando uma recomendação repetida é reescrita com outras palavras em vez de escalar com a contagem.

RF-CMP-019: Compilador de pré-prompt

  • Descrição: uma chamada única à mesma cadeia de IA gratuita (RF-CMP-011) transforma uma nota solta do operador mais o catálogo derivado de dp: e blocos disponíveis no objeto de direção do RF-CMP-002 (nunca a narrativa nem número), exibido para o operador editar antes do prompt de composição (RF-CMP-017); roda no briefing, é opcional por uso (a direção manual do RF-CMP-002 basta sem ele), disponível desde a Fatia 1, e fica fora do teto de 60s (RNF-PERF-002).
  • Prioridade: Essencial
  • Atores: Operador, sistema
  • H1: Dado uma nota solta do operador e o catálogo de dp: e blocos, Quando o operador aciona o compilador, Então uma chamada à cadeia gratuita devolve um objeto de direção editável (nunca narrativa nem número), E o operador revisa e confirma antes de compor, E o passo é opcional (a direção manual do RF-CMP-002 basta sem ele).
  • Notas: viola-se este requisito se o compilador emitir narrativa ou número em vez do objeto de direção, se rodar dentro do teto de composição, ou se virar obrigatório. Disponível desde a Fatia 1.

RF-CMP-020: Fail-loud humano da cadeia gratuita esgotada, com aviso ao admin em um clique

  • Descrição: decisão do dono ({Q5}, 2026-07-29): a operação de composição fica só com a cadeia gratuita (RF-CMP-011), sem crédito pago, mas com comunicação explícita ao operador e um caminho de escalonamento. Quando a cadeia gratuita inteira falha numa composição (RF-CMP-011, o gateway sinaliza explicitamente que todos os provedores falharam), o sistema não entrega narrativa nenhuma, nem local nem de stub (reforça RF-CMP-011); o operador vê uma mensagem em linguagem humana, sem jargão técnico nem código de erro cru, dizendo o que aconteceu e o que dá para fazer (por exemplo, que a capacidade de escrita automática da casa esgotou a cota do dia, que os números do relatório continuam válidos, e que ele pode escrever a narrativa à mão agora ou tentar novamente mais tarde); e uma ação de um clique dispara um aviso ao admin da agência, com o contexto da falha (qual relatório, quando, e quais provedores falharam e por quê), pelo canal interno que a plataforma-mãe já usa para notificação (a Central de Notificações do Hub, messages.publish_notification), nunca um canal novo. O número nunca depende de IA (extração determinística, RF-CMP-005/006); é isso que torna a falha suportável, o relatório não se perde, só fica sem texto automático.
  • Prioridade: Essencial
  • Atores: sistema, Operador, Admin
  • H1: Dado que a cadeia gratuita de IA falhou por completo (RF-CMP-011) durante uma composição, Quando o resultado chega ao operador, Então nenhuma narrativa é entregue (nem local nem de stub), E o operador vê uma mensagem em linguagem humana explicando o que aconteceu e o que fazer, sem jargão técnico nem código de erro cru, E os números do relatório continuam íntegros e disponíveis, E uma ação de um clique dispara um aviso ao admin da agência pela Central de Notificações do Hub com o relatório, o horário e os provedores que falharam e o motivo de cada um.
  • Notas: viola-se este requisito se uma narrativa (mesmo de stub) chegar ao operador nesse cenário, se a mensagem exibir jargão técnico ou código de erro cru (por exemplo um código de falha interno do gateway ou um status HTTP cru), se o aviso ao admin for inventado num canal novo em vez de reusar a Central de Notificações existente, ou se o aviso não carregar o relatório, o horário e o motivo real por provedor. O motivo por provedor tem que ser o real, capturado na origem (RF-CMP-011): um motivo genérico ou fabricado a jusante não passa. A resolução de quem é "o admin da agência" (RF-ADM-002/RF-ADM-003, o papel de administração da agência no RBAC do Hub) é responsabilidade da integração como módulo do Hub (Fatia 6/7); esta fatia entrega a base determinística (classificação do motivo, mensagem humana, payload do aviso, e o disparo em si contra messages.publish_notification) com o ponto de ligação explícito, não a tela do botão nem a resolução de destinatário, que pertencem ao compositor (Fatia 8) quando o RBAC estiver montado.

RF-CMP-021: Comparação entre janelas de larguras diferentes é recusada ou marcada

  • Descrição: comparar um período PARCIAL com um período CHEIO produz um número falso, e hoje isso depende de disciplina de quem redige. Este requisito move a guarda para o código. Sempre que a composição produzir uma comparação entre duas janelas de tempo (o selo da métrica âncora de RN-012 item 2, um bloco comparison, uma variação derived cuja fórmula opera dois períodos, ou a base de comparação escolhida pela cadência em RN-014), o sistema calcula a largura em dias das duas janelas. Se as larguras diferem em mais do que a tolerância configurada (parâmetro de agência do ART-CFG, com seed default de 10 por cento), há apenas dois desfechos possíveis: (a) a comparação é RECUSADA e a variação não é emitida, ou (b) a comparação é emitida com a marca explícita de janela parcial colada ao número, dizendo as duas larguras reais (por exemplo, "31 dias comparados com 92 dias"), no mesmo componente não editável do selo. Nunca um terceiro desfecho: emitir a variação limpa, como se as janelas fossem equivalentes, é a violação. A escolha entre (a) e (b) é do parâmetro de agência, não do operador por página. A marca de janela parcial é gravada no dp: derivado e copiada para o snapshot, então ela sobrevive ao publish e não pode ser removida pela narrativa nem pela curadoria.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado um relatório cuja janela tem 31 dias e cuja base de comparação tem 92 dias, Quando a composição emite a variação entre as duas, Então ou a variação não é emitida, ou ela sai com a marca explícita de janela parcial declarando as duas larguras em dias, E em nenhum dos dois casos uma variação sem marca é gravada no dp: derivado, E a marca presente no dp: derivado aparece no snapshot publicado.
  • Notas: viola-se este requisito quando uma variação entre janelas de larguras diferentes além da tolerância é gravada ou publicada sem a marca de janela parcial, quando a marca existe no rascunho mas some do snapshot, ou quando o operador consegue remover a marca pela curadoria. Caso real que originou o requisito: um trimestre com apenas um mês de dados foi comparado com o trimestre cheio do ano anterior e o relatório afirmou queda de 71 por cento, número que a diferença de largura das janelas produziu sozinha. A guarda é aritmética (largura em dias), não julgamento de prosa.

RF-CMP-022: Métrica não somável entre janelas usa o pico, nunca a soma

  • Descrição: nem toda métrica se soma quando duas janelas de tempo são juntadas, porque a mesma pessoa contada em duas janelas continua sendo uma pessoa. As métricas não somáveis entre janelas são um conjunto nominal fechado, parâmetro de agência do ART-CFG, com seed default de três: alcance, usuários únicos e visitantes únicos. Para uma métrica desse conjunto, quando o relatório agrega mais de uma janela (por exemplo, três meses num relatório trimestral montado a partir de três exportações mensais), o valor agregado é o PICO das janelas, nunca a soma, e o dp: derivado registra na formula que o valor é o pico e sobre quantas janelas. Métrica fora do conjunto continua somando normalmente. O conjunto é editável pelo admin da agência porque plataformas de mídia novas trazem métricas não somáveis novas, e travar a lista em código forçaria deploy a cada uma.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado um relatório que agrega três janelas mensais e cuja fonte traz alcance em cada uma, Quando o valor agregado de alcance é calculado, Então o dp: gravado é o maior dos três valores e não a soma deles, E a formula do dp: derivado declara que o valor é o pico e sobre quantas janelas, E uma métrica fora do conjunto de não somáveis agregada no mesmo relatório continua sendo somada.
  • Notas: viola-se este requisito quando o alcance, os usuários únicos ou os visitantes únicos aparecem somados entre janelas em qualquer número exibido, quando a formula do valor agregado não distingue pico de soma, ou quando o conjunto de métricas não somáveis vira lista em código sem edição pelo admin (contraria a regra de configurabilidade do bloco 11). O requisito NÃO proíbe somar métricas fora do conjunto: proíbe nominalmente somar alcance, usuários únicos e visitantes únicos entre janelas. Por que o dono desta regra é o EXTRATOR e não o compositor (ADR-019): aqui o número em si seria falso, então ele não pode chegar a existir; o extrator recusa produzir o agregado somado e entrega o pico com a janela nomeada. Compare com RF-CMP-021, onde os dois números são verdadeiros e a mentira está na comparação, e por isso o dono é o selo mais o compositor.

RF-CMP-023: Régua de confiabilidade derivada, nunca escrita

  • Descrição: o bloco reliability (Ap.3) é montado por código a partir de três fatos que o sistema já calcula para compor a página: quais fontes de dado alimentaram este relatório (os artefatos que parsearam com sucesso, RF-ING-003), quais blocos esperados viraram lacuna e por quê (a mesma origem do bloco gap, RF-CMP-003), e se a âncora tem base de comparação ou está no estado "sem base" (RN-012 item 2, RN-015). O corpo sai de um template determinístico, sem IA nenhuma, e é não editável: um operador que apague "sem base" do texto produziria uma página afirmando comparabilidade que o sistema sabe não existir. Hoje esse parágrafo é digitado à mão pelo redator em cada relatório, o que é o padrão "correto enquanto alguém lembrar"; este requisito o transforma em garantia de máquina. O operador que discorda acrescenta uma note ao lado, e a régua derivada continua visível.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado um relatório composto a partir de dois artefatos, com um bloco esperado virado lacuna e com a âncora no estado "sem base", Quando a página é montada, Então o bloco reliability existe sem nenhuma chamada de IA, E ele nomeia as duas fontes, a lacuna com o motivo e o estado "sem base" da âncora, E nenhuma superfície (compositor, API, curadoria) permite editar ou remover esses três campos, E acrescentar a note do operador não altera nenhum dos três.
  • Notas: viola-se este requisito quando a régua é redigida pela IA ou pelo humano, quando qualquer um dos três fatos derivados fica editável, quando o bloco pode ser excluído do documento pela curadoria, ou quando ele diverge do que o bloco gap e o selo da âncora declaram na mesma página (divergência entre a régua e os blocos que ela resume é defeito, não estilo).

RF-CMP-024: Base da comparação declarada e visível dentro da frase

  • Descrição: todo número comparativo declara contra o que compara, e a declaração aparece DENTRO da frase, nunca só em rodapé nem só em metadado. Percentual pelado ("+18 por cento") é o veículo da classe de mentira que este requisito fecha; a forma exigida é "mais 18 por cento ante dezembro de 2025 (mesmo mês do ano anterior)". No bloco comparison isso é o campo base obrigatório, valor fechado de dois (meta ou periodo_anterior), derivado da origem do dado e não escolhido pelo operador, renderizado no cabeçalho do bloco e em cada linha. No selo da âncora e em qualquer variação derived de dois períodos, a base entra colada ao número no mesmo componente não editável. Sem discriminador, o leitor vê "queda de 18 por cento" sem saber se é contra uma promessa que a agência fez ou contra o mês passado, que são duas afirmações de gravidade diferente.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado um bloco comparison cuja origem é a meta importada e outro cuja origem é o período anterior, Quando a página é renderizada, Então cada um exibe a sua base por extenso no cabeçalho e em cada linha, E nenhum número comparativo da página aparece sem a base na própria frase, E a base não é editável pelo operador, E ela sobrevive no snapshot publicado.
  • Notas: viola-se este requisito quando um percentual comparativo é publicado sem a base na frase, quando a base vive só no rodapé ou só em metadado, quando o operador consegue trocar meta por periodo_anterior pela curadoria, ou quando a base some do snapshot.

RF-CMP-025: Tupla de identidade do número exibido

  • Descrição: a guarda de coerência de rótulo que existe hoje ({F9-13}) fecha apenas um espaço da frase, o nome da métrica. Passa por ela, hoje, uma frase como "o criativo em vídeo alcançou 167.364 impressões em julho", onde o número é o total da CAMPANHA e o criativo respondeu por 41 por cento dele: a métrica citada está certa, e a frase é falsa mesmo assim. O conserto é generalizar. Todo ponto de dado extraído carrega uma TUPLA DE IDENTIDADE completa, de cinco campos nominais: métrica, entidade, período, segmento e agregação. A guarda compara TODOS os cinco com o que a frase afirma, e não só a métrica. Um número de campanha citado como sendo de um criativo diverge no campo entidade e reprova; um número de julho citado como do trimestre diverge no período e reprova; um total citado como média diverge na agregação e reprova.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado um ponto de dado cuja tupla declara entidade igual a campanha e uma frase da narrativa que atribui esse número a um criativo, Quando o gate de coerência roda, Então a frase é reprovada com o campo divergente nomeado (entidade), E a mesma frase com a entidade correta passa, E a divergência é detectada também nos campos período, segmento e agregação, E nenhum número exibido chega ao snapshot sem os cinco campos da tupla preenchidos.
  • Notas: viola-se este requisito quando a guarda compara só o nome da métrica, quando um ponto de dado é gravado com qualquer um dos cinco campos vazio, ou quando uma frase que atribui número de uma entidade a outra passa no gate. A tupla é do ponto de dado e é gravada no snapshot, então a verificação é refazível depois da publicação.

RF-CMP-026: Conectivo causal exige evidência designada

  • Descrição: uma afirmação de causa ("o aumento veio da campanha do Meta") afirma mais do que os números mostram, porque os números mostram coocorrência. Este requisito nomeia o conjunto de conectivos causais tratados (veio de, foi causado por, resultou em, gerou, por causa de, graças a, devido a, o que levou a, o que explica) e exige que a frase que use qualquer um deles aponte para uma evidência designada no artefato: um recorte que isole a variável (a mesma métrica com e sem aquele fator), um teste declarado na fonte, ou uma atribuição que a própria plataforma de origem emita. Sem evidência designada, o compositor não bloqueia a leitura: ele a EMITE COMO COOCORRÊNCIA ("o aumento aconteceu no mesmo período em que a campanha do Meta subiu"), que é verdadeira e continua útil.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado uma frase da narrativa que usa um conectivo causal do conjunto nominado e um artefato sem evidência designada para aquela relação, Quando a composição termina, Então a frase é reescrita como coocorrência antes de chegar ao operador, E a mesma frase com evidência designada no artefato é mantida na forma causal com a evidência referenciada, E nenhum conectivo do conjunto chega ao snapshot sem evidência designada.
  • Notas: viola-se este requisito quando um conectivo causal é publicado sem evidência designada, quando a guarda trata o conjunto como ilustrativo em vez de nominal, ou quando a falta de evidência apaga a frase inteira em vez de degradá-la para coocorrência (apagar perde informação verdadeira).

RF-CMP-027: A edição agendada nasce como rascunho e já compara com a edição anterior

  • Descrição: quando o ciclo de uma agenda ativa vence, o sistema adota o relatório que o operador já deixou preparado para aquela conta, propósito e janela (criado, com artefato subido e direção confirmada, sem ter clicado em compor), compõe sozinho a edição daquele período e ela nasce em RASCUNHO, com a base de comparação já resolvida contra a edição anterior da mesma agenda. Comparar com a anterior é o que dá sentido à cadência: uma edição periódica que não se compara com a anterior é um relatório avulso com data no título. A base é a que RN-014 prescreve para aquela cadência, resolvida pela precedência de RN-012 item 2, e passa sem exceção pela guarda de largura de janela de RF-CMP-021: se a janela da edição anterior tem largura diferente da desta, a comparação é recusada ou sai marcada como parcial, nunca limpa. A PRIMEIRA edição de uma agenda não tem anterior e nasce no estado "sem base" do selo, que é íntegro e não reprova (RN-015).
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dada uma agenda mensal ativa cuja edição do mês anterior está publicada, Quando o ciclo do mês corrente vence, Então a edição do mês corrente nasce sozinha em rascunho, E a base de comparação dela é a edição do mês anterior, E a base aparece por extenso dentro da frase de cada número comparativo (RF-CMP-024), E uma edição cuja janela tenha largura diferente da anterior sai recusada ou marcada como parcial (RF-CMP-021), E a primeira edição de uma agenda nasce no estado "sem base" sem reprovar.
  • Notas: viola-se este requisito quando a edição agendada nasce publicada, quando ela nasce sem base existindo edição anterior, quando compara contra base que RN-014 não prevê para aquela cadência, quando a comparação escapa da guarda de largura de janela, ou quando a primeira edição de uma agenda reprova por falta de base.

RF-CMP-028: A geração automática NUNCA publica sozinha

  • Descrição: nenhuma edição nascida de agenda vai ao ar sem gesto humano de publicação, e nenhuma delas atravessa por caminho próprio as guardas que uma edição manual atravessa. Isto é o coração do escopo da geração agendada e não um detalhe de fluxo: a automação é a rota por onde uma guarda é contornada em silêncio, porque ela roda sem ninguém olhando e no volume. O que este requisito PROÍBE, nominalmente: publicar uma edição sem gesto humano; publicar uma edição que não passou pelo gate de proveniência (RF-CMP-007); publicar uma edição que não passou pelo gate de honestidade do selo e da comparação (RN-012, RF-CMP-021, RF-CMP-024); publicar uma edição com lacuna não declarada (RF-CMP-003); e criar qualquer parâmetro, chave de configuração ou caminho de código que permita a uma edição agendada pular qualquer um deles, ainda que por opção do admin. Não existe publicação automática ligável por configuração: a configuração que a permitisse é ela própria a violação.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dada uma edição nascida de agenda e completa, Quando nenhum operador a publica, Então ela permanece em rascunho indefinidamente e nenhum link público responde por ela, E quando o operador a publica ela atravessa os mesmos gates de proveniência e de honestidade que uma edição manual atravessa, E uma edição agendada que reprove qualquer um desses gates não publica, E não existe parâmetro de agência nem de agenda que permita publicar sem esses gates.
  • Notas: viola-se este requisito quando uma edição agendada é publicada sem gesto humano, quando ela publica por caminho de código diferente do publish manual, quando qualquer gate passa a ser condicional à origem da edição (agendada contra manual), ou quando nasce opção de configuração de publicação automática.

RF-CMP-029: Ciclo que não gerou é visível, nunca silencioso

  • Descrição: uma agenda ativa cujo ciclo venceu sem produzir edição tem que dizer isso, com o motivo, no mesmo lugar em que o operador olha as agendas. Os motivos possíveis são nominais e são quatro: não havia artefato do período para o cliente, o parse do artefato falhou, a cadeia de inteligência artificial gratuita estava indisponível (RF-CMP-020), ou a composição reprovou uma guarda. Silêncio é o modo de falhar mais caro de um agendamento, porque a operação passa a confiar que o sistema está gerando enquanto ele parou, e ninguém reporta o que não aparece. O aviso ao operador responsável usa a Central de Notificações interna, nunca um canal novo. Um ciclo que falhou não é repetido sozinho em retentativa: fica registrado como ciclo sem edição, e o operador decide.
  • Prioridade: Essencial
  • Atores: sistema, Operador, Admin
  • H1: Dada uma agenda ativa cujo ciclo venceu sem artefato disponível para o cliente, Quando o ciclo se encerra, Então a agenda registra o ciclo como sem edição com um dos quatro motivos nominais, E o operador responsável recebe o aviso pela Central de Notificações interna, E a agenda continua ativa para o ciclo seguinte, E o ciclo falhado não é repetido por conta do sistema.
  • Notas: viola-se este requisito quando um ciclo vencido sem edição não deixa registro, quando o motivo é genérico em vez de um dos quatro nominais, quando o operador não é avisado, quando a falha desativa a agenda sem gesto humano, ou quando o sistema repete a tentativa por conta própria.

RF-CMP-030: Cada etapa do funil canônico carrega volume, custo unitário e taxa de conversão da etapa anterior

  • Descrição: no funil canônico de quatro etapas (RF-REN-016), cada etapa carrega três números e não um: o VOLUME da etapa, o CUSTO UNITÁRIO daquele volume e a TAXA DE CONVERSÃO vinda da etapa imediatamente anterior. A primeira etapa não tem taxa de conversão, porque não existe etapa anterior a ela, e essa ausência é estrutural: não é lacuna, não entra no bloco gap e não reprova nada (RN-015, nulo de comparação). Os três números são pontos de dado com proveniência (Ap.8): volume e custo unitário vêm do artefato quando o artefato os traz, e a taxa de conversão é derived com fórmula e operandos declarados (volume da etapa dividido pelo volume da etapa anterior). Uma etapa cujo volume não foi medido é lacuna de cobertura de verdade: entra no gap, renderiza como etapa tracejada e nunca como zero (RF-REN-015), e a taxa da etapa SEGUINTE também não é emitida, porque calcular conversão sobre denominador não medido produz um número falso com aparência de verdadeiro.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dado um funil canônico com as quatro etapas medidas, Quando o relatório renderiza, Então cada etapa mostra volume, custo unitário e taxa de conversão da etapa anterior, E a primeira etapa mostra volume e custo unitário sem taxa e essa ausência não entra no gap, E cada taxa é um ponto de dado derived com fórmula e operandos declarados, E quando o volume da segunda etapa não é medido a segunda etapa aparece tracejada, a taxa da terceira não é emitida, e os dois motivos aparecem no gap.
  • Notas: viola-se este requisito quando uma etapa exibe volume sem custo unitário ou sem taxa existindo dado para eles, quando a ausência de taxa na primeira etapa é tratada como lacuna, quando uma taxa é escrita como número sem dp derivado, quando uma taxa é calculada sobre denominador não medido, ou quando etapa não medida aparece como zero.

RF-CMP-031: Toda métrica de resultado exibida tem origem declarada no catálogo do Ap.10

  • Descrição: a camada de resultado é o lugar onde um número errado custa mais caro, porque é sobre ele que o cliente decide continuar ou parar. Este requisito fecha a porta: toda métrica de resultado exibida em qualquer relatório está nominada no catálogo do Ap.10 e tem, nesse catálogo, a origem declarada como uma de três, e não uma quarta: extraída do artefato por locator (Ap.8), importada como parâmetro de negócio ou meta do cliente (RF-ING-007, RF-ING-008), ou derivada por fórmula nomeada cujos operandos são todos pontos de dado que já existem. Uma métrica de resultado fora do catálogo não é exibida, e uma métrica do catálogo cujos operandos não estejam todos disponíveis não é emitida: vira lacuna declarada (RF-CMP-003), nunca um número calculado sobre operando faltante tratado como zero. O catálogo é lista nominal e fechada; ampliá-lo é decisão de produto com entrada nova no Ap.10, nunca métrica digitada no compositor.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dado um relatório cuja camada de resultado exibe métricas do Ap.10, Quando o gate de publicação roda, Então cada métrica exibida resolve para uma entrada do Ap.10 com origem declarada, E uma métrica de resultado fora do catálogo reprova o publish, E uma métrica do catálogo cujo operando de negócio não foi importado sai como lacuna declarada e não como número, E nenhum operando faltante é substituído por zero.
  • Notas: viola-se este requisito quando uma métrica de resultado é exibida sem entrada no Ap.10, quando uma entrada do Ap.10 existe sem origem declarada, quando uma fórmula opera sobre ponto de dado inexistente tratando-o como zero, ou quando o compositor permite digitar uma métrica de resultado à mão.

RF-CMP-032: Uma ação humana no compositor não vira várias falhas registradas

  • Descrição: o compositor carrega uma tela buscando o relatório e a pré-visualização ao mesmo tempo. Quando o endereço não resolve, as duas chamadas falham e o sistema registra duas ocorrências para um único evento humano. Isso não quebra a tela, e é justamente por isso que passa despercebido: o dano é na MEDIÇÃO. Qualquer contagem que alguém faça depois (quantas pessoas esbarraram nisso, este defeito está crescendo, este agrupamento passou do limiar) sai inflada por um fator que não está declarado em lugar nenhum. Este requisito crava que o registro de falha conta eventos do usuário, não chamadas do cliente.
  • Prioridade: Média
  • Atores: Operador (produz sem saber); Super Admin (consome a contagem no diagnóstico).
  • Pré-condições: existe registro automático de ocorrência para as respostas de erro.
  • Entrada: uma navegação do operador que termina em falha.
  • Saída: uma ocorrência registrada por evento humano.
  • H1: Dado um operador que abre uma vez uma tela do compositor cujo recurso não resolve, Quando a tela termina de carregar, Então o número de ocorrências registradas para aquele evento é UM, E não uma por chamada disparada pela tela, E o comportamento da tela para o operador (a mensagem e o caminho de saída) não muda por causa disso, E uma tela que precise mesmo de duas chamadas independentes declara isso e continua registrando um evento.
  • Notas: o requisito é violado quando uma única navegação deixa mais de uma ocorrência do mesmo código, e a verificação é por contagem correlacionada entre os dois endereços no mesmo período, não por leitura de código. A correção é do lado do cliente, no caminho de FALHA (encadear em vez de disparar em paralelo, ou reconhecer que a segunda chamada é redundante quando a primeira já falhou); o caminho de SUCESSO pode continuar paralelo, porque é ele que faz a tela abrir rápido, e trocar isso seria pagar desempenho por causa de um defeito de contagem. Ver o Roadmap, Onda DEF, card {DEF-02}, e o Plano de Testes, seção Alfa.

C.7 Módulo reports-curate

RF-CUR-001: Curadoria do rascunho no compositor

  • Descrição: o compositor é a curadoria do rascunho: o operador edita o texto dos blocos, reordena e esconde blocos. É curadoria, nunca editor livre de layout.
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se um bloco escondido aparecer no relatório publicado. Cada número no preview exibe a sua origem por um indicador clicável (um selo que abre o source.locator do dp:, por exemplo "planilha X, célula B12"), para o operador conferir de onde o número veio; é a face de UI da garantia de proveniência (RF-CMP-007, Ap.8). Exceção ao esconder: um bloco marcado como protegido por fato adverso obrigatório (RN-012, RF-CMP-018) não pode ser escondido, e a recusa acontece na hora, com o motivo, dentro do compositor. O ajuste fino de apresentação (RF-CUR-008) é stepping de token dentro de escala fechada, não editor de layout; a proibição de editor livre de layout segue valendo.

RF-CUR-002: Regenerar bloco via prompt

  • Descrição: o operador pede à IA para regenerar um bloco específico com uma nova instrução (por exemplo, "foco no público 25 a 34").
  • Prioridade: Alta
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se regenerar um bloco alterar outros, ou perder a proveniência. Regenerar um bloco é chamada de resposta imediata, síncrona (bloco 9, J.4).

RF-CUR-003: Inserir criativos no relatório

  • Descrição: o operador insere imagens de anúncio (criativos) como blocos do relatório.
  • Prioridade: Alta
  • Atores: Operador
  • 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 (RF-ING-005), E o criativo aparece no preview.
  • Notas: viola-se este requisito se o criativo entrar como URL crua ou base64 no payload. O bloco inserido aqui é o primeiro do sistema que nasce fora da composição: ele carrega a marca de autoria do operador e sobrevive a qualquer reescrita do conjunto de blocos (RF-CUR-010), e a legenda dele está sob a mesma disciplina de número que o resto da prosa do relatório (RF-CUR-011).

RF-CUR-004: Ajustar a cor, o logo e os títulos do relatório para o cliente

  • Descrição: no compositor, o operador sobrepõe POR RELATÓRIO a identidade que aquele relatório usa, e a sobreposição é campo a campo e independente: definir a cor de acento não toca o logo, e o inverso. Ela é opcional em cima de uma cadeia que já existe (RN-020): sem sobreposição, a cor sai da ficha de identidade do cliente (RF-ADM-005, Ap.6) e, na falta dela, do padrão declarado da agência (RF-ADM-006); o logo sai da marca da agência (RF-ADM-004). Limpar uma sobreposição não grava vazio: remove a chave, e o campo volta na hora ao elo seguinte da cadeia, porque a ausência da chave é a única representação de "não configurado" que os consumidores rio abaixo precisam conhecer. O conjunto de chaves da sobreposição é fechado em duas, a cor de acento e o logo do cliente, e nenhuma outra é aceita em camada nenhuma. Os "títulos" deste requisito são dois e nominais: o título de cada bloco, que a whitelist de campos editáveis já entrega (RF-CUR-005), e o título do relatório, que é o nome da peça e se renomeia enquanto ela é rascunho (RF-CUR-013). Não existe, e não passa a existir por este requisito, um "título do cliente": a ficha de identidade do cliente (Ap.6) não tem esse campo, e inventá-lo criaria uma segunda fonte de verdade sobre o nome de quem já tem cadastro na plataforma-mãe.
  • Prioridade: Alta
  • Atores: Operador
  • H1: Dado um rascunho no compositor, Quando o operador define a cor de acento do relatório, Então o preview passa a exibir essa cor na mesma interação e sem publicar, E publicar copia essa cor para o snapshot, E limpar a sobreposição remove a chave em vez de gravar vazio e o preview volta imediatamente à cor do elo seguinte da cadeia (RN-020), E definir ou limpar um dos dois campos não altera o valor do outro, E nenhuma chave fora do par fechado é aceita, nem pela rota, nem pela guarda da aplicação, nem pelo banco, E quando o operador sobrepõe o logo do cliente a página publicada passa a exibir esse logo no portador de marca do cliente e continua exibindo a marca da agência no portador da agência (RN-020 item 4).
  • Notas: viola-se este requisito quando o preview exibe uma cor diferente da que a publicação resolveria para o mesmo estado do rascunho; quando o ajuste não é copiado para o snapshot; quando limpar a sobreposição grava um vazio em vez de remover a chave; quando definir um campo altera o outro; quando uma chave fora do par fechado é aceita em qualquer das três camadas; quando a cor sobreposta publica sem passar pela correção de contraste que o renderizador já aplica a qualquer acento (RF-REN-007); ou quando a sobreposição de logo faz a marca da agência sumir da página pública.
  • Notas: estado de construção, declarado em 2026-08-09 porque a metade que falta muda o que este requisito entrega hoje. A metade da COR está construída, junto do título do relatório (RF-CUR-013) e da resolução por ponto único (RF-CUR-012). A metade do LOGO não foi construída, e não pode ser antes de a página pública ganhar o segundo portador de marca (RN-020 item 4): hoje existe um portador só e ele carrega a marca da agência, então pôr o logo do cliente nele apagaria da página a marca que RF-ADM-004 exige. A cláusula do logo continua no H1 acima e é o que mantém este requisito em aberto; ela não foi afrouxada nem movida para outro requisito por conveniência de redação.

RF-CUR-005: Whitelist de campos editáveis por tipo de bloco

  • Descrição: cada tipo de bloco declara no schema quais campos são editáveis; o compositor só expõe esses campos.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o operador conseguir editar à mão um número com proveniência, quebrando a garantia de origem.

RF-CUR-006: Apresentar o rascunho antes de publicar

  • Descrição: o operador apresenta o rascunho em modo apresentação (tela cheia, navegação por passos), com o rótulo "rascunho", direto do compositor, sem publicar.
  • Prioridade: Média
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se apresentar exigir publicar antes.

RF-CUR-007: Só humano publica

  • Descrição: a ação de publicar exige sessão humana; nenhum agent ou automação publica.
  • Prioridade: Essencial
  • Atores: Operador
  • H1: Dado um rascunho pronto, Quando a publicação é acionada, Então ela só conclui a partir de uma sessão humana autenticada com escopo publish, E qualquer tentativa de publicação por agent ou token de serviço é recusada (RF-CMP-010).
  • Notas: viola-se este requisito se um relatório for publicado sem um humano no gatilho.

RF-CUR-008: Ajuste fino de apresentação por tokens

  • Descrição: o operador ajusta um conjunto fechado de 4 propriedades visuais como passo de índice sobre a escala de tokens (nunca CSS, HTML ou valor livre): paddingStep, gapStep (espaçamento entre objetos), fontSizeStep (dentro da escala do papel do bloco, um título nunca desce abaixo do corpo) e buttonAccentStep (um degrau dentro da rampa de acento do cliente já validada AA, nunca hex). O escopo é por bloco (style_overrides no contrato v3, Ap.2) e por relatório (theme_overrides, default da página); o bloco vence o relatório. É ajuste fino, não editor de layout: não muda posição, ordem, presença de bloco, nem cor ou valor fora da escala (RF-CUR-001 segue). O renderizador aplica o override por um parâmetro de passo com clamp aos limites da escala; o snapshot copia o override resolvido.
  • Prioridade: Alta
  • Atores: Operador
  • H1: Dado um bloco ou o relatório, Quando o operador ajusta uma das 4 propriedades, Então o valor muda em passos dentro da escala de tokens com clamp (nunca fora), E o publish é recusado se o buttonAccentStep resultar em contraste abaixo de AA (mesmo algoritmo do RF-REN-007) ou se o override estourar o layout no render real mobile ou PDF, E nenhuma chave fora do conjunto fechado é aceita (schema additionalProperties:false).
  • Notas: viola-se este requisito se um ajuste aceitar valor fora da escala, CSS ou HTML livre, mudança de posição, ordem ou presença de bloco, cor arbitrária, contraste abaixo de AA publicado, ou layout estourado publicado. Layout quebrado é estruturalmente impossível (só índices de escala fechada).

RF-CUR-009: Toda trava entrega a saída legal e tem escotilha registrada

  • Descrição: uma trava que só diz não constrói um produto insuportável, e um produto insuportável perde TODAS as guardas de uma vez, porque o operador constrói o caminho sombra (monta o relatório fora do sistema e cola o resultado). Três limites de usabilidade valem para toda guarda deste documento (RF-CMP-018, RF-CMP-021, RF-CMP-022, RF-CMP-024, RF-CMP-025, RF-CMP-026, RF-REN-013, RF-REN-014, RF-REN-015):
  1. A recusa entrega a saída legal, não só a negativa. Quem recusa a soma de alcance entre janelas oferece o pico com a janela nomeada; quem recusa a comparação entre janelas de larguras diferentes oferece o recorte equivalente ou a média diária; quem oculta a seção por histórico insuficiente diz quantos períodos faltam. A mensagem nomeia o que dá para fazer, na mesma tela.
  2. Existe override, com justificativa escrita e gravada. O operador pode seguir contra uma guarda não estrutural escrevendo o motivo; o motivo é obrigatório, fica no log de auditoria com autor, relatório e guarda, e a página publicada carrega a marca de que aquele item foi liberado por decisão humana. Sem escotilha o operador sai do sistema, e aí não sobra guarda nenhuma. As guardas ESTRUTURAIS (número sem proveniência, RF-CMP-006 e RF-CMP-007; publicar sem sessão humana, RF-PUB-003) não têm override, e a diferença entre os dois conjuntos é nominal, não julgamento na hora.
  3. As travas disparam cedo, na extração e na composição. O gate de publicação é a última rede, nunca o primeiro contato: descobrir no publish que meia dúzia de blocos são inválidos obriga a refazer o trabalho inteiro, e é assim que o operador aprende a evitar o sistema.

Limiar de alerta a acompanhar: quando as ressalvas de uma página passarem a ser mais numerosas que os achados dela, o relatório virou inútil por excesso de guarda, e o parâmetro de agência é que precisa ser revisto, não a guarda desligada.

  • Prioridade: Alta
  • Atores: Operador, Admin, sistema
  • H1: Dado um operador que esbarra numa guarda não estrutural durante a composição, Quando a guarda dispara, Então a mensagem nomeia a saída legal disponível para aquele caso, E o operador consegue seguir escrevendo uma justificativa obrigatória, E a justificativa fica gravada no log de auditoria com autor, relatório e guarda, E a página publicada carrega a marca de liberação humana naquele item, E a mesma tentativa contra uma guarda estrutural é recusada sem oferecer override.
  • Notas: viola-se este requisito quando uma guarda não estrutural recusa sem oferecer saída, quando o override existe sem justificativa obrigatória ou sem registro, quando o override alcança uma guarda estrutural, ou quando a única superfície em que as guardas disparam é o gate de publicação.

RF-CUR-010: O bloco inserido pelo operador sobrevive a qualquer reescrita do conjunto de blocos

  • Descrição: até aqui todo bloco do rascunho nascia de um lugar só, a composição (RF-CMP-004), e o caminho que grava a composição escreve o conjunto inteiro de blocks[] de uma vez. A inserção de criativo (RF-CUR-003) cria a segunda autoria: um bloco que o operador pôs ali com a própria mão. A partir do instante em que as duas autorias convivem no mesmo rascunho, "o que acontece com o bloco do operador quando a composição grava o conjunto de novo" deixa de ser detalhe de implementação e vira regra de produto, porque as duas respostas possíveis (sobrescrever tudo, ou preservar) são internamente coerentes e só uma é a que este produto quer. A resposta é: o bloco de autoria do operador SOBREVIVE. Ele declara a autoria no próprio contrato (Ap.2, valor reservado operator-inserted em tags), e todo caminho do sistema que reescreva o blocks[] de um rascunho preserva os blocos que carregam essa marca. O preço está declarado e é real: gravar a composição deixa de ser "substitua tudo" e passa a ser junção, e junção levanta duas perguntas, ordem e colisão. As duas ficam respondidas aqui, do jeito mais barato que existe, para que nenhuma delas dependa de julgamento na hora. Ordem: os blocos preservados voltam DEPOIS dos blocos propostos, na ordem relativa em que já estavam entre si; posicionar é gesto que o operador já tem (RF-CUR-001, reordenar) e nenhum mecanismo novo nasce daqui. Colisão: o id do bloco do operador é cunhado em espaço de nomes próprio, distinto do que a composição usa, de modo que a colisão passa a ser impossível por construção em vez de resolvida caso a caso. O que se perde, e a perda foi decidida e não sofrida: os blocos de autoria da composição são substituídos, e com eles a ordem que o operador porventura tivesse dado A ELES; preservar também essa ordem exigiria casar bloco antigo com bloco novo por conteúdo, que é julgamento que o sistema não tem, e a consequência de errar o casamento (texto de um período colado na posição de outro) é pior do que reordenar de novo. Comentário ancorado num bloco da composição que sumiu segue o tratamento que já existe (RN-005, orphaned); comentário ancorado num bloco do operador não orfana, porque o bloco não sai. Preservar não é passe livre: o bloco do operador atravessa exatamente os mesmos gates de publicação que qualquer outro (RF-CMP-007, RN-006, RF-CUR-011). E preservar não é irreversível: quem quiser o bloco fora do relatório o esconde (RF-CUR-001), que é o gesto que o sistema já tem e o único que tira bloco da página publicada. Nada disto alcança relatório já publicado, cujo snapshot é imutável (RF-PUB-001).
  • Prioridade: Essencial
  • Atores: Operador, sistema
  • H1: Dado um rascunho com pelo menos um bloco inserido pelo operador e pelo menos um bloco vindo da composição, Quando qualquer caminho do sistema grava de novo o conjunto de blocos daquele rascunho, Então todos os blocos de autoria do operador continuam no rascunho com o mesmo id, o mesmo props e a mesma marca de autoria, E eles aparecem depois dos blocos propostos, na ordem relativa em que já estavam entre si, E nenhum id gerado pela composição coincide com o id de um bloco do operador, E um comentário ancorado num bloco do operador não vira órfão.
  • Notas: viola-se este requisito quando uma gravação do conjunto de blocos apaga, renumera ou desmarca um bloco de autoria do operador; quando o bloco preservado volta sem a marca de autoria, e portanto não sobrevive à gravação seguinte; quando a preservação depende de o operador lembrar de alguma coisa em vez de acontecer sozinha; quando um id gerado pela composição colide com um id do operador; quando o bloco preservado escapa de algum gate de publicação por ter sido preservado; ou quando tirar um bloco preservado do relatório passa a exigir um mecanismo novo em vez de esconder (RF-CUR-001).

RF-CUR-011: Toda prosa editável pelo operador está sob a disciplina de número

  • Descrição: este produto existe para que nenhum número chegue à página sem dono declarado (RF-CMP-006, RF-CMP-007). A definição operacional de "número exibido" do Ap.8 nomeava dois lugares: o {dp} em campo estruturado de props e o token {{dp:id}} dentro de body_md. Prosa editável que não se chamasse body_md ficava, por essa redação, fora da varredura, e a caption do bloco media (Ap.3) é o primeiro caso concreto: uma legenda de criativo escrita à mão com um número dentro publicaria sem origem e sem violar requisito nenhum, que é uma porta aberta na única parede que o sistema inteiro sustenta. O perímetro passa a ser DERIVADO, e não uma lista de campos mantida à mão: está sob a disciplina de número todo campo de texto que a ficha do Ap.3 marca como editável pelo operador (RF-CUR-005), seja qual for o nome do campo e seja qual for o tipo de bloco. A forma derivada é parte da regra e não conveniência de redação: uma lista curada não reprova a ausência de nenhum item em particular, então um campo de texto editável novo passaria pela varredura com o gate verde sem ninguém perceber, que é exatamente como a caption chegou até aqui. Com a derivação, campo de texto editável novo entra no perímetro por nascer. O que NÃO muda, e precisa estar escrito para não ser endurecido por engano: a classificação do que é métrica continua a mesma do Ap.8 e é por natureza do dígito, nunca por campo. Data, ordinal e faixa de segmentação do briefing seguem livres, então uma legenda "Criativo 3" não reprova nada; o que reprova é o dígito que representa métrica, do tipo "340 leads". Trocar isso por "qualquer dígito reprova" na caption seria uma regra diferente e mais dura do que a que vale para body_md, e violaria este requisito em vez de cumpri-lo. O preço, declarado: a prosa do operador deixa de ser livre onde carrega métrica, e por isso a recusa tem de entregar a saída legal na mesma tela (RF-CUR-009 item 1), que aqui são duas e nominais: referenciar o número por {{dp:id}}, ou reescrever a frase sem a métrica.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dado um bloco cuja ficha do Ap.3 marca um campo de texto como editável pelo operador, Quando esse campo contém um dígito que representa métrica fora de um token {{dp:id}} resolvido, Então o gate de proveniência recusa a publicação nomeando o bloco e o campo, E a recusa nomeia as duas saídas legais, E a mesma frase com o número referenciado por {{dp:id}} resolvido publica, E uma data, um ordinal ou uma faixa de segmentação no mesmo campo não reprovam, E um campo de texto editável acrescentado ao Ap.3 depois desta regra passa a ser varrido sem que nenhuma lista precise ser editada.
  • Notas: viola-se este requisito quando um dígito que representa métrica é publicado num campo de texto editável sem {{dp:id}} resolvido; quando o perímetro da varredura é uma lista de campos escrita à parte em vez de derivada da ficha do Ap.3; quando algum campo de texto editável do Ap.3 fica de fora da varredura; quando a caption de media é varrida por critério mais duro que o de body_md, reprovando data, ordinal ou faixa; ou quando a recusa acontece sem nomear o campo e sem oferecer a saída legal.

RF-CUR-012: A identidade resolvida do relatório sai de um ponto único

  • Descrição: a cor e o logo de um relatório são resolvidos por UM ponto do sistema, e toda superfície que precisa da resposta chama esse ponto em vez de recalcular a cadeia por conta própria. O motivo é medido e não estético: enquanto a cadeia viveu só dentro do caminho de publicação, o preview do compositor não resolvia identidade nenhuma e exibia a cor padrão do renderizador, de modo que o operador curava o relatório numa cor e só descobria a cor real do cliente depois de publicar. Duas implementações da mesma precedência divergem por construção, e a divergência entre o que o operador vê e o que o cliente recebe é o defeito que este requisito existe para tornar impossível. A resposta resolvida declara a própria procedência, campo a campo: a cor diz se veio da sobreposição do relatório, da ficha do cliente, do padrão da agência ou de nenhum dos três, e o logo diz se veio da agência ou da sobreposição do relatório. Os dois marcadores são separados de propósito: colapsá-los faria "herdou da agência porque a ficha está vazia" e "o operador escolheu no relatório" virarem o mesmo valor, e é exatamente essa distinção que sustenta o aviso do RF-ADM-006.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado um mesmo estado de rascunho, Quando o preview do compositor e a publicação resolvem a identidade daquele relatório, Então as duas devolvem a mesma cor e o mesmo logo, E a resposta resolvida declara a procedência da cor entre os quatro valores nominais e a do logo entre os dois, E existe um único ponto no sistema que aplica a precedência de RN-020, E nenhuma superfície que exiba o relatório cai num valor padrão por não ter passado por esse ponto.
  • Notas: viola-se este requisito quando duas superfícies calculam a precedência por conta própria, ainda que concordem no dia em que foram escritas; quando uma superfície que exibe o relatório não passa pela resolução e cai num valor padrão; quando a procedência não é gravada; ou quando as procedências de cor e de logo são colapsadas num marcador só. O congelamento da identidade no snapshot e a rerresolução na republicação são RN-020 item 3.

RF-CUR-013: Renomear o relatório fora do assistente

  • Descrição: o título do relatório é o nome da peça no acervo da agência, e é renomeável enquanto ela é rascunho, de dentro do compositor, sem depender do assistente de criação. Antes desta regra o título só era escrito no assistente, que existe apenas no ato de criar; saindo dele o operador não tinha mais como renomear, embora o meio de gravação já existisse, com controle de acesso e registro de auditoria. Renomear é ato do operador sobre o rascunho e segue a mesma fronteira dos demais: depois de publicado o relatório não é renomeado, e mudar o nome exige nova publicação como qualquer outra mudança (RF-PUB-001).
  • Prioridade: Média
  • Atores: Operador
  • H1: Dado um relatório em rascunho aberto no compositor, Quando o operador o renomeia ali mesmo, Então o novo nome persiste e aparece na lista de relatórios e na ficha do cliente, E a alteração fica no log de auditoria com autor e alvo, E a mesma tentativa num relatório já publicado é recusada.
  • Notas: viola-se este requisito quando renomear só é possível no assistente, quando o nome novo não persiste ao recarregar a tela, ou quando um relatório publicado é renomeado sem nova publicação. Fato medido em 2026-08-09, registrado porque delimita o alcance deste requisito: o título do relatório não chega à página pública por caminho nenhum. O retrato publicado não o copia, e o título da página que o cliente final abre vem do card de compartilhamento (Ap.7) ou de um texto genérico quando ele não foi preenchido. Portanto renomear é hoje gesto de organização interna da agência e não mudança no que o cliente lê, e o título do relatório está fora do perímetro da disciplina de número (RF-CUR-011), que deriva dos campos editáveis dos BLOCOS. Se o título passar a chegar à página pública, ele entra no perímetro no mesmo ato; essa é decisão de produto e está registrada no Questionário de Elicitação ({Q9}).

C.8 Módulo reports-publish

RF-PUB-001: Snapshot imutável no publish

  • Descrição: publicar congela um snapshot: o payload de blocos, as metas vigentes e o branding são copiados no ato. Comentários, analytics e o arquivo por cliente seguem vivos; o snapshot não.
  • Prioridade: Essencial
  • Atores: Operador, sistema
  • 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.
  • Notas: viola-se este requisito se o planejado versus realizado de um relatório antigo mudar retroativamente ao editar as metas.

RF-PUB-002: Link público secreto seguro

  • Descrição: o link tem token de no mínimo 128 bits no caminho, é marcado noindex e protegido por rate-limit.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o token for fraco, a página for indexável, ou não houver rate-limit.

RF-PUB-003: Senha opcional

  • Descrição: o operador pode proteger o link com uma senha opcional (frase de 4 palavras); o default é sem senha.
  • Prioridade: Alta
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se um link com senha exibir conteúdo antes de validar a senha.

RF-PUB-004: Expiração opcional

  • Descrição: o operador pode definir uma data de expiração por relatório; o default é permanente.
  • Prioridade: Alta
  • Atores: Operador
  • 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 (RF-REN-010), E um link sem expiração continua servindo indefinidamente.
  • Notas: viola-se este requisito se um link expirado ainda servir o conteúdo.

RF-PUB-005: QR code do link

  • Descrição: o modal de publicação gera um QR code do link.
  • Prioridade: Média
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se o QR não resolver para o link publicado.

RF-PUB-006: Card de compartilhamento editável

  • Descrição: no publish, o operador revisa e edita o card de compartilhamento (og:title, og:description e capa brandada) com preview de como fica no WhatsApp.
  • Prioridade: Alta
  • Atores: Operador
  • 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 (Ap.7).
  • Notas: viola-se este requisito se o card publicado divergir do que foi revisado no preview.

RF-PUB-007: Envio manual pelo operador

  • Descrição: o sistema não envia o link no nascimento; o operador copia e manda pelo canal que quiser. A automação de envio pode vir depois.
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se o sistema mandar mensagem ao cliente no ato de publicar.

RF-PUB-008: Revogar link

  • Descrição: revogar um link faz o purge do cache e passa a servir uma página brandada de link revogado (resposta 410).
  • Prioridade: Essencial
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se, após revogar, o conteúdo continuar acessível por cache. Resolução token para snapshot é no-store; o asset imutável é cacheável mas some no processo LGPD.

RF-PUB-009: Republicar reusa o mesmo link sem versionar

  • Descrição: republicar sobrescreve o snapshot e mantém o mesmo link; não há biblioteca de versões da página.
  • Prioridade: Essencial
  • Atores: Operador
  • 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.
  • Notas: viola-se este requisito se republicar gerar um segundo link ou uma galeria de versões.

RF-PUB-010: Log de alterações somente interno

  • Descrição: republicar grava um log de alterações em linguagem humana (quem, o quê, onde, quando), visível somente para a agência; o cliente final não vê histórico de correção.
  • Prioridade: Alta
  • Atores: Operador, Admin
  • 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.
  • Notas: viola-se este requisito se o cliente final conseguir ver o log de alterações.

RF-PUB-011: Efeito colateral após a confirmação não contradiz o estado real

  • Descrição: a publicação confirma uma transação (o relatório passa a publicado, o instantâneo é gravado, o token público nasce) e, depois dela, ainda executa efeitos colaterais que não fazem parte do compromisso, como gerar a imagem do código de acesso. Um efeito colateral que roda DEPOIS da confirmação não pode transformar uma operação bem sucedida em erro na tela, porque o operador passa a acreditar no oposto do que o sistema fez: ele lê falha e o relatório está publicado e o link está no ar. Este requisito não decide onde o efeito colateral deve morar; ele proíbe o desfecho mentiroso, e as duas saídas legítimas são trazer o efeito para dentro do compromisso ou rebaixar a falha dele a aviso que declara o estado verdadeiro.
  • Prioridade: Alta
  • Atores: Operador (é quem lê a mensagem e decide o que fazer); sistema.
  • Pré-condições: a operação abre transação e confirma antes de executar o efeito colateral.
  • Entrada: a falha do efeito colateral posterior à confirmação.
  • Saída: resposta de sucesso com aviso que nomeia o que não pôde ser feito, ou falha real quando nada foi confirmado.
  • H1: Dado uma operação cuja transação já foi confirmada, Quando um efeito colateral executado depois dela falha, Então a resposta ao operador declara o estado VERDADEIRO do sistema (a operação aconteceu) e nomeia em separado o que não pôde ser feito, E a resposta nunca é o erro genérico da operação inteira, E nenhum operador recebe falha de uma operação cujo compromisso foi confirmado, E o efeito colateral que for indispensável ao sucesso é executado DENTRO do compromisso, e não depois dele.
  • Notas: o requisito é violado quando o operador recebe erro de uma operação que aconteceu, e a verificação é por comparação entre o que a resposta afirma e o que ficou gravado, nunca por impressão. A cláusula final existe para que a correção não possa ser "engolir a falha em silêncio": um efeito colateral indispensável não vira aviso, vira parte do compromisso; o que pode virar aviso é o acessório. Cruza com o RF-PUB-005, que exige a imagem do código de acesso no momento da publicação, e com o RF-PUB-009, que faz a republicação reusar o mesmo link: é por causa do RF-PUB-009 que uma segunda tentativa hoje conclui em vez de ser recusada, o que reduz o dano mas não desfaz a mentira da primeira resposta. Ver o Roadmap, Onda DEF, card {DEF-01}, e o Plano de Testes, seção Alfa.

C.9 Módulo reports-render

RF-REN-001: Contrato de bloco v3

  • Descrição: todo conteúdo é blocks[], cada bloco com id estável, type, hidden opcional, tags opcionais e props. O id é âncora de comentário e de analytics e nunca é regenerado. Publicar remove os blocos ocultos do snapshot. Seções semânticas (Resumo, Vitórias, Problemas, Recomendações) são tags, não tipos (Ap.2).
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um id de bloco mudar entre publicações (quebraria a âncora de comentário) ou um bloco oculto entrar no snapshot.

RF-REN-002: Registro declarativo de blocos e tolerância a tipo desconhecido

  • Descrição: os tipos de bloco vivem num registro declarativo (componente, schema de validação e defaults). Um tipo desconhecido não quebra o link: o leitor ignora em silêncio, o compositor falha alto.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um tipo desconhecido derrubar a página do cliente.

RF-REN-003: Versão de schema com conversão em leitura

  • Descrição: o payload carrega schema_version; o leitor converte versões antigas em leitura por funções puras, sem reescrever o armazenado.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o leitor reescrever o snapshot armazenado ao convertê-lo.

RF-REN-004: Corpo de texto sanitizado

  • Descrição: o corpo de texto dos blocos é Markdown sanitizado com allowlist na renderização, nos consumidores todos (leitor, preview, PDF). Corrige a abertura de XSS herdada do formato anterior.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um script no corpo de um bloco executar em qualquer superfície.

RF-REN-005: Package renderizador único em TypeScript puro

  • Descrição: o renderizador é um package de funções puras que emitem HTML, servindo os três consumidores: o leitor público, o preview do compositor e o PDF. O React não reimplementa os blocos.
  • Prioridade: Essencial
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o preview ou o PDF reimplementarem blocos por fora. Maior alavanca de reuso do projeto.

RF-REN-006: Leitor público isolado somente leitura

  • Descrição: o leitor público serve o snapshot sob a role reports_reader, com GRANT de SELECT apenas nas três tabelas que ele precisa (report, snapshot e comment), não no schema reports inteiro (SELECT no schema todo exporia rascunho, artefato e metas). A role não escreve nada, não alcança nenhuma outra tabela do próprio schema nem qualquer outro schema, e não faz query pesada no caminho público (RN-011, RNF-SEG-001).
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado o serviço do leitor público, Quando ele resolve um link, Então usa a role reports_reader com SELECT só em report, snapshot e comment, E não tem permissão para escrever, nem para ler report_draft, artifact, report_goal ou qualquer outro schema do Hub.
  • Notas: viola-se este requisito se a role do leitor puder escrever, ler uma quarta tabela do schema reports, ou alcançar outro schema. Se o raio de exposição incomodar, extrair só o leitor é barato e reversível.

RF-REN-007: Tokens visuais por cliente e cor do cliente como acento no tema escuro

  • Descrição: a página usa um conjunto de tokens --r-* por cliente, aplicando as cores da ficha de identidade. O leitor público é escuro sempre (fundo fixo escuro do sistema de resultados); a cor do cliente entra como ACENTO (realce, título, destaque), não como fundo. Se o acento do cliente tem contraste ruim sobre o fundo escuro, a luminância é ajustada por um algoritmo determinístico (em espaço HSL) até atingir contraste AA, o mesmo padrão do chrome da plataforma-mãe. O PDF é claro (um segundo conjunto de tokens, RF-REN-008).
  • Prioridade: Alta
  • Atores: sistema
  • 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 com a cor do cliente como acento sobre o fundo escuro ajustada por algoritmo até AA, E trocar a paleta de um cliente não afeta a página já publicada do outro (RF-PUB-001).
  • Notas: viola-se este requisito se as cores de um cliente vazarem para a página de outro, se a cor do cliente for usada como fundo em vez de acento, ou se um acento de contraste ruim ficar abaixo de AA sem o ajuste de luminância.

RF-REN-008: PDF condicional reusando o gerador da plataforma-mãe

  • Descrição: o PDF é gerado reusando o conversor de HTML da plataforma-mãe (padrão de HTML com assets embutidos), em tema claro com contraste AA. Se esse gerador não estiver disponível, o PDF não existe.
  • Prioridade: Média
  • Atores: sistema, Operador
  • 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.
  • Notas: viola-se este requisito se o PDF sair com contraste abaixo de AA ou por um caminho de renderização diferente do package. O HTML do PDF não carrega scripts de chrome.

RF-REN-009: Assets por URL assinada e base64 pré-buscado no PDF

  • Descrição: o leitor usa URL assinada para os assets; o gerador de PDF recebe os assets em base64 pré-buscado.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o leitor expuser URL não assinada, ou o gerador de PDF fizer fetch de rede na geração.

RF-REN-010: Estados brandados de link inválido

  • Descrição: link revogado ou expirado é um estado de primeira classe do leitor: uma página brandada (com a marca da agência) dizendo "peça um novo link à agência".
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o estado revogado ou expirado cair numa página de erro genérica sem marca, ou vazar conteúdo.

RF-REN-011: Portal logado do cliente final (leitura, opcional)

  • Descrição: além do link direto, o Contact autenticado pela sessão do Hub tem um portal de leitura que lista os relatórios publicados para os Accounts a que ele pertence, organizados de forma simples, com atalho para abrir cada relatório e ver os comentários recentes. É só leitura: o cliente não compõe, não publica, não administra nada. Reusa o login base do Hub (o Reports não cria conta própria, RF-ADM-008).
  • Prioridade: Média
  • Atores: Cliente final
  • 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 a ele.
  • Notas: viola-se este requisito se o portal listar relatório de outro cliente, ou oferecer ao Contact qualquer ação além de ler e comentar. O escopo por Contact para Account é verificado na aplicação (não por company_id, RNF-SEG-001). É a tela cliente-painel do mockup.

RF-REN-012: Cobertura nominal dos tipos de bloco herdados dos relatórios reais

  • Descrição: o registro declarativo de RF-REN-002 contém, no mínimo, os dezesseis tipos nominados do Ap.3, e não um subconjunto deles: hero, kpis, funnel_simple, stat_cards, video_retention, rankings, data_table, insights, gap, comparison, media, section_intro, reliability, line_chart, donut e action_plan. A lista é nominal e fechada por decisão: não é "os tipos existentes mais alguns", porque uma enumeração ilustrativa não reprova a ausência de nenhum item em particular. Cinco desses tipos (section_intro, reliability, line_chart, donut, action_plan) não são hipótese de produto: cada um está em uso em relatórios já entregues a cliente, e sem eles o sistema não reproduz o relatório que a operação entrega hoje, o que contraria diretamente o critério de adoção (A.7). A tolerância a tipo desconhecido de RF-REN-002 continua valendo para tipos futuros, e não é substituta desta cobertura: um tipo desta lista que caia no caminho de tipo desconhecido é falha, não degradação graciosa.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado um documento cujos blocos percorrem os dezesseis tipos nominados do Ap.3, Quando o renderizador processa esse documento, Então cada um dos dezesseis resolve para o seu componente próprio, E nenhum deles cai no caminho de tipo desconhecido, E a ausência de qualquer um dos dezesseis no registro é detectada por conferência do registro contra a lista nominal do Ap.3.
  • Notas: viola-se este requisito quando qualquer um dos dezesseis tipos falta no registro, quando um deles renderiza pelo caminho de tipo desconhecido, ou quando a conferência do registro passa a comparar contra uma lista escrita à parte em vez do Ap.3. Estado medido em 2026-08-02: o registro tem onze dos dezesseis; faltam section_intro, reliability, line_chart, donut e action_plan, cobertos pelos cartões {F17-01} a {F17-04} e {F17-09} do Roadmap. comparison e media estão no registro e são tratados no Ap.3 com a nota honesta sobre o grau de confirmação por uso de cada um.

RF-REN-013: Abertura singular e divisor de seção repetível

  • Descrição: hero é SINGULAR no documento (no máximo um), section_intro é repetível. O motivo é semântico e não estético: o hero carrega period e source, que declaram o que o documento inteiro cobre; dois hero significariam dois períodos e o documento se contradizendo sobre a própria janela. section_intro existe justamente para dividir o documento em partes sem repetir essa declaração, e por isso não tem period nem source (Ap.3). A regra vale nas três superfícies: o compositor não emite o segundo hero, a curadoria não consegue duplicar um hero, e o gate de publicação recusa um documento com dois.
  • Prioridade: Essencial
  • Atores: sistema, Operador
  • H1: Dado um documento que já tem um bloco hero, Quando a composição, a curadoria ou a API tentam acrescentar um segundo hero, Então a operação é recusada nas três superfícies, E o gate de publicação recusa um documento que contenha dois blocos hero, E acrescentar um segundo section_intro continua sendo permitido sem restrição de quantidade.
  • Notas: viola-se este requisito quando um documento é publicado com dois blocos hero, quando o compositor emite o segundo, ou quando section_intro ganha campo de período ou de fonte (isso reintroduz o conflito de janela por outra porta).

RF-REN-014: Seção com histórico insuficiente se oculta

  • Descrição: um bloco cuja leitura exige N períodos declara min_periods (Ap.2, Ap.3). Quando o relatório tem menos de N períodos de dado disponível para aquela série, o bloco NÃO é renderizado com o que houver: ele se oculta, e a ausência entra como lacuna declarada no bloco gap com o motivo (histórico insuficiente, N exigido contra N disponível). Seção sem massa de dados não fica curta, fica falsa: um gráfico de linha com dois pontos desenha uma tendência que o dado não sustenta, e um ranking sobre 15 dias ordena ruído. Os defaults nascem por tipo (line_chart 3, comparison com base de período anterior 2, rankings conforme parâmetro de agência) e são admin editáveis.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado um bloco line_chart com min_periods igual a 3 e um relatório com apenas 2 períodos de dado para aquela série, Quando o relatório é composto, Então o bloco não aparece na página, E a lacuna correspondente aparece no bloco gap declarando o histórico exigido e o disponível, E o mesmo bloco com 3 períodos disponíveis renderiza normalmente.
  • Notas: viola-se este requisito quando um bloco com histórico abaixo do mínimo é renderizado assim mesmo, quando ele se oculta sem deixar a lacuna declarada (ocultar em silêncio é a mesma desonestidade que RF-CMP-003 fecha, pela porta oposta), ou quando min_periods vira constante em código sem edição pelo admin.

RF-REN-015: Vocabulário único de não medido

  • Descrição: a ausência de medição tem um significado e uma representação em todo o sistema, listados nominalmente: valor NULO (nunca zero e nunca string vazia), render interrompido (traço na série, etapa tracejada no funil, célula vazada na tabela, e em nenhum caso uma queda desenhada até zero), e motivo declarado uma vez no bloco gap. Os três jeitos que hoje convivem (funnel_simple.steps[].dashed, o bloco gap, e o ponto nulo de line_chart) passam a ser três renderizações do MESMO conceito, com a mesma origem no dado, e não três conceitos. O que este requisito PROÍBE nominalmente: gravar ou exibir não medido como zero em qualquer bloco; representar não medido por string vazia, hífen ou texto digitado à mão; e introduzir uma quarta representação num tipo de bloco novo.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado um relatório em que uma etapa do funil, um ponto de uma série e uma célula de tabela são todos não medidos, Quando a página é renderizada, Então os três aparecem como interrupção e nenhum deles aparece como zero, E os três motivos aparecem no bloco gap, E nenhum ponto de dado gravado para eles carrega o valor zero.
  • Notas: viola-se este requisito quando um não medido é gravado ou exibido como zero, quando aparece como texto digitado à mão em vez do estado do dado, ou quando um tipo de bloco novo inventa a quarta representação. O caso que a regra impede é o leitor concluir que o desempenho foi nulo quando a medição simplesmente não existiu.

RF-REN-016: Funil canônico de quatro etapas, com vocabulário vindo do modelo de venda

  • Descrição: quando o cliente tem modelo de venda declarado (RF-ADM-023), o funil daquele relatório é o funil CANÔNICO: exatamente quatro etapas, sempre nesta ordem, Atenção, Interesse, Consideração e Ação. A estrutura é uma só nos dois modelos de venda; o que troca é apenas o rótulo de cada etapa, e a tabela de rótulos por modelo é o Ap.10. Isto é ADITIVO ao tipo funnel_simple e não substitui nada: relatório de cliente SEM modelo de venda declarado continua usando o funil livre, com o número de etapas e os rótulos que o operador montar, exatamente como faz hoje, e nenhum relatório já publicado muda. O bloco grava o modelo de venda que o produziu, copiado no ato da composição, para que o snapshot seja auto-suficiente e o rótulo publicado não mude se o cadastro do cliente mudar depois. Nenhum tipo de bloco novo nasce aqui: o funil canônico é o funnel_simple com o perfil canônico aplicado, e a camada de resultado é stat_cards alimentada pelo catálogo do Ap.10. A lista nominal de dezesseis tipos de RF-REN-012 continua fechada e inalterada.
  • Prioridade: Essencial
  • Atores: sistema, Cliente final
  • H1: Dado um cliente com modelo de venda direta, Quando o funil canônico dele renderiza, Então aparecem exatamente quatro etapas na ordem Atenção, Interesse, Consideração e Ação com os rótulos diretos do Ap.10, E o mesmo funil para um cliente consultiva tem as mesmas quatro etapas na mesma ordem com os rótulos consultivos, E um cliente sem modelo declarado renderiza o funil livre com o número de etapas que o operador montou, E o rótulo publicado num snapshot não muda quando o modelo de venda do cliente é alterado depois, E o registro de tipos de bloco continua com os dezesseis do Ap.3.
  • Notas: viola-se este requisito quando o funil canônico rende número de etapas diferente de quatro, quando a ordem muda, quando um rótulo é digitado à mão em vez de vir do Ap.10, quando o funil livre deixa de funcionar para cliente sem modelo declarado, quando o rótulo de um snapshot publicado muda por alteração de cadastro, ou quando um tipo de bloco novo é criado para isto.

RF-REN-017: Métrica de resultado interna não atravessa para a página do cliente

  • Descrição: parte da camada de resultado do Ap.10 é dado comercial da agência e não do cliente: o fee cobrado, a margem de contribuição e os lucros calculados a partir deles. Esses números existem no sistema porque a agência precisa deles para gerir, e publicá-los por descuido na página que o cliente abre é um vazamento comercial que nenhum gate de honestidade pega, porque o número é verdadeiro. Por isso cada entrada do Ap.10 carrega uma visibilidade, valor fechado de dois: interna (aparece ao operador e ao admin, nunca no snapshot publicado) e cliente (pode aparecer na página publicada). O default de cada métrica está no Ap.10, e o default das métricas de fee, margem e lucro é interna, porque a guarda que falha aberta não é guarda. A visibilidade é parâmetro de agência (ART-CFG) e pode ser afrouxada por decisão explícita do admin da agência, nunca por composição e nunca por operador em página. O corte acontece no PUBLISH: uma métrica interna não entra no snapshot.payload, e não basta escondê-la no render, porque o snapshot é servido a quem tem o link.
  • Prioridade: Essencial
  • Atores: Admin, sistema, Cliente final
  • H1: Dado um relatório cuja camada de resultado exibe ao operador o fee da agência e o lucro operacional, Quando o operador publica, Então nenhuma das duas métricas está presente no snapshot.payload, E a página pública não as exibe nem no HTML nem em metadado, E as duas continuam visíveis ao operador e ao admin no compositor, E quando o admin da agência marca uma delas como cliente a métrica passa a entrar no snapshot dos relatórios publicados a partir dali, E nenhum snapshot já publicado muda.
  • Notas: viola-se este requisito quando uma métrica interna chega ao snapshot.payload, quando ela é apenas ocultada no render em vez de removida do snapshot, quando a visibilidade é decidida por relatório em vez de por parâmetro de agência, quando o operador consegue afrouxá-la, ou quando uma métrica nasce sem visibilidade declarada no Ap.10.

C.10 Módulo reports-comment

RF-COM-001: Comentário só de usuário logado

  • Descrição: comentar exige login (Contact do Hub). O leitor público segue somente leitura; comentar não é escrita no caminho do leitor.
  • Prioridade: Essencial
  • Atores: Cliente final, Operador
  • 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.
  • Notas: viola-se este requisito se um comentário anônimo for gravado. Resolve identidade e spam.

RF-COM-002: Endpoint de comentário fora do leitor

  • Descrição: o comentário é gravado por um endpoint da API do aplicativo do operador do Reports (não do leitor público, não do Hub), autenticado por sessão Contact, para não abrir escrita no caminho somente-leitura. O leitor público resolve e serve; o comentário é aceito por outra superfície, com a sua própria autenticação.
  • Prioridade: Essencial
  • Atores: sistema
  • H1: Dado o serviço do leitor público, Quando um comentário é enviado, Então a escrita acontece por um endpoint da API do app do operador do Reports, autenticado por sessão Contact, E a role do leitor público segue sem qualquer permissão de escrita (RF-REN-006).
  • Notas: viola-se este requisito se o caminho do leitor público ganhar permissão de escrita para aceitar comentário, ou se a escrita do comentário for hospedada no leitor.

RF-COM-003: Comentário ancorado em bloco

  • Descrição: cada comentário é ancorado a um bloco pelo id estável e guarda o trecho citado (excerpt obrigatório).
  • Prioridade: Alta
  • Atores: Cliente final, Operador
  • 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.
  • Notas: viola-se este requisito se um comentário for gravado sem âncora de bloco e trecho.

RF-COM-004: Comentário órfão em republicação

  • Descrição: se o bloco ancorado sumir numa republicação, o comentário permanece listado com o trecho citado e a marca "referente a versão anterior"; não há versionamento de página.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um comentário sumir junto com o bloco, ou forçar versionamento de página. Resolve a colisão entre comentário ancorado e republicação sem versão.

RF-COM-005: Resolver comentário notifica o autor

  • Descrição: resolver um comentário fecha o loop: notifica quem apontou e marca o comentário como resolvido.
  • Prioridade: Média
  • Atores: Operador
  • 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".
  • Notas: viola-se este requisito se resolver um comentário não notificar o autor.

RF-COM-006: Atalho de magic link para comentar

  • Descrição: a página oferece ao cliente sem sessão um atalho para receber acesso por magic link (função do Hub), permitindo comentar sem senha.
  • Prioridade: Média
  • Atores: Cliente final
  • 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.
  • Notas: viola-se este requisito se o cliente precisar de senha própria do Reports para comentar. O provisionamento do Contact é pré-requisito.

C.11 Módulo reports-analytics

RF-ANL-001: Pipeline de eventos próprio fora do leitor

  • Descrição: as métricas de audiência vêm de um pipeline de eventos próprio, separado do caminho de renderização do leitor.
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se a analytics escrever pelo caminho do leitor (RF-REN-006).

RF-ANL-002: Visualizações e último acesso

  • Descrição: o analytics começa com número de visualizações e último acesso por link; scroll e tempo por bloco ficam diferidos.
  • Prioridade: Alta
  • Atores: Admin, Operador
  • 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.
  • Notas: viola-se este requisito se as visualizações ou o último acesso não refletirem os acessos.

RF-ANL-003: Exclusão das sessões da plataforma e do modo apresentar

  • Descrição: a contagem de visualizações mostrada ao cliente conta apenas as leituras do link público feitas de fora da plataforma. Um acesso vindo de navegador com sessão ativa da plataforma-mãe não conta, seja ele da agência dona do relatório ou de outra agência do ecossistema, porque o único sinal disponível é do domínio da plataforma e não carrega identificador de empresa. O modo apresentação também não conta. A finalidade é que a reunião da própria agência não envenene o engajamento medido do cliente.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado um acesso ao link público a partir de navegador com sessão ativa da plataforma, Quando as métricas são contadas, Então esse acesso não entra na contagem de visualizações do cliente; E dado o mesmo link aberto sem sessão da plataforma, Quando as métricas são contadas, Então esse acesso ENTRA na contagem; E dado o cliente que abriu sessão de comentário pelo próprio link de acesso, Quando ele lê o relatório, Então a leitura dele ENTRA na contagem; E o modo apresentação não emite evento de visualização.
  • Notas: o requisito proíbe nominalmente três fatos, e apenas estes: contar um acesso vindo de navegador com sessão da plataforma; descontar um acesso sem sessão da plataforma; descontar a leitura do cliente que entrou pela sessão de comentário. A exclusão é deliberadamente mais ampla que "a própria agência", e o texto diz isso porque o sistema faz isso: o leitor público é anônimo e o único sinal disponível é um indicador opaco de domínio raiz que afirma apenas "existe sessão da plataforma neste navegador", sem identidade e sem identificador de empresa, então ele exclui igualmente o operador de outra agência. A alternativa, levar o identificador de empresa até o navegador do leitor público, foi recusada: colocaria um identificador de tenant num cookie lido por serviço anônimo, contra a postura de isolamento do RNF-SEG-001, para comprar uma distinção que nenhuma superfície deste sistema consome, já que o painel de uma agência jamais agrega dados de outra (RF-ANL-006) e a leitura de relatório alheio depende do token secreto (RNF-SEG-001), ou seja, não é fluxo legítimo. O efeito da amplitude é sempre para menos: a exclusão nunca infla a audiência do cliente. A terceira cláusula do H1 existe porque essa amplitude tem um limite que precisa ser vigiado: a sessão de comentário do cliente é servida pelo próprio leitor e não cria o sinal da plataforma, e se algum dia passasse a criá-lo a audiência real do cliente seria zerada em silêncio, que é o oposto do que este requisito protege.

RF-ANL-004: Anônimo agregado e identificador opaco

  • Descrição: o analytics de anônimo é agregado; o identificador do visitante é opaco (watermark), nunca o email cru no HTML público. Logado aparece como nome; deslogado como "visitante".
  • Prioridade: Alta
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se um dado pessoal identificável de visitante aparecer no HTML público.

RF-ANL-005: Origem por referrer

  • Descrição: a origem do acesso é inferida pelo referrer (impreciso), dado que o link é único (não há URL por canal).
  • Prioridade: Média
  • Atores: sistema
  • 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.
  • Notas: viola-se este requisito se o sistema afirmar um canal de origem que não veio do referrer.

RF-ANL-006: Painel de analytics por agência

  • Descrição: o admin da agência vê um painel de analytics agregado dos relatórios da agência (visualizações, último acesso, comentários não lidos, duração de produção).
  • Prioridade: Média
  • Atores: Admin
  • 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.
  • Notas: o Super Admin precisa de contexto de empresa ATIVO para ver os dados de uma agência; em modo global o painel vem vazio, e isso é o comportamento correto. A regra de acesso ao relatório é por agência e não abre exceção para o papel que enxerga todas as empresas, porque uma exceção assim seria uma segunda regra de acesso ao mesmo dado, e regra de acesso duplicada é como o isolamento entre agências morre (RF-ADM-001, RN-009). Está escrito aqui porque o painel vazio em modo global parece defeito para quem chega sem saber, e já custou tempo de investigação. Viola-se este requisito se o painel passar a agregar dados de várias agências para o papel global, não se ele vier vazio sem contexto.
  • Notas: viola-se este requisito se o painel de uma agência mostrar dados de outra.

4. Requisitos Não-Funcionais

D.1 Segurança

RNF-SEG-001: Isolamento cross-tenant e o caminho de leitura pública

  • Acesso do operador e do admin (usuários de agência autenticados): RLS RESTRICTIVE por company_id em todas as tabelas do schema reports, com REVOKE ALL no schema novo e helpers de escopo com search_path fixado em funções SECURITY DEFINER; nenhuma FK cross-schema contorna esse escopo.
  • Leitor público (anônimo, sem company_id de sessão): usa a role reports_reader, com GRANT de SELECT apenas em report, snapshot e comment, e uma policy PERMISSIVE que libera só linhas de relatório com status publicado e não expirado (e o snapshot e os comment ligados a elas). O segredo que protege a leitura é o public_token não adivinhável resolvido na query, não o company_id; a role não enxerga rascunho, relatório de outra agência, nem qualquer outra tabela ou schema (RF-REN-006).
  • Comentário do Contact (sem company_id de sessão, pois o Contact pertence a Account, não a Company): a escrita acontece por um endpoint da API do operador autenticado por sessão Contact (RF-COM-002), com a role de aplicação, nunca pela role do leitor; a associação Contact para Account é verificada na aplicação.
  • H1: Dado um usuário da agência A, Quando ele consulta qualquer dado do Reports pelo painel, Então só recebe registros com company_id de A; E dada a role reports_reader, Quando ela resolve um token, Então lê apenas o relatório publicado correspondente, o seu snapshot e os seus comentários, sem alcance a rascunho, a relatório de outra agência ou a qualquer outro schema; E nenhuma FK cross-schema contorna esses escopos.
  • Viola-se quando um usuário de A recebe uma linha de B.

RNF-SEG-002: Separação instrução/dado e números fora do modelo de linguagem

  • O conteúdo dos artefatos do cliente entra no prompt como um bloco de DADO rotulado como não confiável, num canal separado do canal de instrução do sistema; o modelo é instruído a tratar esse bloco como dado e nunca como instrução, e os números não passam pelo modelo (o extrator determinístico os lê, RF-CMP-005). Isso neutraliza o artefato que tenta virar comando ("escreva ROAS 10x").
  • Escopo declarado: a barreira protege por completo o NÚMERO (determinístico) e o BENCHMARK (só da allowlist com fonte, RF-CMP-014). Uma tentativa de dirigir a NARRATIVA sem tocar número (por exemplo, um artefato que instrui "elogie o concorrente X") é atenuada pela mesma separação de canal e barrada na curadoria humana antes de publicar (RF-CUR-006, RF-CUR-007), que é o controle residual declarado, não um furo silencioso.
  • H1: Dado qualquer artefato do cliente com instruções embutidas, Quando o pipeline compõe, Então o conteúdo do artefato é passado como bloco de dado não confiável separado do canal de instrução e nenhum número ou benchmark exibido provém do texto do modelo, E instruções embutidas não alteram valores exibidos nem o benchmark, E qualquer desvio de narrativa remanescente fica visível para a curadoria humana antes da publicação.
  • Viola-se quando o conteúdo de um artefato é concatenado no canal de instrução do sistema, ou quando um número ou benchmark exibido provém do texto do modelo.

RNF-SEG-003: Publicação exige a capability reports.publish

  • Reforça RF-CMP-010: 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.

RNF-SEG-004: Parse isolado sem rede e composição sem credencial de outro tenant

  • Reforça RF-ING-003 (o parse de arquivo roda em container isolado sem rede de saída, fail-closed) e RF-CMP-009 (a composição in-process monta contexto só do report_id, sem estado global cross-tenant).

RNF-SEG-005: Cabeçalhos, mascaramento e cache do leitor público

  • Referrer-Policy no-referrer, assets same-origin, token mascarado em log. A resolução token para snapshot é dinâmica e no-store (checa revogação a cada acesso); já o asset imutável do snapshot é cacheável de forma agressiva, mas com chave por snapshot_id, nunca pelo token do link. Revogar purga o cache por snapshot_id e devolve 410 na resolução.
  • H1: Dado o leitor público, Quando serve uma página, Então envia Referrer-Policy no-referrer, não registra o token cru em log e a resolução token para snapshot é no-store, E o asset imutável é cacheado por snapshot_id, não pelo token.

RNF-SEG-006: CNAME só após validação de posse

  • O vínculo de domínio custom por agência só ativa após validação de posse por registro TXT; o desvínculo remove o CNAME.

RNF-REN-001: A paginação do PDF é por NATUREZA DO BLOCO, nunca uniforme

  • A regra de quebra de página classifica cada tipo de bloco em duas naturezas e trata cada uma de um jeito. Pequeno e indivisível (hero, kpis, stat_cards, funnel_simple, donut, media, reliability): NÃO quebra, o bloco inteiro vai para a página seguinte se não couber. Longo e divisível (data_table, video_retention, rankings, insights em série, action_plan): QUEBRA entre linhas ou itens, com o cabeçalho repetindo na página nova.
  • Regra invariante, qualquer que seja a natureza: uma página nova nunca começa por um número solto sem o rótulo dele. Ou o bloco inteiro migra, ou o cabeçalho se repete. É essa invariante, e não a contagem de páginas, que define se a paginação está certa.
  • Por que uniforme NÃO serve, e isto está escrito porque já foi tentado nas duas pontas. Sem regra nenhuma, o gerador corta bloco no meio e a página começa por um número sem rótulo, que é a mesma classe de defeito de rótulo que RF-CMP-025 fecha, agora produzida pela diagramação. Com a regra uniforme de "nunca quebrar", todo bloco longo é empurrado inteiro para a página seguinte: a primeira página fica com cerca de 60 por cento em branco e o documento incha (medido: 11 páginas, contra 8 na regra seletiva, para o mesmo conteúdo). As duas pontas do uniforme falham, por motivos opostos, e é por isso que a regra é por natureza. Quem for "simplificar" isso depois precisa saber que o simples já foi tentado.
  • H1: Dado um documento de 20 ou mais blocos misturando as duas naturezas, Quando o PDF é gerado, Então nenhum bloco indivisível aparece cortado entre páginas, E todo bloco divisível que quebra repete o cabeçalho na página nova, E nenhuma página começa por um número sem o rótulo dele, E nenhuma página tem sobra vertical grande com conteúdo pendente que caberia nela.
  • Notas: viola-se este requisito com corte no meio de bloco indivisível, com página começando por número solto, com bloco divisível quebrado sem repetir cabeçalho, e também com página majoritariamente vazia enquanto há conteúdo pendente. A verificação é visual, olhando o arquivo gerado, porque as duas falhas conhecidas (corte e inchaço) não aparecem em contagem de páginas nem em teste de estrutura: a de inchaço só foi vista porque alguém abriu o PDF.

Armadilha de ambiente conhecida, registrada porque toda sessão futura tropeça nela. Rodar a suíte de testes da plataforma-mãe sem apontar a variável que localiza o pacote deste módulo faz o processo usar o checkout compartilhado, que costuma estar numa fase antiga do trabalho. O resultado são falhas 100% falsas que parecem defeito de produção: o teste exercita uma versão do pacote que não é a que está sendo desenvolvida, e o erro que aparece não existe no código atual. O sintoma é traiçoeiro porque a falha é plausível e o instinto é procurar o defeito no código novo. Antes de investigar qualquer falha de suíte cruzada entre os dois lados, confirme primeiro qual versão do pacote o processo carregou.

RNF-MANUT-007: Guarda ou interpretador que percorre lista curada só protege o que alguém lembrou de listar

  • Descrição: quando uma verificação, um gate ou um interpretador decide o que olhar a partir de uma LISTA escrita à mão, ele fica cego para tudo que não está nela, e a cegueira não aparece como erro: aparece como aprovação. O caso passa, a página renderiza, o teste fica verde, e ninguém procura, porque nada falhou. Este requisito exige que toda verificação desse tipo declare explicitamente como trata o caso NÃO listado, e que a escolha seja uma de duas, nunca o silêncio: ou a lista é derivada do próprio conjunto que ela deveria cobrir (e então não pode divergir dele), ou o item fora da lista é tratado como falha e não como ausência. O que este requisito PROÍBE nominalmente: uma lista curada cujo caso não previsto passe em silêncio; e uma verificação cuja cobertura seja afirmada sem que exista o controle negativo de um item fora da lista.
  • Prioridade: Alta
  • Atores: sistema, quem constrói a verificação
  • H1: Dada uma verificação que decide o que olhar a partir de uma lista, Quando um item fora da lista é submetido a ela, Então ou ele é tratado como falha, ou a lista é derivada do conjunto e portanto não pode omiti-lo, E existe um controle negativo que exercita justamente o item fora da lista, E nenhum caso não previsto passa em silêncio.
  • Notas: viola-se este requisito quando um item não listado é aprovado por omissão, quando a lista é mantida à mão em paralelo ao conjunto que ela cobre, ou quando a cobertura é afirmada sem controle negativo. A família é a mais numerosa deste projeto e foi catalogada ao longo de 2026-08-03 e 2026-08-04, sempre com a mesma assinatura: o gate de proveniência que enxergava só alguns tipos de bloco e deixava o plano de ação publicar número cru; o registro de tipos de bloco, que por isso é lista NOMINAL e FECHADA conferida contra o apêndice e não uma lista à parte (RF-REN-012); os nomes ausentes do contrato público do pacote, que eram indefinidos para o consumidor externo (RNF-MANUT-006); a rota de caminho fixo engolida pela dinâmica, que responde erro sem que ninguém veja (RNF-MANUT-004); e o interpretador da página de perguntas, que reconhecia apenas o formato antigo de cabeçalho e por isso não renderizava o corpo de um documento escrito no formato mais novo, enquanto os outros documentos continuavam certos justamente por usarem o formato coberto. Em todos, o defeito era invisível porque a coisa não listada não reclama.

RNF-MANUT-006: Peça exposta a consumidor externo está no contrato público, e isso se verifica por máquina

  • Descrição: o pacote do módulo tem um índice único que é o contrato público dele, e todo consumidor externo enxerga apenas o que esse índice expõe, nunca o módulo de origem. Uma função que existe, é testada por unidade e é chamada de dentro do próprio pacote pode, mesmo assim, ser inalcançável para quem está de fora, e o sintoma disso não é erro: é o valor indefinido, que falha em silêncio no consumidor. Este requisito exige que a fronteira seja verificada por máquina e não por disciplina: um teste compara os nomes realmente exportados por cada módulo do pacote contra os nomes presentes no índice, obtidos por leitura do próprio código e nunca de uma lista copiada à mão, e reprova quando um contrato público fica de fora. Os nomes que são internos de propósito são classificados antes, com o critério escrito e o consumidor real conferido um a um, para que o teste não nasça quebrado nem seja afrouxado depois para passar. É a mesma família de defeito de "a guarda existe e não é chamada" (RNF-MANUT-003), e a diferença é que aqui a peça nem sequer podia ser chamada.
  • Prioridade: Alta
  • Atores: sistema, quem constrói o pacote
  • H1: Dado um módulo do pacote que exporta um nome destinado a consumidor externo, Quando o teste de contrato roda, Então esse nome está presente no índice público, E remover o nome do índice faz o teste reprovar nomeando o módulo e o nome, E um nome classificado como interno não reprova, E a classificação de interno declara o critério e o consumidor real de cada nome.
  • Notas: viola-se este requisito quando um nome destinado a consumidor externo não está no índice, quando a verificação passa a comparar contra uma lista escrita à parte em vez dos exports reais, ou quando um nome é reclassificado como interno apenas para fazer o teste passar. Nasceu em 2026-08-03: quatro peças da onda de blocos reais estavam construídas, testadas e mesmo assim indefinidas para o consumidor externo, porque nunca tiveram linha no índice. A varredura completa que se seguiu encontrou 63 nomes fora do índice e exatamente 1 defeito vivo, o aviso de falha da composição, que é justamente a peça que existe para tornar a falha da inteligência artificial compreensível ao operador: sem ela, o operador recebia o erro técnico cru em vez da mensagem que separa a narrativa que falhou dos números que continuam íntegros.

Reincidência medida, porque é o número de repetições que justifica a guarda existir. A mesma causa produziu seis ocorrências em um único dia (2026-08-03): as quatro peças da onda de blocos reais (a derivação da régua de confiabilidade, a imposição de abertura singular, a guarda de largura de janela e a métrica não somável), o aviso de falha da composição, e o anti-fadiga por quadrantes, que por sua vez é pré-requisito duro da fase de geração agendada e estava inalcançável para quem dependia dele. Quatro mais um mais um fecham em seis. Da segunda ocorrência em diante o defeito deixou de ser acidente e virou propriedade do jeito de trabalhar, e é por isso que a verificação é por máquina e não por atenção: a sexta ocorrência foi pega pelo teste de contrato, não por alguém lembrando de olhar.

RNF-MANUT-005: Número que vem de cima também se mede

  • Descrição: um número, uma contagem ou uma afirmação de arquitetura que chegue pronta, de qualquer origem (de um council, de uma auditoria, de quem coordena o trabalho, de uma sessão anterior deste projeto ou de um documento já publicado), é medido contra a fonte antes de entrar neste documento. Não existe origem que dispense a medição, e a origem mais perigosa é a que parece mais autorizada, porque é a que ninguém confere. O que este requisito PROÍBE nominalmente: adotar uma contagem sem refazer a aritmética contra o conjunto; escrever no documento uma afirmação sobre o comportamento do código sem ter lido o código; e ajustar uma medição própria para bater com um número recebido, em vez de reportar a divergência. Quando a medição diverge do número recebido, o que entra no documento é a medição, e a divergência fica registrada com as duas versões e o motivo.
  • Prioridade: Alta
  • Atores: quem escreve o documento
  • H1: Dado um número ou uma afirmação de arquitetura recebida pronta, Quando ela vai entrar neste documento, Então existe no documento a contagem refeita ou a leitura do código que a sustenta, E quando a medição diverge do recebido a divergência aparece escrita com as duas versões e o motivo, E nenhuma medição própria é ajustada para bater com o número recebido.
  • Notas: viola-se este requisito quando um número entra por procedência em vez de por medição, quando uma afirmação sobre o código entra sem leitura do código, ou quando uma divergência é silenciosamente resolvida a favor da fonte externa. Este requisito nasceu em 2026-08-03, e no mesmo dia três afirmações que já estavam escritas como fato foram medidas, e as três estavam erradas: o placar das fórmulas do Ap.10 (constava oito, medido em onze), a arquitetura da rubrica do Ap.4 (constava julgamento bloco a bloco, medido como documento inteiro numa chamada, o que também derrubou um risco de custo de 75 chamadas que eram 3) e a forma da composição (constava de cinco a sete leituras encadeadas, medidas em dois passos, com as passadas da régua independentes e paralelizáveis). Nos três casos a fonte era interna e confiável, nos três a medição é que estava certa, e nos três a afirmação já vivia neste documento como se fosse fato verificado. É por isso que o gatilho deste requisito não é a desconfiança da fonte: é o ato de escrever. Toda vez que um número ou uma afirmação de arquitetura vai para o documento, ele é medido, inclusive quando já estava lá.

RNF-MANUT-004: Rota nova prova-se pelo HTTP servido, e rota estática vem antes da dinâmica

  • Duas regras, uma causa. (a) Ordem: no roteador, uma rota de caminho fixo é registrada ANTES de qualquer rota dinâmica do mesmo prefixo. Registrada depois, ela é engolida pela dinâmica, que trata o segmento fixo como valor de parâmetro e responde erro para todo chamador. (b) Prova: toda rota nova nasce com um teste de regressão que bate no HTTP REAL, subindo o roteador, e não no handler importado direto.
  • O motivo de (b) é que (a) sozinha não se sustenta: chamar o handler direto pula justamente a camada onde o defeito mora, então o caminho testado nunca é o caminho servido, e a suíte fica verde enquanto a rota responde erro em produção. É a mesma família dos outros dois defeitos deste projeto que os testes aprovaram: a política de segurança que bloqueava os scripts inline do leitor (RNF-SEG-007) e a guarda que enumerava três scripts conhecidos e não reprovava o quarto.
  • O que agrava: a interface MASCARA a falha. Uma rota sombreada devolve erro, o front trata erro como lista vazia, e a tela diz "não tem nada" em vez de "não consegui carregar". O sintoma visível é indistinguível de ausência legítima de dados, que é exatamente a confusão que RN-015 separa (nulo de cobertura contra nulo de comparação) aplicada à camada de transporte.
  • H1: Dado uma rota de caminho fixo que compartilha prefixo com uma rota dinâmica, Quando o roteador é montado, Então a fixa está registrada antes da dinâmica, E existe um teste que faz a requisição HTTP real contra o roteador montado e recebe a resposta da rota fixa, E inverter a ordem de registro faz esse teste falhar.
  • Motivo medido: aconteceu DUAS vezes no mesmo dia (2026-08-02), no painel de analytics e no domínio custom, escrito por caminhos diferentes. Duas ocorrências independentes da mesma causa em um dia é padrão, não coincidência, e padrão vira requisito.
  • Terceira ocorrência em 2026-08-03, e ela é a que ensina, porque aconteceu em código escrito DEPOIS de o verificador já existir. A rota da tela de agendas nasceu registrada no fim do arquivo e foi engolida pela rota dinâmica do mesmo prefixo, exatamente como as duas anteriores, mesmo com a ferramenta de checagem disponível e o requisito já escrito. A lição, que é a razão de este parágrafo existir: ferramenta que existe mas não é trava não impede nada. Enquanto a verificação for algo que alguém PODE rodar, ela depende de lembrança e falha junto com a memória de quem escreve; ela só protege quando REPROVA sozinha, no mesmo lugar em que o código não passa. É por isso que o H1 acima exige o teste que inverte a ordem e vê reprovar, e não a existência de um verificador executável: um verificador que ninguém é obrigado a rodar é documentação, não guarda. Três ocorrências, sendo a terceira posterior à própria ferramenta, é o que transforma esta regra de recomendação em trava obrigatória de integração contínua.

RNF-SEG-008: A documentação publicada diz o QUE e o PORQUÊ, nunca o ONDE

  • Os documentos deste projeto são servidos numa página pública, sem login. Portanto eles não citam topologia interna: nome de repositório, nome de branch, hostname interno, caminho de arquivo de código, caminho de container ou de servidor, porta, nome de arquivo de ambiente, nem nome de pessoa. Nada disso acrescenta ao leitor do documento, e cada menção descreve a superfície interna para quem lê de fora.
  • O que FICA, porque é substância e não endereço: a afirmação de prova ("provado de ponta a ponta em produção", "3 relatórios publicados pela API real", "552 de 552 testes verdes"), o contrato de comportamento (nomes de função e de campo do contrato, códigos de erro, rotas da própria API do produto), e os identificadores do modelo de dados especificado aqui (reports.report, hub.companies). O rastro técnico de ONDE aquilo mora continua vivo no lugar certo, que é o commit e a documentação interna do projeto.
  • H1: Dado qualquer um dos documentos publicados deste projeto, Quando a varredura de topologia roda antes de publicar, Então o resultado é zero ocorrência de nome de repositório, branch, hostname interno, caminho de código, porta, arquivo de ambiente e nome de pessoa, E uma afirmação de prova existente no texto continua presente depois da varredura.
  • Motivo medido (2026-08-02): uma varredura dos 6 documentos encontrou 26 menções de topologia acumuladas por sessões diferentes ao longo do tempo (8 no DRS, 13 no Roadmap, 1 no Inventário, 4 no Plano de Testes), mais um nome de cliente. Nenhuma foi escrita com má intenção: cada sessão registrou onde tinha acabado de mexer. É por isso que a verificação é do PUBLISH e não da redação: o autor de cada frase não tem como ver o acúmulo.

RNF-SEG-007: Nenhum script inline sem hash correspondente na política de segurança de conteúdo

  • O leitor público é internet-facing e renderiza narrativa produzida por inteligência artificial e texto escrito por Contact, então unsafe-inline em script-src é recusado nominalmente e sem exceção. A autorização de script inline é por hash (hash-source), e os hashes são computados a partir das MESMAS constantes que montam as tags de script, para que não exista o estado em que a tag e o hash divergem.
  • O que este requisito PROÍBE, nominalmente: unsafe-inline em script-src; um script inline servido sem o hash correspondente na política; e um hash escrito à mão a partir de uma cópia do script em vez de derivado da constante que o gera.
  • Uma conferência automática compara os scripts inline efetivamente servidos contra os hashes declarados na política e reprova qualquer divergência, nos dois sentidos (script sem hash, e hash sem script).
  • H1: Dado o leitor público servindo uma página com scripts inline, Quando a conferência roda, Então todo script inline servido tem hash correspondente declarado na política, E script-src não contém unsafe-inline, E acrescentar um script inline novo sem o hash faz a conferência reprovar, E alterar o conteúdo de um script sem regerar o hash também faz a conferência reprovar.
  • Motivo medido (2026-08-02): a política do leitor não declarava script-src e caía no default-src 'self', o que bloqueou os três scripts inline da fatia de comentários. O magic link nunca criava sessão e os dois botões da tela eram código morto, enquanto a tela PARECIA correta porque tudo vinha renderizado do servidor. A suíte de testes aprovou tudo, porque teste não roda navegador com política de segurança aplicada. Este requisito existe para que a próxima ocorrência reprove sozinha.

D.2 Desempenho

RNF-PERF-001: Leitor público rápido e cacheável

  • A página do link é servida do cache; o payload real (na faixa observada de 8 a 90 KB) resolve confortavelmente sem trabalho pesado no caminho público. O asset imutável do snapshot é cacheado por snapshot_id (RNF-SEG-005), o que sobrevive à republicação sem servir conteúdo revogado.
  • H1: Dado um link já publicado, Quando o cliente o abre, Então a página responde a partir do cache (asset por snapshot_id) sem query pesada, E a resolução token para snapshot não faz varredura custosa.

RNF-PERF-002: Composição síncrona in-process, fila como evolução

  • No V1 a composição é síncrona in-process (um relatório real leva na faixa de 10 a 30 segundos; o wizard mostra o wait), o que aguenta o volume das poucas agências. Uma fila com estado de progresso persistido entra como evolução, disparada pelo primeiro arquivo que estoure o tempo de request, e aí assincroniza só o render, reusando o padrão de container efêmero do parse (RF-CMP-009).
  • Comportamento de estouro no V1 (antes de a fila existir): há um teto de tempo; ao ser atingido, a composição reprova com mensagem clara ao operador (não deixa a request pendurada), preserva o registro de custo e tempo já gastos (RF-CMP-012) e o rascunho é retomável. O estouro recorrente do teto é justamente o gatilho para promover a fila assíncrona.
  • O teto é POR BLOCO, com um teto de documento que escala com o tamanho. O teto único de 60 segundos por documento estava calibrado errado e punia exatamente o relatório grande, que é o relatório real: um documento de 7 seções cabe folgado em 60 segundos e um de 25 seções não cabe, então o teto reprovava por tamanho e não por lentidão. A forma correta: teto por bloco (referência de 6 segundos), e teto de documento igual ao número de blocos multiplicado pelo teto por bloco, com piso de 60 segundos e um limite superior configurável por agência. Um documento de 25 blocos passa a ter 150 segundos, e um bloco individual que trave continua sendo cortado em 6.
  • H1 do teto escalado: Dado um documento de 25 blocos e um de 7 blocos, Quando ambos são compostos, Então o teto aplicado a cada um é o número de blocos multiplicado pelo teto por bloco com piso de 60 segundos, E um bloco isolado que ultrapasse o teto por bloco é interrompido sem derrubar a composição inteira, E nenhum documento é reprovado por tamanho enquanto cada bloco individual respeitar o seu teto.
  • H1: Dado uma composição no V1 que ultrapassa o teto de tempo, Quando o teto é atingido, Então a composição reprova com mensagem clara e a request não fica pendurada, E o custo e o tempo já incorridos ficam registrados, E o operador pode tentar de novo.

D.3 Disponibilidade

RNF-DISP-001: Deploy independente do leitor e do parse isolado

  • O leitor público e o container de parse isolado têm deploy independente da fila de release da plataforma-mãe; o chrome do operador e a composição in-process acompanham essa fila.
  • 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.

RNF-DISP-002: Disponibilidade do link

  • O leitor público é serviço isolado e cacheável, com disponibilidade maior que o chrome do operador: o link do cliente não sai do ar quando o painel de composição está em manutenção. Por ser ferramenta interna, não há número contratual de disponibilidade (decisão resolvida no bloco 9, J.4).

D.4 Privacidade e LGPD

RNF-LGPD-001: Minimização por TTL do artefato cru (reforça RF-ING-004).

RNF-LGPD-002: Processo de eliminação explícito (reforça RF-ADM-011 e RF-ADM-024).

  • A eliminação de dados pessoais é um processo explícito, acionado por gesto auditado, que alcança as duas origens do dado: as tabelas (RF-ADM-011) e os arquivos (RF-ADM-024). Ela é completa ou falha: nunca relata sucesso sobre o que não verificou, e o relato separa o que foi apagado, o que foi preservado com motivo e o que não pôde ser olhado.
  • H1: Dado um cliente elegível à eliminação, Quando ela roda, Então o relato cobre tabelas e arquivos, E nenhuma execução que deixou de varrer qualquer uma das duas origens termina com resposta de sucesso.

RNF-LGPD-003: Base legal registrada para o analytics

  • A coleta de analytics do link tem base legal registrada.

D.5 Manutenibilidade

RNF-MANUT-001: Renderizador como package versionado (reforça RF-REN-005).

RNF-MANUT-002: Chrome do Reports em grupo de rotas lazy

  • O chrome do Reports carrega sob demanda para não inchar o bundle do Hub; o white-label runtime do Hub não pinta o chrome do Reports.

RNF-MANUT-003: Toda trava nasce com fixture de controle negativo na integração contínua

  • Regra do projeto, sem exceção: uma guarda entra acompanhada de um caso de teste que ela DEVE reprovar, e esse caso vive na suíte da integração contínua. Trava sem caso que ela deve reprovar não é trava, é comentário: ela passa verde para sempre, inclusive no dia em que parar de funcionar, e ninguém descobre.
  • O valor está na inversão: no dia em que a suíte passar VERDE com a fixture de controle negativo, isso significa que a trava morreu, e a integração contínua grita. É o oposto do teste comum, que grita quando o produto quebra; aqui o teste grita quando a proteção some.
  • Fixtures de controle negativo obrigatórias já nomeadas, uma por guarda: o trimestre com apenas um mês de dados comparado ao trimestre cheio, que produziu a afirmação de queda de 71 por cento (RF-CMP-021); alcance somado entre três janelas mensais (RF-CMP-022); a frase que atribui a um criativo o número de impressões da campanha inteira (RF-CMP-025); o conectivo causal sem evidência designada (RF-CMP-026); o gráfico de linha com dois pontos (RF-REN-014); o não medido gravado como zero (RF-REN-015); o segundo bloco hero no mesmo documento (RF-REN-013); a composição com dado mudado e leitura idêntica (RF-CMP-016).
  • H1: Dado qualquer guarda deste documento, Quando a suíte da integração contínua roda, Então existe ao menos uma fixture cuja expectativa é a REPROVAÇÃO por aquela guarda, E a suíte falha se essa fixture passar, E uma guarda nova sem fixture de controle negativo não é aceita no merge.

D.6 Acessibilidade

RNF-A11Y-001: Contraste AA no leitor e no PDF

  • As duas superfícies de leitura (tela escura e PDF claro) atendem contraste AA nos dois conjuntos de tokens. Quando a cor de acento do cliente cai abaixo de AA sobre o fundo, a luminância é ajustada por algoritmo determinístico até AA antes de renderizar (RF-REN-007).

5. Modelo de Dados

O provisionamento completo dos artefatos de dados (tabelas, campos, roles, entitlement) está no documento companheiro Inventário de Artefatos. Este bloco descreve o schema e as relações.

E.1 Schema reports (no banco da plataforma-mãe)

O ModularReports adiciona o schema reports ao banco do ModularHub. Ele lê e escreve tabelas do Hub (Company, Account, Contact, identidade visual, log de auditoria, entitlements) e possui suas próprias tabelas.

erDiagram
    COMPANY ||--o{ REPORT : "possui (company_id)"
    ACCOUNT ||--o{ REPORT : "referente a"
    REPORT ||--|| RASCUNHO : "tem rascunho 1:1"
    REPORT ||--|| SNAPSHOT : "tem snapshot unico"
    REPORT ||--o{ ARTIFACT : "usou (insumo, TTL)"
    REPORT ||--o{ COMMENT : "recebe"
    REPORT ||--o{ ACESSO : "gera eventos"
    IDENTIDADE ||--o{ REPORT : "brandiza (copiado no publish)"
    CONTACT ||--o{ COMMENT : "autor (logado)"
    REPORT ||--o{ TEMPLATE : "instancia de"
    ACCOUNT ||--o{ META : "tem metas (planejado)"
    REPORT ||--o| PROCESSAMENTO : "estado da composição (evolução)"
    COMPANY ||--o{ DOMINIO : "domínio custom"

E.2 Tabelas do schema reports

TabelaPapelCampos principais
reportO relatório e sua identidade públicaid, company_id, account_id, template_id, type (propósito, valor fechado de três, RN-014), cadence (valor fechado de seis, atributo da instância, RN-014), title, period (daterange, para permitir query por período e para medir a largura real da janela, RF-CMP-021), public_token (no report, republish mantém o link), password_hash opcional, expires_at opcional, status, created_at, published_at, production_minutes
report_draftRascunho editável, 1:1 com reportreport_id, blocks (jsonb, contrato v3), data_points (jsonb, registro de proveniência, Ap.8), briefing (jsonb), theme_overrides (jsonb, ajuste fino de apresentação do relatório, conjunto fechado de 4 chaves, RF-CUR-008), identity_overrides (jsonb, sobreposição de identidade por relatório, conjunto fechado de 2 chaves, a cor de acento e o logo do cliente, RF-CUR-004 e RN-020), share_card (jsonb, Ap.7), updated_at
snapshotFoto imutável publicada, única por report (sem versionamento)report_id, payload (jsonb: blocos, data_points, metas vigentes, branding e share_card copiados), schema_version, published_at
artifactInsumo cru, descartávelid, report_id, sha256, type, parse_status, purge_after (TTL LGPD)
asset_ownerPosse de cada arquivo enviado, gravada no mesmo ato da gravação do arquivo; é o que a eliminação lê para decidir o que pertence ao cliente (RF-ADM-024)asset_id (PK), company_id, account_id, report_id, sha256, mime_type, size_bytes, created_at, created_by; guarda o dono corrente, e a troca de dono é evento do log de auditoria (RF-ADM-012), que guarda a série
erased_assetLápide: o registro append de cada arquivo eliminado, sem nenhum byte do conteúdo (RF-ADM-024)id, company_id, account_id, report_id, kind (valor fechado de dois: arquivo enviado ou artefato gerado), asset_id, sha256, reason, recorded_at
commentComentário logado ancoradoid, report_id, block_id, excerpt (NOT NULL), author_contact_id, body, orphaned (bool), resolved_at, created_at
link_eventEvento de audiênciaid, report_id, event_type, referrer, opaque_visitor_id, is_agency_session (bool, para exclusão), created_at
templateTipo de relatório com blocos padrão, individual da agência ou Global (RN-013)id, company_id (agência de origem; nulo apenas nos Globais semeados pela operação da plataforma), name, type, default_blocks (jsonb; envelope de duas chaves, blocks e direction_profile, nunca array cru; ficha completa no Inventário, {ART-ENT-07}), is_global (booleano, não nulo, falso por default), created_at
report_goalMetas e planejamento por cliente (referência de planejado versus realizado, persistente, sem TTL)id, company_id, account_id, metric (text), target_value (numeric), unit, period_label, source_artifact_id opcional, created_at, updated_at
report_job (EM ESCOPO desde 2026-08-03, ADR-021)Estado de uma composição que roda fora da sessão do operador, que é o caso de toda edição nascida de agendaid, report_id, schedule_id opcional, status (queued, composing, done, failed), progress, tokens, duration_ms, error opcional, created_at
report_scheduleA agenda de geração de um cliente para um propósito (RF-ADM-019 a RF-ADM-022)id, company_id, account_id, type (propósito, RN-014), cadence (RN-014), calendar_anchor, template_id, owner_user_id, state (active, paused), created_at, updated_at; restrição de unicidade sobre (account_id, type)
schedule_cycleUm ciclo vencido de uma agenda, com o desfecho dele (RF-CMP-029, RF-ADM-022)id, schedule_id, window (daterange da janela do ciclo), outcome (valor fechado de seis: generated, skipped_paused, no_artifact, parse_failed, ai_unavailable, guard_failed), report_id opcional, closed_at
account_profilePerfil do cliente dentro do Reports, o lugar único dos atributos que mudam a leitura dos relatórios deleaccount_id, company_id, is_seasonal (bool) e peak_month opcional (RF-ADM-018), sale_model (consultiva, direta, ou nulo quando não declarado, RF-ADM-023), updated_at
business_parameterFato vigente do negócio do cliente que é operando de fórmula e não alvo (RF-ING-008)id, company_id, account_id, metric (text, do catálogo do Ap.10), value (numeric), unit, valid_from (date), source_artifact_id opcional, created_at
agency_domain (Fatia 14)Domínio custom da agência com validação de posseid, company_id, domain, txt_token, status (pending, verified), created_at
Estado da tabela template (medido em 2026-07-30): o escopo de RN-013 está aplicado. Ela nasceu na criação do schema com a coluna is_factory e com a visibilidade ampla amarrada a company_id nulo, forma que não expressa RN-013, porque apagar a agência de origem na promoção inviabiliza a despromoção, e "de fábrica" não é o mesmo conceito que "Global" (um Global pode ter nascido individual numa agência). A Fatia 10 ({F10-00} do Roadmap) trocou is_factory por is_global (booleano, não nulo, falso por default), preservou company_id como agência de origem e reescreveu o escopo em quatro regras restritivas, uma por comando: leitura quando a linha é Global, ou é da Company da sessão, ou quem lê é o Super Admin; inserção, atualização e exclusão só quando a linha é da Company da sessão E não é Global, ou quem grava é o Super Admin, sendo que a condição sobre a linha antiga na atualização é o que fecha a autopromoção.

E.3 Tabelas do Hub que o Reports usa

Tabela do HubUso pelo Reports
hub.companiesAgência (tenant); identidade no chrome e no link
hub.accountsCliente; o Reports cria Account de dentro do app (RF-ADM-007)
hub.account_identidade_visualIdentidade visual e tom de voz do cliente; o Reports popula essa ficha; o snapshot copia os valores no publish
hub.contacts e users_profileAutor do comentário (Contact logado)
hub.audit_logLog de alterações (event_type reports.*) por ponto único, com valor anterior e novo; append-only por RLS; o log de republicação deriva daqui (RF-ADM-012, RN-007)
hub.module_entitlementsLiga o módulo reports booleano por Company (a migration adiciona o literal reports à restrição CHECK; o catálogo de módulos em código ganha a entrada reports)
catálogo de permissões em código do HubRegistra os slugs reports.* (defaults por papel e metadados de capability); o override por usuário reusa a concessão de capability existente (RF-ADM-002)
messages.notifications (via messages.publish_notification, SECURITY DEFINER)Central de Notificações interna do Hub; o aviso ao admin de RF-CMP-020 é publicado aqui (type='system_alert', origin_module='reports'), nunca um canal novo (memory feedback_notif_internas_central_nao_whatsapp)

E.4 Regras de negócio de dados

  • RN-001: report.public_token vive no report; republicar mantém o link e sobrescreve o snapshot (RF-PUB-009).
  • RN-002: o snapshot.payload copia metas e branding no publish; alterações posteriores não mudam o snapshot (RF-PUB-001).
  • RN-003: todo número no payload referencia um dp: no data_points; número sem dp: reprova o publish (RF-CMP-007).
  • RN-004: artifact é apagado após purge_after sem quebrar o relatório publicado (RF-ING-004).
  • RN-005: comment.block_id referencia o id estável de um bloco; se o bloco sumir, orphaned vira verdadeiro e o excerpt preserva o contexto (RF-COM-004).
  • RN-006: o payload do bloco referencia asset por asset_id e sha256, nunca URL crua ou base64 (RF-ING-005).
  • RN-007: nenhum registro do log de auditoria contém dado pessoal cru; só evento e identificadores de ator e de alvo. O log é append-only por RLS. A pseudonimização LGPD acontece nas tabelas de origem (corpo do comentário, watermark do snapshot, Contact do Hub), nunca mutando o log (RF-ADM-011, RF-ADM-012).
  • RN-008: report_goal é referência persistente por cliente (sem TTL, não é artifact); um relatório lê as metas vigentes para os blocos de comparação, e o snapshot.payload copia essas metas no publish (RF-ING-007, RF-PUB-001). Alterar uma meta depois não muda o snapshot já publicado.
  • RN-009: report.company_id tem de ser igual ao company_id do Account referenciado por report.account_id; um trigger BEFORE INSERT/UPDATE confere contra hub.accounts e rejeita gravar um relatório que cruze a agência de uma Company com o Account de outra (fecha o furo de isolamento sem depender de bug de RLS, reforça RNF-SEG-001).
  • RN-010: a imutabilidade do snapshot.payload (RN-002) é imposta no schema, não só no app: a role de aplicação não recebe UPDATE direto em snapshot; a escrita acontece apenas pela função SECURITY DEFINER de publish/republish. O mesmo vale para o TTL do artifact (RN-004) e as metas congeladas (RN-008): o congelamento vive na cópia feita pela função de publish.
  • RN-011: a role reports_reader recebe GRANT de SELECT apenas em report, snapshot e comment, sem qualquer permissão de escrita e sem leitura de outra tabela ou schema (reforça RNF-SEG-001, RF-REN-006).
  • RN-012 (guarda de honestidade determinística): roda por código, não por IA, e independe da direção do operador.
  1. Não supressão de fato adverso aritmético (lei, sai do data_points): quando gasto maior que zero e conversões (compras ou conversas) igual a zero, ou meta de CPA definida e CPA realizado maior que a meta, ou receita igual a zero e gasto maior que zero, o fato é obrigatório na página; nenhum campo da direção o remove.
  2. Selo na métrica âncora: a métrica âncora é derivada por código do campo objetivo_campanha do briefing (valor fechado do enum do Ap.1, não texto livre) pelo mapa objetivo para métrica primária (parâmetro admin editável do ART-CFG, com seed default: vendas para receita ou ROAS, leads para leads ou CPL, trafego para cliques ou CPC, reconhecimento para alcance ou CPM, engajamento para engajamento), nunca escolha direta do operador (ele escolhe o objetivo, o mapa escolhe a métrica); ela carrega um selo calculado por código (número mais sinal, ex.: "queda de 18% vs período anterior"), gravado no dp: derivado, renderizado como componente não editável colado ao número; a narrativa não remove nem sobrepõe o selo, e não há UI para o operador pôr badge de valência na âncora (badges saem de vocabulário emitido por código). A base de comparação do selo (o "período anterior" do cálculo) tem 2 fontes possíveis, nesta ordem de precedência (decisão do dono, reconciliada 2026-07-30 no fix de {F9-12}): (i) o próprio artefato, quando a fonte já traz o período anterior como um dado à parte (os relatórios de mídia costumam trazer "comparado com o período X"; o extrator determinístico captura isso como um ponto de dado com a mesma proveniência de qualquer número, nunca um valor inventado); (ii) o relatório publicado anterior do mesmo cliente, mesmo tipo e mesma cadência, cuja janela tenha largura compatível com a deste relatório (RN-014, RF-CMP-021). A restrição de cadência e de largura vale só aqui, na base do selo, e NÃO no anti-fadiga de RF-CMP-016: o anti-fadiga quer o maior acervo possível de narrativa anterior para não repetir texto, e restringir o acervo por cadência o enfraqueceria sem ganho; o selo quer um número comparável, e comparar contra janela de largura diferente é exatamente o defeito que RF-CMP-021 fecha. As duas leituras do "relatório anterior" servem a propósitos diferentes e por isso têm filtros diferentes, de propósito. Quando nenhuma das 2 resolve, o selo publica só com o número, SEM seta, e isso NÃO é uma violação de honestidade, é o terceiro estado íntegro do selo (RF-CMP-018, formato "sem base"), não a ausência do selo; reprovar o publish aqui tornaria o primeiro relatório de todo cliente impublicável, o que nunca foi a intenção deste requisito.
  3. Pureza de canal: o pré-prompt e a composição recebem o label do dp: como dado tipado sanitizado (strip de quebra de linha, truncado em cerca de 60 caracteres, escapado), referenciado por metric_id, nunca o label concatenado no corpo da instrução (reforça RNF-SEG-002). O campo editável do prompt (RF-CMP-017) é só instrução: é proibido conter texto de dado do cliente; o dado entra apenas por referência tipada (metric_id ou dp:), nunca por texto digitável ou colado; um lint rejeita string de dado colada no campo de instrução.

O alerta "queda maior que X%" não é lei: é config de produto por agência (default X igual a 20, base mínima N igual a 30), versionada, admin editável (admin-editability-first), nunca do operador por página.

Limite honesto desta guarda: ela garante deterministicamente que os 3 fatos aritméticos egrégios nunca somem da página e que o selo da métrica âncora derivada do objetivo nunca é removido, sobreposto ou contradito por badge do produto; ela não detecta spin em prosa livre nem sela métricas adversas fora da âncora, e resultados ruins nuançados dependem do selo da âncora correta somado à curadoria humana, fronteira assumida e não coberta por código.

Viola-se RN-012 quando um dos 3 fatos adversos aritméticos existe nos dados e não aparece na página publicada, quando a métrica âncora publica sem o selo, quando um badge de valência oposta ao selo aparece na âncora, quando um label de dp: entra no corpo de instrução em vez de vir por metric_id sanitizado, ou quando texto de dado do cliente é colado no campo de instrução do prompt em vez de referenciado por metric_id.

  • RN-013 (escopo de template, Global e individual): todo template do sistema segue o mesmo esquema de escopo, sem exceção por tipo de relatório ou por agência.
  1. Um template nasce individual: pertence à agência de quem o criou (company_id preenchido) e só é visto e editado dentro dela.
  2. Global é uma marca que só o Super Admin põe e tira (RF-ADM-016), por gate de papel e nunca por permissão concedível: como a concessão individual de permissão do Hub alcança qualquer permissão e qualquer usuário, modelar a promoção como permissão a tornaria delegável a quem não é Super Admin, e a exclusividade sem exceção deste item deixaria de valer. Um template Global é visível para leitura a todas as agências com o módulo ligado, e só o Super Admin o edita, despromove ou exclui, inclusive contra a agência que o criou.
  3. A promoção marca o próprio registro, não gera cópia: o identificador é o mesmo, os relatórios que já apontam para ele continuam apontando, e a agência de origem é preservada em company_id justamente para que a despromoção saiba a quem devolver.
  4. Despromover devolve o template ao escopo individual da agência de origem. Um Global sem agência de origem (semeado pela operação da plataforma, company_id nulo) não tem despromoção; a forma de retirá-lo é a exclusão, e excluir é seguro porque nenhum relatório já criado depende do template (item 6).
  5. Qualquer usuário com a capability de gerir template duplica um Global para dentro da própria agência (RF-ADM-017). A cópia nasce individual e sem vínculo com o original, então variar um Global nunca depende de pedir promoção ao Super Admin.
  6. O template é gabarito consumido no nascimento do relatório: os blocos padrão são copiados para o rascunho no instante em que o relatório é criado (RF-ADM-009). Editar, promover, despromover ou excluir um template depois não altera nenhum relatório já criado, rascunho ou publicado; report.template_id é referência histórica e cai para nulo se o template for excluído.
  7. A biblioteca visível a uma agência é a união dos Globais com os individuais dela, e nada mais. O Super Admin, e só ele, enxerga também os individuais de todas as agências, porque é dele o gesto de promover.

Viola-se RN-013 quando um papel fora do Super Admin promove ou despromove um template, quando promover ou despromover deixa de ser gate de papel e passa a depender de uma permissão concedível, quando a promoção gera um segundo registro em vez de marcar o existente, quando a agência de origem se perde na promoção, quando um template individual de uma agência aparece na biblioteca de outra, quando um template Global deixa de aparecer para alguma agência com o módulo ligado, quando duplicar um Global exige Super Admin ou produz cópia atrelada ao original, ou quando editar, promover, despromover ou excluir um template muda os blocos de um relatório que já nasceu dele.

  • RN-014 (propósito é do modelo, cadência é da instância): as duas coisas que a linguagem do dia a dia mistura ("um relatório trimestral") são separadas no schema, e cada uma tem o seu lugar.
  1. Propósito é o tipo do relatório e vive no modelo (template.type, copiado para report.type no nascimento). É um valor fechado de três: campanha (fechamento de uma campanha específica), fechamento (fechamento de um período de operação) e pesquisa (levantamento ou diagnóstico, sem operação corrente da agência no período). Três e não mais: um propósito novo é decisão de produto com modelo próprio, não um valor digitado.
  2. Cadência é a periodicidade com que aquele relatório se repete e vive na instância (report.cadence), declarada no passo 1 do wizard (RF-CMP-001). É um valor fechado de seis: quinzenal, mensal, trimestral, semestral, anual e pontual. A cadência NÃO fragmenta type: não existe, e não pode passar a existir, um tipo "trimestral" ou "mensal" no catálogo de propósitos.
  3. Estado desta regra: DECISÃO, desde 2026-08-03. A condicional que existia aqui caiu com a resposta do dono a {Q4b} (J.5): a geração agendada entrou no escopo e a agenda carrega a cadência como campo do par (cliente, propósito), que é a mesma separação descrita nesta regra. A forma abaixo deixa de ser recomendação sujeita a resposta e passa a valer como decisão.
  4. O motivo de não fragmentar type por cadência é medido, não estético. A chave "relatório anterior do mesmo cliente e mesmo tipo" é usada por duas mecânicas distintas do sistema, o anti-fadiga de narrativa (RF-CMP-016) e a precedência da base de comparação do selo (RN-012 item 2). Se a cadência entrasse no type, todo cliente que mudasse de cadência (de mensal para quinzenal, de trimestral para mensal) partiria o próprio acervo em dois e as duas mecânicas voltariam ao estado de "sem base", encolhendo o pool de comparação exatamente do cliente mais maduro. E a auditoria dos relatórios já entregues não sustenta a fragmentação: a cadência não mudou o esqueleto de blocos de nenhum deles, mudou só contra o que cada um comparava.
  5. O que a cadência muda é a base de comparação, e essa é toda a consequência dela no sistema:
CadênciaCompara contra
quinzenala quinzena imediatamente anterior
mensalo mês imediatamente anterior
trimestralo trimestre imediatamente anterior e o mesmo trimestre do ano anterior
semestralo semestre imediatamente anterior e o mesmo semestre do ano anterior
anualo ano anterior
pontualnada por cadência; a base, se houver, sai da fonte (i) de RN-012 item 2

A cadência mensal pode adicionalmente comparar com o mesmo mês do ano anterior quando a sazonalidade do negócio do cliente justificar; essa segunda base é opcional e configurável por agência (ART-CFG), nunca inventada pela composição.

  1. A base escolhida por cadência entra na resolução de RN-012 item 2 como refinamento da fonte (ii) e está sujeita, sem exceção, à guarda de largura de janela de RF-CMP-021: uma base de cadência cuja janela real tenha largura diferente da janela deste relatório é recusada ou marcada como parcial, nunca comparada limpa.

Viola-se RN-014 quando um valor fora dos três propósitos é gravado em type, quando um valor fora das seis cadências é gravado em cadence, quando um nome de cadência aparece como tipo de relatório no catálogo de propósitos, quando a cadência é inferida da largura do período em vez de declarada, ou quando um relatório de cadência declarada compara contra uma base que a tabela acima não prevê.


RN-015: Nulo de COBERTURA reprova, nulo de COMPARAÇÃO é íntegro

São dois nulos com o mesmo formato e consequências opostas, e confundi-los é literalmente como três guardas já morreram neste projeto: alguém viu um nulo benigno, generalizou para "nulo aqui é normal" e afrouxou a guarda que tratava do nulo maligno.

ClasseSignificadoO que o sistema faz
Nulo de coberturao dado que o relatório afirma cobrir NÃO foi obtido: a métrica não veio no artefato, o parse daquele arquivo falhou, a etapa do funil não é medidaReprova. É lacuna: entra no bloco gap com motivo, entra na régua de confiabilidade (RF-CMP-023), e o número correspondente NUNCA é exibido como zero (RF-REN-015). Um relatório que afirme cobrir o que não cobriu é o defeito central que este produto existe para impedir
Nulo de comparaçãoo número foi medido corretamente e não existe BASE contra a qual compará-lo: primeiro relatório do cliente, primeira campanha, cadência recém-mudadaÍntegro. É o estado "sem base" do selo (RN-012 item 2): o número sai limpo, sem seta e sem variação, com a ausência de base declarada. Reprovar aqui tornaria impublicável o primeiro relatório de todo cliente

O "sem base" existente é precedente perigoso justamente por ser um nulo benigno bem resolvido: ele ensina que nulo passa. Esta regra escreve a distinção para que a próxima guarda seja calibrada pela CLASSE do nulo, e não pela sua aparência. Toda guarda nova que trate de nulo declara, no próprio requisito, a qual das duas classes ela se aplica.

RN-016: Sazonalidade vive no cadastro do cliente, não no relatório

O atributo de sazonalidade (RF-ADM-018) é do cliente e é lido pelo sistema no ato da composição; nenhuma tela oferece ao operador a escolha da base de comparação por relatório. A base escolhida é copiada para o snapshot no publish, então um relatório já publicado não muda de base quando o cadastro muda. Viés de escolha de base não é detectável dentro de um relatório (cada peça isolada parece defensável) e só aparece na comparação entre relatórios do mesmo cliente ao longo do tempo, o que é exatamente a situação em que ninguém está olhando.


RN-017: A agenda gera, o humano publica

A geração agendada acrescenta um produtor de rascunhos ao sistema, e não um segundo caminho de publicação.

  1. Uma agenda pertence ao par (cliente, propósito) e é única nesse par (RF-ADM-019). Ela não é tipo de relatório e não fragmenta type nem cadence (RN-014).
  2. ativa e pausada são estados da MESMA linha. Pausar não apaga a agenda, não apaga a configuração dela, não apaga edição já gerada e não revoga link publicado (RF-ADM-021). Retomar não gera nada no ato e não repõe em lote os ciclos vencidos (RF-ADM-022).
  3. Todo ciclo vencido de uma agenda fecha com um desfecho gravado, e os desfechos são nominais e fechados em seis: gerou, pulado por pausa, sem artefato, parse falhou, cadeia de inteligência artificial indisponível, guarda reprovou. Não existe ciclo sem desfecho, porque ciclo sem desfecho é exatamente o silêncio que faz a operação confiar numa geração que já parou (RF-CMP-029).
  4. Nenhuma edição nascida de agenda publica sem gesto humano, e nenhum gate é condicional à origem da edição. Não existe, e não pode passar a existir, parâmetro de agência, de agenda ou de ambiente que autorize publicar pulando o gate de proveniência ou o de honestidade (RF-CMP-028).
  5. O acervo não distingue edição agendada de edição avulsa para efeito de base de comparação e de anti-fadiga: as duas entram no mesmo pool do par (cliente, tipo), porque separá-las recriaria pelo lado da origem a mesma fragmentação que RN-014 item 4 impede pelo lado da cadência.
  6. A agenda automatiza a COMPOSIÇÃO, não a COLETA. Esta é a fronteira do que a cadência entrega, e está escrita com todas as letras porque ela muda a expectativa de quem lê "geração automática". Este documento não define mecanismo de upload automático de artefato e não existe conector de ingestão: nada vai buscar o dado do cliente sozinho. O que o disparador faz é adotar um relatório que o operador já preparou (criou, subiu o artefato e confirmou a direção, sem ter clicado em compor) cuja conta, propósito e período batem com a janela do ciclo. Sem esse relatório preparado, o ciclo fecha com o desfecho no_artifact e ninguém é surpreendido. O que a cadência tira do operador é montar e escrever, não é juntar os dados. Chamar isso de geração automática sem esta frase seria prometer mais do que o sistema entrega.
  7. O gate de honestidade NÃO roda dentro da composição automática, e é deliberado. Ele roda no publish, que continua manual e obrigatório (item 4). O rascunho que nasce da agenda é matéria-prima, e a honestidade é uma pergunta que se faz quando alguém decide publicar, não quando um texto é gerado. Por isso o desfecho de falha da composição automática (item 3) cobre falha estrutural e exceção, nunca reprovação de honestidade: não existe honestidade a reprovar num rascunho que ninguém propôs publicar. Condição registrada para o futuro: se algum dia a publicação deixar de ser manual, o gate de honestidade precisa passar a rodar ANTES, dentro do caminho automático, e essa mudança é pré-requisito e não consequência.
  8. A cadência curta e automática pressiona o anti-fadiga mais do que a criação manual pressionava, porque as edições passam a nascer no ritmo do calendário e não no ritmo do operador. Por isso a correção do anti-fadiga para medir variação do DADO contra variação do TEXTO (RF-CMP-016 na forma dos quatro quadrantes) é pré-requisito da agenda, e não melhoria paralela: sem ela, punir repetição de texto num acervo denso empurra a composição a inventar variação onde o dado não variou.

Viola-se RN-017 quando um par (cliente, propósito) tem duas agendas, quando pausar destrói qualquer coisa além do nascimento de edições futuras, quando retomar gera em lote, quando um ciclo vencido fecha sem desfecho ou com desfecho fora dos seis nominais, quando uma edição agendada publica sem gesto humano, quando um gate passa a olhar a origem da edição, ou quando o pool de comparação passa a separar edição agendada de edição avulsa.


RN-018: Modelo de venda troca vocabulário, nunca estrutura

O modelo de venda do cliente é atributo do cliente e tem exatamente um efeito no sistema.

  1. Os valores são dois e fechados: consultiva e direta. Um cliente pode não ter modelo declarado, e isso é estado válido, não pendência.
  2. O funil canônico tem QUATRO etapas nos dois modelos, sempre na ordem Atenção, Interesse, Consideração e Ação. A quantidade, a ordem e o significado das etapas são idênticos; só os rótulos trocam, pela tabela do Ap.10.
  3. Cada etapa carrega três números (volume, custo unitário e taxa de conversão vinda da anterior) nos dois modelos, e a primeira etapa não tem taxa nos dois (RF-CMP-030).
  4. O conjunto de métricas da camada de resultado é IDÊNTICO nos dois modelos (Ap.10). O modelo de venda não acrescenta, não remove e não renomeia nenhuma métrica de resultado.
  5. O modelo de venda não é type, não é cadence, não é template e não é dimensão da biblioteca. Criar tipo de relatório por modelo de venda partiria o acervo do cliente que mudasse de modelo, pelo mesmo mecanismo de RN-014 item 4.
  6. O bloco publicado grava o modelo de venda que o produziu; mudar o cadastro do cliente depois não altera rótulo de snapshot já publicado (RF-PUB-001).

Viola-se RN-018 quando um valor fora dos dois é gravado, quando o funil canônico tem número de etapas diferente de quatro ou ordem diferente, quando o modelo de venda muda o conjunto de métricas de resultado, quando ele vira tipo, cadência ou template, quando um cliente sem modelo declarado deixa de renderizar o funil livre, ou quando o rótulo de um snapshot publicado muda por edição de cadastro.


RN-019: Os dois sistemas convivem, e conviver não é espelhar

O ModularReports não substitui, por ora, o renderizador de páginas de resultados que atende os clientes hoje (decisão do dono registrada em J.5, com o "por enquanto" preservado: ela é reversível, não permanente).

  1. Os dois convivem. Nenhuma migração de links, de páginas publicadas ou de acervo é planejada enquanto esta for a decisão vigente.
  2. O ModularReports NÃO herda automaticamente mudança feita no outro sistema. Um tipo de bloco, um componente ou uma regra que nasça lá entra aqui por DECISÃO e por cartão de roadmap, com o mesmo critério de grau de confirmação por uso do ADR-017, e nunca por espelhamento reativo.
  3. Divergência entre os dois sistemas é estado esperado e não é defeito. Ela só vira trabalho aqui quando alguém decidir que vira, e a decisão fica escrita no cartão.
  4. O critério de destino é humano e deliberadamente sem mecanismo ({Q5c}, respondida em 2026-08-03). Por enquanto o sistema novo recebe os relatórios montados à mão, e o caminho antigo segue com o resto. O dono declarou explicitamente que esse critério não tem peso, e o tamanho da solução vem daí: nenhum seletor de sistema no assistente, nenhum campo de destino no cadastro do cliente, nenhuma regra automática que decida qual sistema recebe cada relatório. Quem monta escolhe onde monta. Construir o mecanismo aqui seria transformar em máquina uma decisão que o dono classificou como de pouca importância, e passar a mantê-la para sempre.

Viola-se RN-019 quando uma mudança do outro sistema entra neste por espelhamento, sem cartão e sem decisão; quando a divergência entre os dois é tratada como defeito a corrigir por padrão; quando se planeja migração de acervo enquanto a decisão vigente for a convivência; ou quando nasce qualquer mecanismo de roteamento entre os dois sistemas (seletor, campo de destino ou regra automática).


RN-020: Identidade do relatório, precedência campo a campo e os dois portadores de marca

A identidade que uma página de resultados exibe tem níveis de origem e portadores de marca, e confundir nível com portador é o que faz uma entrega apagar a outra.

  1. A precedência é campo a campo, e cada campo tem a sua cadeia. A cor de acento resolve na ordem sobreposição do relatório, ficha de identidade do cliente (Ap.6), padrão declarado da agência (RF-ADM-006) e, na falta dos três, o estado de lacuna declarada. O logo resolve na ordem sobreposição do relatório e marca da agência. Definir ou limpar um dos campos nunca move o outro: são duas cadeias que compartilham a mesma sobreposição, não um valor único com dois nomes.
  2. A sobreposição vive no rascunho, num conjunto de chaves fechado em duas (a cor de acento e o logo do cliente), com a mesma disciplina do ajuste fino de apresentação de RF-CUR-008: chave fora do conjunto é recusada, e a mesma regra é reforçada na rota, na guarda da aplicação e no banco, porque o valor vira token visual numa página pública sem login. A ausência da chave é a única representação de "não configurado". Nunca se grava vazio: duas representações de vazio obrigariam todo consumidor rio abaixo a aprender a segunda, e é assim que um deles esquece.
  3. O retrato congela, e republicar rerresolve. A identidade resolvida é copiada para o retrato publicado (RN-002), então mudar a ficha do cliente ou a sobreposição depois não altera relatório já publicado. Publicar de novo rerresolve do zero e sobrescreve o retrato mantendo o mesmo link (RN-001), porque a republicação é ato humano deliberado e auditado (RF-CUR-007), e a imutabilidade de RF-PUB-001 protege contra edição de rascunho, nunca contra uma publicação nova.
  4. Os dois portadores de marca da página pública têm donos nominais. O portador do CLIENTE é a abertura do relatório, e é ali que o logo do cliente aparece quando existe sobreposição. O portador da AGÊNCIA é a barra superior da página, com a marca da agência e a declaração de quem preparou o relatório. Os dois vêm do protótipo navegável aprovado (F.3), que os desenhou separados desde o início. Enquanto a barra superior não existir, existe um portador só e ele é o da agência, e por isso a sobreposição de logo não pode ser construída antes dela: construí-la antes trocaria o cumprimento de RF-ADM-004 pelo de RF-CUR-004, o que não é entregar, é mudar a dívida de lugar.
  5. A capa do card de compartilhamento (Ap.7) permanece marca da agência e não participa desta precedência: ela identifica quem publicou o link, não o cliente do relatório.

Viola-se RN-020 quando a sobreposição de um campo altera o outro; quando limpar uma sobreposição grava vazio em vez de remover a chave; quando uma chave fora das duas é aceita; quando a identidade de um relatório publicado muda sem nova publicação; quando republicar deixa de rerresolver a identidade; quando o logo do cliente ocupa o portador da agência, ou a marca da agência some da página pública por causa de uma sobreposição de relatório; ou quando a precedência é reimplementada fora do ponto único de RF-CUR-012.


6. Interfaces, Integrações e Protótipo

F.1 Superfícies do sistema

SuperfíciePúblicoNatureza
Chrome do operadorOperador e AdminAplicativo isolado dentro da suíte, entrada pelo menu do Hub e endereço direto
Wizard de composiçãoOperador4 passos (Ap.1)
CompositorOperadorCuradoria de blocos, três colunas (artefatos e IA, preview, config)
Modo apresentarOperadorTela cheia, rascunho ou publicado
Modal de publicaçãoOperadorOnde o link nasce (Ap.7)
Leitor públicoCliente finalPágina estática isolada somente leitura
Widget de comentárioCliente final logadoDrawer lateral estilo documento colaborativo
Portal do cliente logadoCliente final logadoLeitura agregada dos relatórios dos seus Accounts, opcional (RF-REN-011); tela cliente-painel do mockup
Painel de analyticsAdminAgregado por agência
Painel do Super AdminSuper AdminEntitlement, agências, limites, retenção

F.2 Integrações com a plataforma-mãe

  • Autenticação e sessão: mesma sessão do Hub; magic link do Hub para o Contact comentar (RF-COM-006).
  • Identidade: ficha account_identidade_visual do Hub (RF-ADM-005).
  • PDF: conversor de HTML da plataforma-mãe, condicional (RF-REN-008).
  • Auditoria: log do Hub (RF-ADM-012).
  • Entitlement e RBAC: entitlements e catálogo de permissões do Hub (RF-ADM-001, RF-ADM-002).

F.3 Protótipo navegável (referência visual aprovada e guia do desenvolvimento)

O sistema tem um mockup navegável do sistema completo publicado e aprovado, que é a referência visual aprovada e o guia do desenvolvimento, 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. Cada tela é construída seguindo a página correspondente do mockup, e divergência visual na implementação se resolve consultando o mockup.

Ponto de entrada (Mapa do mockup, navega para todas as telas): modulareasy.com/mockups/modulareasy/modularreports

As 17 telas, cada uma navegável e clicável:

TelaSuperfícieAbrir
LoginChrome do operadorlogin
Lista de relatóriosChrome do operadorrelatorios
ClientesChrome do operadorclientes
Pasta do clienteChrome do operadorcliente-ficha
Wizard de composição, 4 passosWizardnovo-relatorio
Direção e prompt (prompt exposto e editável, dados somente-leitura)Wizarddirecao-prompt
Compositor (inclui o ajuste fino de apresentação, RF-CUR-008)Compositorcompositor
Modal de publicaçãoModal de publicaçãopublicar
Modo apresentarModo apresentarapresentar
TemplatesChrome do operadortemplates
Página pública do relatórioLeitor públicorelatorio-publico
Analytics do linkPainel de analyticsanalytics
Identidade da agênciaChrome do operadoragencia-branding
Usuários e papéisChrome do operadorusuarios
Painel do Super AdminPainel do Super Adminsuperadmin
Painel do clienteLeitor logadocliente-painel
Mapa do mockupÍndiceindex
A fonte do mockup vive na pasta do projeto (mockup/). Ele é a página de referência que o desenvolvimento segue tela a tela.

Estados de UI a incluir numa próxima iteração do mockup (já especificados nos RFs, ainda sem tela desenhada; a construção os implementa pelo RF): (1) espera da composição (10 a 30 segundos, RNF-PERF-002) e o estouro do teto de 60 segundos com mensagem e rascunho retomável; (2) falha parcial de upload por arquivo ("5 de 6 lidos, 1 falhou: motivo", RF-ING-003); (3) o bloco protegido por fato adverso no compositor (marcado, com recusa explicada ao tentar esconder, RF-CMP-018); (4) a visão do relatório publicado com as ações de republicar e revogar explícitas. Sobre a home dessas ações: republicar e revogar vivem na visão do relatório publicado (o compositor aberto num relatório com status publicado) e como ações rápidas na lista de relatórios (RF-PUB-008, RF-PUB-009); o estado "pendente de validação TXT" do domínio custom (ADR-015) segue também nessa fila de iteração.


7. Arquitetura e Tecnologia

G.1 Forma arquitetural

O ModularReports é um módulo do modular monolith do ModularHub, com bounded context próprio: schema reports, role própria, entitlement próprio. O chrome do operador é um grupo de rotas isolado que renderiza um shell próprio (não o menu do Hub). O leitor público é um serviço isolado somente leitura. A composição de IA roda in-process (síncrona), reusando o gateway de IA da plataforma-mãe; o único processo de fato isolado é o parse de arquivo, num container efêmero sem rede de saída (onde mora o risco de exploit). A Fatia 1 entrega a implementação de referência do renderizador e as interfaces identity, storage e render, agnósticas à decisão de módulo; o package renderizador único (RF-REN-005) é entregue na Fatia 3. O orquestrador de contêineres da plataforma-mãe é docker Compose.

Forma real da composição, medida em 2026-08-03 (restrição arquitetural declarada aqui porque é aqui que ela pertence, e não no código). A composição encadeia dois passos de leitura por modelo de linguagem, e não de cinco a sete como este documento afirmou até esta data sem nunca ter medido. A avaliação de qualidade roda em 3 passadas independentes entre si, que por isso passaram a ser executadas em paralelo em vez de em série. Consequência prática: a latência da composição não cresce com o número de blocos do documento nem com o número de dimensões da régua, e o gargalo que se supunha existir na cadeia de leituras não existe. Se um passo novo entrar e depender de verdade da saída de outro, essa dependência é restrição arquitetural e volta para este parágrafo antes de ir para o código.

G.2 Decisões de arquitetura (ADR)

IDDecisãoContextoConsequência
ADR-001ModularReports é módulo do ModularHub, não app standalone nem banco compartilhado cruComentário exige a identidade que já existe no Hub (Contact); compartilhar usuários, tenants e permissões significa ser o mesmo produto; a regra de isolamento proíbe produtos separados compartilharem banco, não proíbe um produto ter módulosHerda auth, tenants, branding, RBAC, gerador de PDF e cadeia de IA; entra na fila de release do Hub para o chrome do operador
ADR-002Chrome próprio isolado, entrada pelo menu do Hub e endereço diretoRequisito de UX de "aplicativo dentro da suíte"; o menu do Hub já é carregadoNamespace de rotas próprio, tema escopado, bundle lazy
ADR-003Leitor público isolado somente leitura servindo snapshot imutávelO caminho público não pode ter alcance a outros schemas nem query pesadaRole reports_reader com GRANT de SELECT apenas em report, snapshot e comment (não no schema inteiro, RN-011); se o raio de exposição incomodar, extrair só o leitor é barato e reversível
ADR-004Composição de IA in-process síncrona reusando o gateway da plataforma-mãe; parse de arquivo isolado é o único processo separadoO parse é onde mora o risco real (XXE, zip bomb, SVG com script, PDF malicioso executam no parser, antes da IA); a barreira cross-tenant da composição é ausência de estado global mais contexto por report_id, não isolamento de processo; o volume de poucas agências aguenta síncrono (10 a 30 s)Parse em container efêmero sem rede, fail-closed; composição in-process com claims de sistema; fila com worker assíncrono é evolução disparada pelo primeiro request que estourar o tempo
ADR-005Números por extração determinística com proveniência, IA só narraPrompt injection via artefato do cliente; zero alucinação numéricaExtrator por código popula dp:; número sem origem reprova o publish
ADR-006Republicar reusa o mesmo link, sem versionamento de páginasNão guardar milhares de versões; simplicidadeSnapshot único por report; log de alterações humano só interno
ADR-007Contrato de bloco v3 evolutivo, não reescritaO formato anterior funciona e tem payloads reais; type desconhecido não pode quebrar o link do clienteRegistro declarativo, conversão em leitura, corpo sanitizado
ADR-008Renderizador como package de funções puras únicoO mesmo HTML serve leitor, preview e PDFSem segunda implementação de blocos em React ou na composição
ADR-009Storage em bucket dedicado reports no storage da plataforma-mãeO storage real do Hub é de arquivo; não existe object storage separado a inventarArtefato cru e capa no bucket; payload em jsonb; cache HTTP resolve o "estático"
ADR-010Cadeia de IA 100% gratuita, sem upgrade pago em silêncioFerramenta interna sem billing; custo tem que ser previsívelA Fatia 1 mede se os gratuitos passam na rubrica; reprovar volta para decisão com números
ADR-011Pipeline em duas partes: parse isolado (container efêmero sem rede) mais composição in-process reusando o gateway de IA da plataforma-mãeO risco de arquivo malicioso é de parser, não de IA; a plataforma-mãe não tem fila nem token de serviço, a IA dela é síncrona in-process; separar as duas partes põe o isolamento onde ele importaFalha de parse rejeita o arquivo com motivo e vira lacuna (fail-closed por arquivo, sem derrubar os demais do lote); a composição opera sobre dado saneado; token de serviço e fila assíncrona ficam como trabalho futuro
ADR-012Cache do leitor público com chave por snapshot_id, não pelo token do linkA resolução token para snapshot precisa checar revogação a cada acesso (no-store), mas o asset imutável do snapshot pode ser cacheado agressivoRevogar purga o cache por snapshot_id e devolve 410; o cache sobrevive à republicação sem servir conteúdo revogado
ADR-013Log de auditoria append-only por RLS, pseudonimização LGPD na origemO log do Hub só permite INSERT e SELECT por policy e não guarda dado pessoal cru; mutar o log para anonimizar violaria o append-onlyO offboarding apaga a ponta identificável nas tabelas de origem (comentário, watermark, Contact), e as referências por identificador no log deixam de resolver para a pessoa
ADR-014Cor do cliente como acento sobre o leitor escuro, ajustada por algoritmo até AAO leitor é escuro sempre; a cor arbitrária do cliente pode ter contraste ruim sobre o fundo escuroA luminância do acento é ajustada em espaço HSL de forma determinística até AA, mesmo padrão do chrome da plataforma-mãe; o PDF usa o conjunto claro de tokens
ADR-016 (DECISÃO desde 2026-08-03, ver ADR-021)Periodicidade é atributo da instância do relatório, não tipo de modeloA linguagem do dia a dia diz "relatório trimestral" e isso empurra para criar um tipo de modelo por cadência; o schema já carrega a janela real na instância (report.period, daterange), e a chave "cliente mais tipo" sustenta duas mecânicas (anti-fadiga RF-CMP-016 e base do selo RN-012 item 2)report.cadence na instância, type fechado em três propósitos (RN-014); mudar a cadência de um cliente não parte o acervo dele em dois; a cadência escolhe a base de comparação, e essa base passa pela guarda de largura de janela (RF-CMP-021)
ADR-017Tipo de bloco novo nasce do relatório real entregue, não do catálogo imaginadoO registro declarativo herdou os tipos do renderizador anterior; a auditoria dos relatórios já entregues mostrou quatro tipos em uso real ausentes do registro, e um tipo do registro sem nenhum uso realO Ap.3 declara, por tipo, o grau de confirmação por uso (quantos relatórios reais o usam); um tipo sem uso real fica registrado como aposta e não como necessidade validada, e a diferença aparece escrita, não implícita
ADR-018Construir para uso futuro exige data de corte e critério de morte escritoscomparison está no contrato por decisão de produto e não por uso observado (0 de 6 relatórios reais). Sem uma condição de liquidação, uma aposta assim vira tipo morto permanente, e cada tipo morto custa manutenção, superfície de teste e um item a mais que o compositor precisa considerar a cada composiçãoO tipo é construído com prioridade abaixo dos que têm uso comprovado, e carrega no Ap.3 a condição de morte: se em 2 ciclos reais de adoção depois da Fatia 11 nenhum relatório usar comparison, ele é cortado do registro e do contrato
ADR-021A geração agendada ENTRA no escopo, e com ela a report_job deixa de ser evoluçãoResposta do dono a {Q4b} em 2026-08-03: a agenda é configurável, editável, ativável e pausável. As quatro palavras são requisitos, não adjetivos, e nenhuma delas se sustenta com composição só síncrona dentro da sessão do operador: uma edição que nasce sozinha no vencimento do ciclo roda sem sessão nenhumareport_schedule, schedule_cycle e report_job entram no modelo de dados; RF-ADM-019 a RF-ADM-022 e RF-CMP-027 a RF-CMP-029 nascem Essenciais; a composição in-process continua sendo a mesma (ADR-013 intacto), o que muda é quem a dispara e onde o progresso é guardado
ADR-022Modelo de venda é atributo do cliente que troca vocabulário, e não uma dimensão nova de tipoA fonte apontada pelo dono (a planilha de gestão da operação) mostra UMA estrutura, não duas: quatro etapas na mesma ordem, a mesma tripla por etapa e um bloco de resultado idêntico nos dois modelos. Só os rótulos das etapas mudam. O caminho caro (um tipo de relatório por modelo de venda) partiria o acervo de quem mudasse de modelo, pelo mesmo mecanismo de RN-014 item 4, e não compraria nadaaccount_profile.sale_model com dois valores fechados; funil canônico de quatro etapas em funnel_simple (RF-REN-016); tabela de rótulos por modelo no Ap.10; comparabilidade entre relatórios do mesmo cliente preservada
ADR-023Nenhum tipo de bloco novo nasce para o funil canônico nem para a camada de resultadoO funil canônico é o funnel_simple com o perfil canônico aplicado, e a camada de resultado é uma grade de números rotulados, que é exatamente o que stat_cards já renderiza. Criar um tipo por causa de um perfil de conteúdo abriria a lista nominal fechada de RF-REN-012 sem ganho de render, e cada tipo novo custa manutenção nas três superfícies (compositor, leitor e PDF)A lista de dezesseis tipos do Ap.3 permanece fechada e inalterada; o que nasce são campos aditivos em funnel_simple (Ap.3) e um catálogo de métricas (Ap.10), não componentes
ADR-024Os dois sistemas de página de resultados convivem, e a convivência é explícita e não espelhadaResposta do dono a {Q5b} em 2026-08-03: "não substitui nada por enquanto". A palavra "por enquanto" é parte da decisão e fica preservada: ela é reversível. O risco que sobra não é a coexistência, é o espelhamento reativo, que já aconteceu (três dos quatro tipos de bloco absorvidos em 2026-08-02 nasceram no sistema irmão no dia anterior, por reação a um pedido de cliente)RN-019: nenhuma migração enquanto esta for a decisão vigente; mudança do outro sistema entra aqui por cartão e decisão, com o critério de grau de confirmação por uso do ADR-017; divergência entre os dois é estado esperado, não defeito
ADR-020O certificado do domínio custom exige um passo manual no servidor, e isso é limitação DECLARADAA infra não tem emissão de certificado sob demanda, e construir isso é trabalho de plataforma, fora do escopo deste módulo (ADR-015 já previa a confirmação)O ciclo do domínio (pendente, verificado, ativo) e a validação de posse funcionam sozinhos; a ativação com certificado tem um passo manual documentado no procedimento de provisionamento. Fica escrito como limitação conhecida no card e aqui, e não como pendência esquecida: quem for ativar um domínio sabe de antemão que existe esse passo
ADR-019O dono de uma regra de comparação é escolhido pela natureza do erro, não pela camada mais convenienteDuas regras nascidas do mesmo achado (não somar alcance entre janelas; não comparar período parcial com período cheio) pareciam pertencer ao mesmo lugar, e não pertencem: na primeira o número em si seria falso; na segunda os dois números são verdadeiros e a mentira está na comparaçãoRegra de decisão escrita: pergunte se o NÚMERO é falso, ou se o número é verdadeiro e a FRASE é falsa. Número falso, o dono é o EXTRATOR, que se recusa a produzi-lo (RF-CMP-022). Número verdadeiro e frase falsa, o dono é o selo mais o COMPOSITOR, proibido de emitir a afirmação quando o selo diz não comparável (RF-CMP-021, RF-CMP-024, RF-CMP-025)
ADR-015Domínio custom da agência com validação de posse por TXT e proxy/cert on-demandA plataforma-mãe pode ou não ter um mecanismo de domínio custom próprio; se não tiver, o proxy e o certificado on-demand do Reports são trabalho novo (a confirmar antes da Fatia 14)Entidade agency_domain com token TXT; o CNAME só ativa após posse verificada (RNF-SEG-006); a gestão de domínio já aparece como seção da tela de identidade da agência no mockup, o que falta ali é o estado "pendente de validação TXT" antes do "propagado", a incluir numa iteração do mockup

G.3 Stack (nível arquitetural)

CamadaTecnologia
Chrome do operadorReact e o design system da plataforma-mãe, em grupo de rotas lazy
Leitor públicoServiço isolado somente leitura, renderização por funções puras (SSR)
RenderizadorPackage TypeScript puro que emite HTML, compartilhado por leitor, preview e PDF
BancoPostgres da plataforma-mãe, schema e role reports, RLS RESTRICTIVE, payload em jsonb
Composição de IAIn-process (síncrona), reusando o gateway de IA da plataforma-mãe com a cadeia gratuita; contexto por report_id sem estado global; fila assíncrona como evolução
Parse de arquivoContainer efêmero sem rede de saída no orquestrador (docker Compose), fail-closed
PDFConversor de HTML da plataforma-mãe (condicional)
StorageBucket dedicado reports no storage de arquivo da plataforma-mãe (limite por arquivo do storage; o bucket é criação nova)
AutenticaçãoServiço de autenticação da plataforma-mãe, incluindo magic link do Contact

G.4 Isolamento

O ModularReports segue a regra de isolamento da casa: não é um produto separado compartilhando banco de outro produto; é um módulo do ModularHub e por isso compartilha o banco do próprio Hub. Não compartilha banco, auth ou storage com nenhum outro produto do ecossistema.


8. Estratégia de Validação

O plano completo de validação (Alfa e Beta) está no documento companheiro Plano de Testes. Este bloco define a estratégia.

I.1 Gates HARD da Fatia 1 (protótipo do pipeline IA)

A primeira fatia é um protótipo do pipeline de composição com critérios de saída que, se falharem, reprovam a fatia:

  • Zero alucinação numérica: todo número exibido tem proveniência; número sem origem reprova (RF-CMP-007).
  • Tríade de segurança: números fora do modelo de linguagem, publicar exige a capability reports.publish, parse isolado sem rede (RF-CMP-005, RF-CMP-010, RF-ING-003).
  • Isolamento cross-tenant: nenhum dado de um tenant vaza para a composição de outro, contexto por report_id sem estado global (RNF-SEG-001, RF-CMP-009).
  • Qualidade da narrativa: rubrica de 5 dimensões (Ap.4) com juiz por modelo de linguagem calibrado (golden como nota 4 versus degradado como nota 1 ou 2); um juiz que não separa os dois reprova a rubrica antes do produto. Threshold: média maior ou igual a 3,0, nenhuma dimensão igual a 1, e as dimensões de honestidade comparativa e lacuna declarada obrigatoriamente maiores ou iguais a 3.

O golden set da Fatia 1 é composto de relatórios reais já entregues pela agência (com os artefatos crus e os payloads finais), mais um caso adversarial de artefato com dado inflado (teste de fail-loud) e casos fora do nicho. Os portões binários de segurança e veracidade reprovam de forma absoluta; os indicadores percentuais são termômetro enquanto o conjunto de referência for pequeno, e viram portão quando ele crescer (bloco 9, J.4).

I.2 Validação contra a especificação

Cada RF é validado contra o seu H1, incluindo casos de borda. Nenhuma entrega é integrada sem essa validação aprovada.

I.3 Inspeção visual obrigatória

Toda mudança de interface passa por inspeção visual real (tela aberta e conferida), comparada com o mockup aprovado (bloco 6). O leitor público é conferido em desktop e mobile.

I.4 Prova de uso por jornada

Cada jornada de cada papel é percorrida ponta a ponta de verdade (não "a tela abriu", e sim "consegui fazer e vi o resultado"), sem becos sem saída de navegação, com a autonomia que o RBAC concede a cada papel.


9. Entrega e Evolução

A ordem de construção completa, com o critério de verificação de cada tarefa, está no documento companheiro Roadmap de Implementação. Este bloco define o princípio e as ondas.

J.1 Princípio do roadmap

O sistema é planejado por inteiro; o roadmap ordena as fatias por dependência técnica, não corta escopo por valor de negócio. Uma fatia só é construída depois daquilo de que depende.

J.2 Ondas de construção

As fatias independentes da fila de release do Hub vêm primeiro (protótipo, contrato de bloco, renderizador, ingestão, leitor); as que exigem o chrome dentro do monolith vêm depois.

FATIA 1, PROTÓTIPO DO PIPELINE IA (cravada, gates HARD de I.1)
  package renderizador, parse isolado e composição in-process, interfaces identity, storage e render (agnóstico à decisão de módulo)

FATIA 2, CONTRATO DE BLOCO E SNAPSHOT (coração que serve IA, curadoria, leitor e analytics)

FATIA 3, PACKAGE RENDERIZADOR (leitor SSR, preview, PDF a partir das mesmas funções)

FATIA 4, INGESTÃO, QUOTA E STORAGE (upload, parse isolado, TTL, assets)

FATIA 5, LEITOR PÚBLICO (snapshot imutável, estados brandados, cabeçalhos)

FATIA 6, SCHEMA, ROLE E ENTITLEMENT NO HUB (migration, RLS, catálogo de permissões)

FATIA 7, CHROME E SHELL DO OPERADOR (grupo de rotas isolado, entrada pelo Hub)

FATIA 8, WIZARD E COMPOSITOR (4 passos, curadoria, apresentar)

FATIA 9, PUBLICAÇÃO E LINK (snapshot, senha, expiração, QR, card, revogar, republicar; o gate de adoção mede-se aqui)

FATIA 10, TEMPLATES (tipo de relatório, CRUD)

FATIA 11, IMPORTAÇÃO DE METAS (planejado versus realizado)

FATIA 12, COMENTÁRIOS (logado, ancorado, órfão, resolução)

FATIA 13, ANALYTICS (eventos, views, referrer, painel por agência)

FATIA 14, DOMÍNIO CUSTOM E WHITE-LABEL (CNAME por agência com URL neutra atrás)

FATIA 15, PDF CONDICIONAL (reuso do gerador da plataforma-mãe)

FATIA 16, PAINEL DO CLIENTE (o modelo de identidade do cliente já nasce no schema do dia 1)

FATIA 17, BLOCOS REAIS, CADÊNCIA E BIBLIOTECA GLOBAL (os quatro tipos de bloco em uso real ausentes do registro, a cadência na instância, as duas guardas de comparação e os modelos Globais semeados; nasceu da auditoria dos relatórios já entregues e vem na fila antes das fatias 13 a 16, ver Roadmap)

Diferimentos previstos: analytics por bloco (só visualizações e último acesso primeiro); templates e importação depois do gate de adoção; lançar com URL neutra e o domínio custom logo atrás. A Fatia 9 nasce com o recorte mínimo de leitura de metas para o bloco planejado versus realizado; a Fatia 11 é a importação estruturada completa (bloco 9, J.4).

J.3 Ciclos reais de adoção

Logo após as fatias iniciais, ciclos reais de adoção via API são antecipados (o maior risco do produto é virar o terceiro sistema de relatório sem ser adotado). O aceite é um piloto nomeado com uma agência parceira, com o critério de adoção de A.7.

J.4 Decisões de implementação resolvidas

As decisões técnicas de construção foram tomadas por recomendação e não vão para o questionário:

  • Disponibilidade do link: o leitor público é serviço isolado e cacheável, com disponibilidade maior que o chrome do operador. O link do cliente não sai do ar quando o painel de composição está em manutenção. Por ser ferramenta interna, não há número contratual de disponibilidade (RNF-DISP-002).
  • Metas antes da publicação: a Fatia 9 (publicação) nasce com um recorte mínimo de leitura de metas (só o suficiente para o bloco planejado versus realizado), sem esperar a Fatia 11 (importação estruturada completa). Assim o primeiro relatório publicado já pode comparar planejado e realizado.
  • Regenerar um bloco: é chamada de resposta imediata (o operador espera alguns segundos e vê o resultado). O assíncrono (dispara e atualiza sozinho) fica reservado para a composição inteira do wizard, que é mais demorada (RF-CUR-002, RF-CMP-013).
  • Critérios da Fatia 1: os portões binários de segurança e veracidade (zero número inventado, isolamento entre agências, parse isolado sem rede) reprovam o protótipo de forma absoluta. Os indicadores percentuais (taxa de blocos aceitos sem edição, fidelidade, custo, latência) são acompanhados no protótipo como termômetro, não como reprovação, enquanto o conjunto de referência for pequeno; viram portão quando o conjunto crescer.

J.5 Decisões de produto em aberto

Duas decisões de produto seguem no Questionário de Elicitação ({Q1} e {Q2}) e não travam a construção: como uma agência parceira nova começa e quem libera o acesso do cliente para comentar. A pergunta sobre a biblioteca de templates ({Q3}) foi respondida e virou regra do sistema: todo template nasce individual e o Super Admin o marca como Global quando quiser (RN-013); a biblioteca de uma agência começa com os Globais que existirem (os dois modelos semeados do Ap.9) e cresce com os individuais dela.

{Q4b} foi RESPONDIDA em 2026-08-03, e a resposta é a leitura A: a geração agendada entra no escopo. A agenda é configurável, editável, ativável e pausável, e as quatro palavras são requisitos e não adjetivos. Configurável: o operador declara a cadência, a âncora de calendário, o modelo e o responsável, por cliente e por propósito (RF-ADM-019). Editável: muda depois sem recriar, e a agenda mantém a identidade e o vínculo com as edições que já gerou (RF-ADM-020). Ativável e pausável: pausar interrompe o nascimento de edições novas e não apaga a configuração, o histórico nem os links publicados (RF-ADM-021), e retomar segue do próximo ciclo à frente, nunca repondo em lote os ciclos vencidos (RF-ADM-022). A edição nasce em rascunho já comparada com a anterior da mesma agenda (RF-CMP-027), porque comparar com a anterior é o que dá sentido à cadência. Com isso a report_job deixa de ser evolução e passa a ser escopo (ADR-021, E.2). Duas consequências ficam escritas porque já se sabe onde elas doem: a geração automática NÃO publica sozinha e não ganha caminho próprio pelos gates de honestidade, senão a automação vira a rota de fuga de todas as guardas (RF-CMP-028, RN-017 item 4); e o anti-fadiga fica mais pressionado numa cadência curta e automática, o que torna a sua correção para os quadrantes de dado contra texto um PRÉ-REQUISITO da agenda e não uma melhoria paralela (RN-017 item 9).

{Q4} fica respondida por consequência: o relatório compara períodos, e a comparação é automática na edição agendada. As duas guardas de honestidade da comparação seguem valendo de qualquer forma, como já valiam: comparação entre janelas de larguras diferentes é recusada ou sai marcada como parcial (RF-CMP-021), e métrica que não se soma entre janelas usa o pico e nunca a soma.

{Q5b} foi RESPONDIDA em 2026-08-03: não substitui nada por enquanto. A expressão "por enquanto" faz parte da decisão e está preservada: ela é reversível, não permanente. A consequência vigente é que os dois sistemas convivem e que o ModularReports não herda automaticamente cada mudança que o outro receber; um bloco novo que nasça lá entra aqui por decisão e por cartão, com o mesmo critério de grau de confirmação por uso do ADR-017, e nunca por espelhamento (RN-019, ADR-024). Enquanto esta for a decisão vigente, nenhuma migração de links ou de acervo é planejada.

{Q5c} foi RESPONDIDA em 2026-08-03, e a resposta tem duas partes que só valem juntas. O critério: por enquanto, o sistema novo recebe os relatórios montados à mão, e o caminho antigo segue com o resto. O peso que o dono deu a esse critério: ele não importa muito. A segunda parte é requisito tanto quanto a primeira, porque é ela que diz o tamanho da solução: nenhum mecanismo de roteamento é construído (RN-019 item 4). Sem esse registro, a primeira metade sozinha convida alguém a implementar um seletor de sistema que ninguém pediu.

Uma decisão de produto segue no Questionário. {Q8}: a camada de resultado do Ap.10 contém números comerciais da agência (o fee cobrado, a margem de contribuição e os lucros derivados deles); eles aparecem na página que o cliente abre, ou são só de gestão interna? Enquanto a resposta não vem, o default escrito no Ap.10 é o seguro: essas métricas nascem com visibilidade interna e não entram no snapshot publicado (RF-REN-017), e afrouxar isso é gesto explícito do admin da agência.

A forma da cadência está CONFIRMADA pela resposta de {Q4b} e deixa de ser recomendação condicionada: a cadência é campo explícito na instância (com valor default vindo da conta e sugestão pela largura do período, nunca inferida em silêncio) e o type continua fechado nos três propósitos, porque propósito e cadência são dimensões ortogonais e cruzá-las num enum único produziria quinze tipos, quase todos natimortos. A resposta do dono reforça essa forma em vez de a contradizer: a agenda é criada para o par (cliente, propósito) e carrega a cadência como campo dela, que é exatamente a separação que RN-014 já descrevia. O peso do argumento continua no modo de falhar: o modelo errado aqui não quebra, ele produz saída VÁLIDA NA APARÊNCIA (selos "sem base" silenciosos justamente para o cliente que tem doze meses de histórico), que é a pior classe de erro porque ninguém a reporta. RN-014 e ADR-016 passam a valer como decisão.


10. Operação em Produção

K.1 Deploy

  • Leitor público e container de parse isolado têm deploy independente da fila de release da plataforma-mãe (RNF-DISP-001).
  • O chrome do operador e a composição in-process acompanham a fila de release do Hub. O orquestrador de contêineres da plataforma-mãe é docker Compose.

K.2 Retenção e limpeza

  • Artefato cru apagado por TTL após o publish (RF-ING-004).
  • Retenção de snapshots, comentários e analytics configurável pelo admin; offboarding revoga e elimina (RF-ADM-011).

K.3 Auditoria

  • Toda ação relevante grava evento no log do Hub por ponto único (RF-ADM-012).
  • Duração de produção medida por relatório (RF-ADM-015).

K.4 Monitoramento de adoção

  • Canário semanal: percentual dos relatórios reais da semana que saíram pelo Reports (A.7).

11. Configurabilidade

Todo valor operacional que o admin possa querer mudar tem UI de edição no painel, sem exigir migration ou deploy (admin-editability-first):

ConfiguraçãoEscopoDefault
Quota de entrada (nº de artefatos, MB, páginas de PDF)Por agênciaA definir na Fatia 4, admin-editável
TTL do artefato cruPor agênciaCurto (minimização LGPD), admin-editável
Retenção de snapshots, comentários e analyticsPor agênciaAdmin-editável
Allowlist de fontes de benchmark e pesquisa de mercadoGlobal e por agênciaAdmin-editável
Identidade padrão da agência (fallback de identidade do cliente)Por agênciaDeclarada pelo admin
Templates (tipos de relatório e blocos padrão)Individual por agência, ou Global (RN-013)CRUD pelo admin e pelo operador na própria agência; marcar e desmarcar como Global só pelo Super Admin (RF-ADM-009, RF-ADM-016)
Teto de verbosidade por blocoGlobalDefinido no schema, revisável
Tolerância de largura de janela de comparaçãoPor agência10 por cento (RF-CMP-021)
Desfecho da comparação entre janelas parciaisPor agênciaEmitir com marca de janela parcial (RF-CMP-021)
Métricas não somáveis entre janelasPor agênciaAlcance, usuários únicos, visitantes únicos (RF-CMP-022)
Base sazonal da cadência mensalPor agênciaDesligado (RN-014 item 4)

12. Automação e Inteligência Artificial

M.1 Papel da IA

A IA compõe a narrativa do relatório sob curadoria humana. Ela nunca produz números (extração determinística com proveniência) e nunca publica (só uma sessão humana com a capability reports.publish). A composição é síncrona in-process, reusando o gateway de IA da plataforma-mãe, sobre o dado já saneado pelo parse isolado; uma fila assíncrona é evolução.

M.2 Cadeia de modelos

A composição reusa o gateway de IA da plataforma-mãe (proibido client de IA próprio) com a cadeia 100% gratuita (4 provedores ou modelos gratuitos encadeados por prioridade, cada um com cota diária própria, RF-CMP-011). Não há upgrade para modelo pago em silêncio; se a qualidade não passar na rubrica, a decisão volta com números (RF-CMP-011).

M.3 Guardas de qualidade e segurança

  • Proveniência obrigatória e gate de publicação (RF-CMP-006, RF-CMP-007).
  • Verificação de polaridade (RF-CMP-008).
  • Benchmark só da allowlist com fonte (RF-CMP-014).
  • Cap de verbosidade (RF-CMP-015).
  • Anti-fadiga por variação do dado contra variação do texto, com escalada de recomendação repetida (RF-CMP-016).
  • Régua de confiabilidade derivada por código, sem IA (RF-CMP-023).
  • Base da comparação declarada dentro da frase (RF-CMP-024).
  • Tupla de identidade completa do número contra o que a frase afirma (RF-CMP-025).
  • Conectivo causal degradado para coocorrência sem evidência designada (RF-CMP-026).
  • Guarda de largura de janela e métrica não somável (RF-CMP-021, RF-CMP-022).
  • Saída legal na recusa e escotilha registrada (RF-CUR-009).
  • Fixture de controle negativo por guarda na integração contínua (RNF-MANUT-003).
  • Tom de voz por cliente (RF-CMP-013).
  • Fail-loud "o que não achei" (RF-CMP-003).
  • Pesquisa de mercado isolada e opcional, com fonte nomeada (RF-ING-006).
  • Rubrica 5D com juiz calibrado (Ap.4).

M.4 Custo e medição

Cada composição registra custo (tokens) e tempo (RF-CMP-012), mesmo sem billing, como teto numérico de acompanhamento.

Risco de custo de JULGAR: RETIRADO em 2026-08-03, porque o número que o sustentava era falso. Este parágrafo registrava que julgar um relatório de 25 blocos custaria 75 chamadas de modelo (25 blocos vezes 3 passadas) e que isso comeria a cota da produção. A premissa foi medida no código e é falsa. A rubrica nunca julgou bloco a bloco: ela extrai as unidades narrativas de todos os blocos, junta tudo num texto único e julga o DOCUMENTO INTEIRO numa chamada, repetindo essa chamada 3 vezes para tirar a mediana. O custo é de 3 chamadas por relatório, independente do número de blocos, e não cresce com o tamanho do documento. Um relatório de 25 seções custa as mesmas 3 chamadas que um de 7. O risco descrito não existe e o número correto fica registrado aqui no lugar dele. O que continua verdadeiro e não depende deste erro: a cota gratuita é finita, ela já se esgotou uma vez durante a Fatia 1 (99.492 de 100.000 tokens diários num dos provedores), e a decisão de subir para modelo pago nunca acontece em silêncio (RF-CMP-011, RF-CMP-020).


13. Aprendizado Pós-Lançamento

N.1 Métrica de adoção

Canário semanal (A.7) e duração de produção por relatório (RF-ADM-015) medem se o Reports está de fato substituindo o fluxo manual.

N.2 Diferencial de assinatura

A honestidade estrutural (fail-loud "o que não achei" mais origem por número persistida no relatório publicado) é o traço de produto que nenhum concorrente cobre. Acompanhar se é percebido como diferencial pelos clientes.

N.3 Evolução

  • Automação de envio do link (hoje manual, RF-PUB-007) pode entrar como evolução.
  • Analytics por bloco (scroll, tempo) diferido para depois das visualizações e último acesso.
  • Suporte a imagem como fonte de dados condicionado ao protótipo (RF-ING-001, RF-CMP-005).
  • Investigação aberta, corte por tipo de negócio (leadgen contra e-commerce, Ap.9). Medir, em relatórios reais dos dois lados, se o conjunto de métricas, a âncora e a leitura mudam o bastante para justificar tratamento próprio. É investigação e não decisão: o mesmo rigor usado com os tipos de bloco (contar em relatórios reais antes de registrar como necessidade) vale aqui.
  • Cobertura do relatório periódico recorrente. A mineração de padrões só alcançou o que existe como artefato formal publicado; o periódico recorrente sai hoje em planilha e em slide e por isso não apareceu, apesar de ser a maior parte do volume. Levantar exemplares reais dele nesses formatos e reextrair o padrão.

Apêndices

Ap.1 Briefing canônico do wizard

O passo 2 do wizard coleta:

CampoDescriçãoObrigatório
KPI principalA métrica que ancora a leitura (ex.: conversas no WhatsApp, vendas)Sim
DireçãoObjeto estruturado (ver campos abaixo) que substitui a antiga "intenção" livreSim
DestaquesCampanhas ou criativos estrelaNão
Identidade do relatórioTítulo e identidade visual do clienteSim (título)
TipoTemplate (tipo de relatório)Sim

O campo Direção é um objeto com:

Campo da direçãoDescriçãoObrigatório
objetivo_campanhaO objetivo da campanha como VALOR FECHADO (enum: vendas, leads, trafego, reconhecimento, engajamento, o vocabulário das próprias plataformas de mídia). NÃO é texto livre e NÃO é a tese: é a chave que deriva a métrica-âncora do selo de honestidade pelo mapa objetivo para métrica primária (RN-012 ii, ART-CFG). O operador escolhe o objetivo da campanha (fato do trabalho dele); a MÉTRICA-ÂNCORA que sai dele é derivada pelo mapa, nunca escolhida diretamenteSim
objetivoTese em 1 linha, alimenta o hero (RF-CMP-005, Ap.5 item 9); texto livre, distinto do objetivo_campanhaSim
publicoPúblico a quem a leitura se dirigeNão
decisao_alvoO que o leitor decide a partir do relatórioSim
tomHerda da ficha de identidade (Ap.6); sobrepõe pontualmente quando preenchidoNão
enfatizar[]Quais dp: sobem por metric_id, nunca reescrevendo o valorNão
evitar[]Quais dp: descem por metric_id, nunca reescrevendo o valorNão
blocos_preferidos[]Pesos ou ordem sobre o contrato v3 (Ap.2), dentro dos tipos do Ap.3, nunca um bloco novoNão
lacunas_conhecidas[]Alimenta o fail-loud de lacunas (RF-CMP-003)Não

Ap.2 Contrato de bloco v3

  • Tudo é blocks[]; cada bloco: { id, type, hidden?, tags?, props }.
  • id: identificador estável, nunca regenerado; âncora de comentário, de analytics e do atributo data-block-id no DOM.
  • Reordenar e esconder no nível do documento; publicar remove os blocos ocultos do snapshot.
  • Proveniência: registro data_points no raiz do documento; cada campo numérico de props referencia o ponto de dado por { "dp": "<dp_id>" } no lugar do valor. Schema do ponto de dado, formato do binding e definição operacional de "número exibido": Ap.8.
  • share_card no raiz: { og_title, og_description, cover_asset_id }, editável no publish (RF-PUB-006) e copiado para o snapshot.payload no ato (Ap.7).
  • Seções semânticas (Resumo, Vitórias, Problemas, Recomendações): tags, não tipos.
  • Autoria do bloco (RF-CUR-010): tags carrega o valor reservado operator-inserted no bloco que o operador inseriu por gesto próprio (hoje o criativo, RF-CUR-003). É marca de autoria, não seção semântica, e é o que faz o bloco sobreviver a qualquer reescrita de blocks[]. O id desse bloco é cunhado em espaço de nomes distinto do que a composição usa, de modo que colisão de id entre as duas autorias é impossível por construção.
  • schema_version: 3; leitor converte em leitura por funções puras; tipo desconhecido é ignorado em silêncio pelo leitor e sinalizado no compositor.
  • Vocabulário único de "não medido" (RF-REN-015): a ausência de medição tem UM significado e UMA representação em todo o contrato, seja qual for o bloco. Hoje três blocos a expressam de três jeitos (funnel_simple.steps[].dashed, o bloco gap inteiro, e o ponto nulo de line_chart.series[].points[]), e três jeitos para o mesmo conceito é como o leitor aprende a ignorar um deles. O vocabulário unificado: o valor é NULO (nunca zero, nunca string vazia), o render é interrompido (traço, etapa tracejada, célula vazada, nunca uma queda a zero desenhada), e o motivo é declarado uma vez no bloco gap. Um bloco novo que precise expressar não medido usa este vocabulário e não inventa o quarto jeito.
  • Histórico mínimo por seção (RF-REN-014): um bloco cuja leitura exige N períodos declara min_periods; abaixo de N a seção se OCULTA sozinha, em vez de renderizar um gráfico de dois pontos ou um ranking sobre 15 dias. Seção sem massa de dados não fica curta, fica falsa, e uma seção falsa custa mais credibilidade do que a seção ausente.
  • base obrigatório em todo bloco que compare dois números de origens diferentes (hoje só comparison, RF-CMP-024), renderizado de forma visível e nunca só em metadado.
  • Corpo body_md sanitizado com allowlist em todos os consumidores; body_md é só narrativa e não pode conter dígito que represente métrica (número vai em props com dp, Ap.8). A mesma regra vale para TODO campo de texto que a ficha do Ap.3 marque como editável pelo operador, e não só para o campo que se chama body_md: o perímetro é derivado da ficha, nunca uma lista escrita à parte (RF-CUR-011).
  • Whitelist de campos editáveis por tipo (Ap.3); cap estrutural de verbosidade.
  • style_overrides no bloco (opcional, aditivo, v3 retrocompatível): as 4 propriedades fechadas do ajuste fino de apresentação (RF-CUR-008), paddingStep, gapStep, fontSizeStep, buttonAccentStep, schema additionalProperties:false; um bloco sem style_overrides renderiza igual a antes.

Ap.3 Catálogo de tipos de bloco

Tipos herdados dos relatórios reais, base do registro declarativo (extensível, tipo novo é um componente mais uma entrada no registro). São dezesseis, e a lista é nominal e fechada (RF-REN-012), não ilustrativa. Os 9 primeiros vêm do renderizador v2 já em produção (transcritos das interfaces SectionV2 e dos componentes); comparison e media são novos do v3; section_intro, reliability, line_chart, donut e action_plan entraram em 2026-08-02 pela auditoria dos relatórios já entregues.

Procedência dos quatro tipos absorvidos, registrada para não ser relida como validação histórica. line_chart, donut e action_plan nasceram em 2026-07-31, no renderizador irmão que serve as páginas de resultados da operação hoje, e nasceram de forma reativa: um relatório real precisou daquele desenho e o componente foi escrito naquela semana. Eles não vieram de auditoria do acervo histórico. O que a auditoria fez, depois, foi contar em quantos relatórios reais cada um passou a ser usado, e é essa contagem que aparece na tabela abaixo. A distinção importa porque um tipo recém-nascido em sistema irmão tem menos evidência acumulada do que a contagem sozinha sugere, e a leitura otimista desse número é justamente o erro que este parágrafo impede. section_intro e reliability são a decomposição de um único tipo do sistema irmão, e a decomposição é decisão de produto deste sistema, não herança.

Absorção com contrato reescrito, nunca clonado. Os campos abaixo são o contrato v3, não a transcrição do componente irmão. Três campos que existem lá entram aqui como exclusão deliberada, registrada na subseção seguinte. Para cada tipo, a ficha de props: o campo, o tipo, se o campo é numérico (e portanto carrega {dp} em vez do valor cru, Ap.8), se é editável no compositor (RF-CUR-005), e o cap de verbosidade. Campos numéricos com dp nunca são editáveis à mão (RF-CUR-005, RF-CMP-007).

Três campos do renderizador irmão deliberadamente EXCLUÍDOS do contrato v3. Eles existem lá por acidente de construção, e copiá-los reintroduziria defeitos que este sistema já fechou. A exclusão é nominal para que ninguém a interprete como esquecimento e a "conserte" de volta.

Campo do irmãoPor que NÃO entraO que entra no lugar
line_chart.y_formatDuas autoridades de formato para o mesmo número. O v3 já carrega o tipo e o formato no próprio ponto de dado (Ap.8), então um formato paralelo no bloco pode divergir do que a proveniência declara. Divergência entre o rótulo e o dado é exatamente o gerador do defeito de coerência de rótulo que RF-CMP-025 fechaO formato vem só do dp:. Bloco nenhum declara formato de número
cor hexadecimal livre em donut.slices[]Cor solta escapa dos tokens de identidade do cliente (Ap.6) e do clamp de contraste AA (RNF-A11Y-001); uma paleta digitada à mão não tem como garantir legibilidade nem coerência com a marcaslices[].accent_index, um inteiro que indexa a rampa de acento derivada da identidade do cliente. Fora da rampa não existe
intro.body_htmlHTML cru é escape do saneamento com allowlist que todo consumidor aplica (Ap.2); um único campo com HTML livre anula a barreira em todas as superfícies de uma vezbody_md, texto sanitizado com a mesma allowlist dos demais corpos narrativos

Grau de confirmação por uso (ADR-017). Um tipo de bloco só é "necessidade validada" quando relatórios já entregues a cliente o usam; o resto é aposta, e a diferença fica escrita e não implícita. Universo contado: as 8 linhas da tabela de relatórios do acervo, das quais 6 carregam o formato v2 com lista de seções (as outras 2 são de formato anterior a v2 e não têm lista de seções: um fechamento periódico multi-vertical e uma auditoria de marca, que não é relatório de mídia). Critério de contagem: o valor de type efetivamente presente na lista de seções de cada relatório publicado, lido do próprio conteúdo do relatório, não busca textual em código. Placar dos 6 v2: 4 diagnósticos de herança mais 2 fechamentos de campanha, e 4 mais 2 fecham em 6.

TipoRelatórios reais que o usamLeitura
hero, kpis0 de 6 no formato v2herdados do renderizador anterior, mantidos por contrato
funnel_simple, stat_cards, video_retention, rankings, data_table, insights, gap6 de 6necessidade validada, universais no acervo
section_intro4 de 6 (os 4 diagnósticos de herança)necessidade validada; nos 4 relatórios o primeiro dos dois blocos de abertura era contexto em prosa
reliability4 de 6 (os 4 diagnósticos de herança)necessidade validada; nos 4 relatórios o segundo bloco de abertura era a régua de confiabilidade, digitada à mão pelo redator
line_chart4 de 6 (os 4 diagnósticos de herança)necessidade validada
donut4 de 6 (os 4 diagnósticos de herança)necessidade validada
action_plan4 de 6 (os 4 diagnósticos de herança)necessidade validada; sempre o penúltimo bloco, antes do gap
media0 de 6 diretamente, e necessidade confirmada por contornoos 2 fechamentos de campanha montaram uma tabela intitulada "Anúncios veiculados", com coluna de anúncio em texto, quando o que o operador queria mostrar era o criativo; o contorno é a evidência da falta
comparison0 de 6, e nenhum contorno observadoaposta ainda não confirmada por uso, com prioridade rebaixada e data de corte. Está no registro e no contrato porque planejado versus realizado é decisão de produto (RF-BND-003, RF-ING-007), não porque algum relatório entregue tenha pedido. Constrói-se depois dos quatro tipos com uso comprovado, e morre por condição escrita (ADR-018) se o uso não aparecer

O lado negativo do placar foi lido por inteiro, não amostrado: os 2 fechamentos de campanha têm a sequência completa funnel_simple, stat_cards, video_retention, rankings, data_table, insights, gap, e nenhum dos dois contém section_intro, reliability, line_chart, donut ou action_plan, o que é coerente com a explicação de operação (não havia histórico contra o qual comparar naqueles dois). Contagem medida em 2026-08-02.

hero (tese editorial, sem métrica crua no título; SINGULAR por regra, no máximo um por documento, RF-REN-013: o hero carrega o período e a fonte que o documento inteiro cobre, então dois hero significariam dois períodos e o documento se contradizendo sobre o que ele cobre; quem quiser dividir o documento em partes usa section_intro, que é repetível de propósito):

CampoTipoNumérico (dp)?Editável?Cap
eyebrowtextonãosimcurto (rótulo)
titletextonãosimtese (1 linha)
subtitletexto opcionalnãosimcurto
descriptiontextonãosim1 parágrafo
periodtextonãosimcurto
sourcetextonãosimcurto

kpis (cartões de indicadores, lista de itens):

Campo do itemTipoNumérico (dp)?Editável?Cap
labeltextonãosimcurto
valuenúmerosim (dp)não (proveniência)valor formatado
foottexto (nota)nãosimcurto
herobooleano opcionalnãosimdestaque do cartão

funnel_simple (caminho etapa por etapa, com etapa de lacuna marcada):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrow, descriptiontextonãosimeyebrow curto, description 1 parágrafo
steps[].statnúmero (o VOLUME da etapa)sim (dp)nãovalor formatado
steps[].numrótulo de ordemnãosimcurto
steps[].label, steps[].subtextonãosimcurto
steps[].color_var, steps[].dashedtoken de cor, booleano (marca lacuna)nãosim (config)N/A
steps[].unit_costnúmero (custo unitário da etapa, RF-CMP-030)sim (dp)nãovalor formatado
steps[].conv_from_prevnúmero (taxa de conversão vinda da etapa anterior, RF-CMP-030)sim (dp derived)nãopercentual formatado
sale_modelvalor fechado de dois (consultiva, direta) ou ausentenãonãoN/A
summary[].valuenúmerosim (dp)nãovalor formatado
summary[].label, summary[].notetextonãosimcurto

Os três campos acima marcados com RF-CMP-030 e o sale_model são aditivos e entraram em 2026-08-03 (ADR-022, ADR-023). Um bloco funnel_simple sem sale_model é o funil LIVRE de sempre: número de etapas à escolha do operador, rótulos montados por ele, unit_cost e conv_from_prev ausentes, e ele renderiza exatamente como renderizava. Um bloco COM sale_model é o funil CANÔNICO de RF-REN-016: quatro etapas na ordem fixa, rótulos vindos do Ap.10 e a tripla completa por etapa. O sale_model é copiado do cadastro do cliente no ato da composição e nunca editado no compositor, para que o rótulo de um snapshot publicado não mude quando o cadastro mudar depois. Nenhum tipo de bloco novo nasceu para isto (ADR-023).

stat_cards (cartões de estatística):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrow, descriptiontextonãosimdescription 1 parágrafo
cards[].labeltextonãosimcurto
cards[].valuenúmerosim (dp)nãovalor formatado

video_retention (curva de retenção):

CampoTipoNumérico (dp)?Editável?Cap
title, descriptiontextonãosimdescription 1 parágrafo
rows[].labeltextonãosimcurto
rows[].valuenúmerosim (dp)nãovalor formatado
notetextonãosimcurto

rankings (classificação):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrow, descriptiontextonãosimdescription 1 parágrafo
cards[].labeltextonãosimcurto
cards[].valuenúmero (usa dp) ou rótulo textual (ex.: "1o lugar", sem dp)sim quando numériconãovalor formatado; rótulo textual de classificação não é métrica medida e não carrega dp

data_table (detalhamento em tabela):

CampoTipoNumérico (dp)?Editável?Cap
title, description, notetextonãosimdescription 1 parágrafo
columns[] (key, label, align, format, render)definição de colunanãosim (config da coluna)N/A
rows[] (células)célula, número onde a coluna é numéricasim (dp por célula numérica)não nas células numéricasvalor formatado

insights (leitura da operação, texto):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrowtextonãosimcurto
items[].titletextonãosimcurto
items[].bodytexto (narrativa)não (métrica citada resolve por dp, nunca dígito cru)simaté 280 caracteres

gap (o que não foi medido e o plano, fail-loud):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrow, descriptiontextonãosimdescription 1 parágrafo
items[]texto (lacunas)nãosimitem curto
proposals[]texto (plano)nãosimitem curto

comparison (novo no v3, planejado versus realizado ou versus período anterior). Este bloco faz DOIS trabalhos com o mesmo desenho, e os dois têm fonte diferente (a meta importada contra o artefato do período anterior), modo de ausência diferente (meta não cadastrada contra histórico inexistente) e semântica de honestidade diferente (falhar contra uma promessa contra variar contra um mês). Sem discriminador, o leitor vê "queda de 18 por cento" sem saber se é contra uma promessa ou contra o mês passado, que é número verdadeiro com rótulo errado, a mesma família de defeito que RF-CMP-025 fecha. Por isso base é obrigatório e renderizado de forma visível, não metadado (RF-CMP-024):

CampoTipoNumérico (dp)?Editável?Cap
title, descriptiontextonãosimdescription 1 parágrafo
baseenum fechado obrigatório: meta, periodo_anteriornãonão (derivado da origem do dado)renderizado no cabeçalho do bloco e em cada linha, nunca só em rodapé
rows[].metricatextonãosimcurto
rows[].planejadonúmero (meta ou valor do período anterior, conforme base)sim (dp, de report_goal ou do artefato anterior)nãovalor formatado
rows[].realizadonúmerosim (dp, do artefato)nãovalor formatado
rows[].variacaonúmerosim (dp derivado)nãovalor formatado; herda a marca de janela parcial de RF-CMP-021 quando aplicável
min_periodsinteiro (RF-REN-014)nãosim (config)só faz sentido com base: periodo_anterior; default 2

Condição de morte deste bloco, escrita com data e critério (ADR-018): se em 2 ciclos reais de adoção depois da entrega da Fatia 11 nenhum relatório real usar comparison, o tipo é cortado do registro e do contrato. Construir para uso futuro só se justifica quando existe a data em que a aposta é liquidada; sem ela, o registro acumula tipo morto para sempre e cada tipo morto custa manutenção, superfície de teste e um item a mais na lista que o compositor precisa considerar.

media (novo no v3, criativo ou imagem de anúncio):

CampoTipoNumérico (dp)?Editável?Cap
asset_idreferência de assetnãonão (troca por seleção, não à mão)N/A
sha256hashnãonãoN/A
captiontexto (narrativa)não (métrica citada resolve por {{dp:id}}, nunca dígito cru, RF-CUR-011)simlegenda curta

section_intro (divisor de seção em prosa; 4 de 6 relatórios reais). É divisor puro e repetível: abre uma parte do documento e não afirma nada sobre o documento inteiro. Por isso não tem period e não tem source, e a ausência dos dois é deliberada, não omissão: período e fonte pertencem ao hero, que é singular (RF-REN-013). Um section_intro que declarasse período abriria a porta para dois períodos conflitantes no mesmo documento, que é o defeito que a singularidade do hero fecha:

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrowtextonãosimeyebrow curto, title 1 linha
body_mdtexto (narrativa)não (métrica citada resolve por {{dp:id}}, nunca dígito cru)simaté 3 parágrafos

reliability (régua de confiabilidade do relatório; 4 de 6 relatórios reais). É DERIVADA POR CÓDIGO, não escrita (RF-CMP-023): o sistema já calcula, para compor a página, os três fatos que ela afirma, e hoje um humano os redigita à mão em prosa, que é o padrão "correto enquanto alguém lembrar". Aqui vira garantia de máquina. Os três fatos, cada um com o seu dono já existente no sistema:

CampoTipoNumérico (dp)?Editável?Cap
sources[]lista derivada: quais fontes de dado alimentaram este relatório (os artefatos que parsearam com sucesso, RF-ING-003)nãonão (derivado)N/A
gaps[]lista derivada: quais blocos esperados viraram lacuna e por quê (a mesma origem do bloco gap, RF-CMP-003)nãonão (derivado)N/A
comparison_basederivado: a base de comparação da âncora, ou o estado "sem base" (RN-012 item 2, RN-015)nãonão (derivado)N/A
body_mdtexto gerado a partir dos três campos acima por template determinístico, sem IAnãonãoaté 2 parágrafos
notetexto opcional do operador, acrescentado ao lado, nunca por cimanãosimcurto

O bloco é não editável porque editável ele volta a ser prosa que pode divergir do dado: um operador que apague "sem base" do texto produz uma página que afirma comparabilidade que o sistema sabe não existir. O operador que discorda acrescenta uma note, e a régua derivada continua ao lado dela.

line_chart (série no tempo; 4 de 6 relatórios reais). Sem y_format: o formato de cada número vem do dp: e de lugar nenhum mais (ver a tabela de campos excluídos acima):

CampoTipoNumérico (dp)?Editável?Cap
title, description, notetextonãosimdescription 1 parágrafo
x_labels[]texto (rótulo do eixo, tipicamente o período)nãosimcurto
series[].labeltextonãosimcurto
series[].points[]número ou nulosim (dp por ponto quando há valor)nãovalor formatado
series[].axisenum fechado: left, rightnãosim (config)séries de grandezas diferentes não dividem o mesmo eixo; duas séries de grandezas diferentes com axis igual é erro de composição
min_periodsinteiro (histórico mínimo exigido pela seção, RF-REN-014)nãosim (config)default 3; abaixo dele a seção se oculta em vez de desenhar uma reta de dois pontos

Nulo em points[] significa NÃO MEDIDO, e nunca zero: a série se interrompe visualmente naquele ponto (traço, não queda a zero), e o leitor não pode confundir ausência de medição com desempenho nulo. É o mesmo conceito que funnel_simple.steps[].dashed e que o bloco gap já expressam de três jeitos diferentes hoje, e o vocabulário é unificado por RF-REN-015.

donut (composição de um todo em partes; 4 de 6 relatórios reais). Sem cor hexadecimal livre nas fatias (ver a tabela de campos excluídos acima):

CampoTipoNumérico (dp)?Editável?Cap
title, description, notetextonãosimdescription 1 parágrafo
slices[].labeltextonãosimcurto
slices[].valuenúmerosim (dp)nãovalor formatado
slices[].accent_indexinteiro que indexa a rampa de acento derivada da identidade do cliente (Ap.6)nãosim (config)fora da rampa não existe; o clamp de contraste AA de RNF-A11Y-001 continua valendo
center_label, center_valuetexto e númerocenter_value sim (dp)rótulo sim, valor nãocurto

action_plan (o que fazer a seguir, item a item; 4 de 6 relatórios reais, sempre imediatamente antes do gap):

CampoTipoNumérico (dp)?Editável?Cap
title, eyebrow, descriptiontextonãosimdescription 1 parágrafo
items[].titletextonãosimcurto
items[].bodytexto (narrativa)não (métrica citada resolve por {{dp:id}})simaté 280 caracteres
items[].horizonenum fechado: imediato, curto, medionãosim (config)N/A
items[].ownertextonãosimcurto

Ap.4 Rubrica 5D de qualidade da narrativa

Escala de 1 a 4 ancorada em comportamento; juiz por modelo de linguagem em temperatura 0, 3 passadas e mediana, calibrado com o golden como nota 4 e degradados manuais como nota 1 ou 2. O juiz avalia o DOCUMENTO INTEIRO numa chamada, não bloco a bloco: as unidades narrativas de todos os blocos são extraídas e unidas num texto único antes de julgar, e as 3 passadas são desse mesmo texto. O custo é de 3 chamadas por relatório e não cresce com o número de blocos.

DimensãoO que mede
D1 AcionabilidadeA narrativa aponta uma consequência ou próximo passo, não só descreve o número
D2 Honestidade comparativaToda comparação tem dono nomeado (benchmark citado ou classificação da plataforma)
D3 Lacuna declaradaO que não foi medido aparece como etapa visível, não some
D4 TomA narrativa reflete o tom de voz declarado do cliente
D5 EconomiaSem adjetivo sem número, sem verbosidade

Threshold: média maior ou igual a 3,0, nenhuma dimensão igual a 1, D2 e D3 maiores ou iguais a 3. Spot-check humano 100% na Fatia 1, 20% depois.

Uma premissa deste apêndice foi medida no código e estava errada, e a correção está registrada em vez de apagada. Até 2026-08-03 este apêndice afirmava que a rubrica julgava BLOCO A BLOCO e que a cegueira dela para defeitos de documento inteiro vinha dessa fragmentação. É falso. A medição no código mostra que o juiz extrai as unidades narrativas de todos os blocos, junta num texto único e avalia o documento inteiro numa chamada, 3 vezes para a mediana. Não há fragmentação e nunca houve.

A cegueira observada é real, mas a causa é outra: ela é do ENUNCIADO da avaliação, não da granularidade. As cinco dimensões D1 a D5 perguntam sobre a qualidade da escrita e da ancoragem, e nenhuma delas pergunta sobre a RELAÇÃO entre seções distantes. O juiz enxerga o documento inteiro e mesmo assim não reprova eco entre a seção 4 e a 19, porque ninguém lhe pediu isso. A correção certa, portanto, não é fragmentar nem amostrar: é acrescentar um segundo critério que pergunte o que o primeiro não perguntava.

Segundo critério, coerência de documento (E1 a E3):

DimensãoO que mede
E1 Eco entre seçõesA mesma leitura reaparece em seções distantes com outras palavras, sem acrescentar nada
E2 Contradição entre seçõesDuas seções afirmam o oposto sobre a mesma métrica no mesmo período
E3 Tese sustentadaA tese aberta no hero é retomada e fechada, e não abandonada no último terço

Roda na mesma forma do primeiro critério: uma chamada sobre o documento inteiro, 3 passadas e mediana. O custo total de julgar passa a ser 6 chamadas por relatório (3 de cada critério), e continua não crescendo com o número de blocos. O E1 a E3 reprova por fato verificável (duas seções afirmando o oposto sobre a mesma métrica; a tese do hero sem retomada no último terço), nunca por impressão de estilo.

Resultado da calibração, medido em 2026-08-03. O conjunto de referência ganhou um relatório REAL de 25 seções, entregue a cliente pagante e anonimizado antes do parse (cliente, sub-marcas, cidades, concorrentes, domínio e identificadores de conta trocados; números e estrutura preservados). Veredito desse documento: nota máxima nas cinco dimensões do critério antigo e média 3,33 no critério novo, que apontou eco entre seções distantes. Um documento que a operação entregou e o cliente pagou tira nota perfeita no critério que já existia e perde ponto no que acabou de nascer: é a melhor evidência possível de que o segundo critério faz falta.

E os dois documentos legados do conjunto continuam reprovando, pela dimensão de honestidade comparativa, e passam limpos no critério novo. Isso fecha a dúvida que travava a Fatia 1: comprimento não explica a reprovação. A régua não estava calibrada contra a escala errada, ela é exigente contra a prática antiga da casa, que é exatamente o que o dono decidiu manter.

A série de leituras foi medida em 2026-08-03, e a premissa que estava escrita aqui era falsa. Este parágrafo afirmava que a composição encadeava de 5 a 7 leituras e propunha verificar se elas eram mesmo sequenciais. A medição mostrou DOIS passos, não de cinco a sete, e mostrou também que as 3 passadas da régua de qualidade, que rodavam uma após a outra, eram independentes entre si e não precisavam ser sequenciais: passaram a rodar em paralelo. O resultado está escrito no bloco 7 (G.1), que é onde restrição arquitetural pertence, e a previsão de gargalo que se apoiava no número antigo cai junto com ele. Fica o registro do erro em vez do apagamento: um número de arquitetura entrou neste documento sem nunca ter sido medido, e sobreviveu a várias revisões porque parecia plausível.

Ap.5 Regras de narrativa do redator

Extraídas dos relatórios reais aprovados:

  1. Número no título e leitura no corpo.
  2. Tríade número mais comparação mais implicação na mesma frase.
  3. Métrica traduzida em consequência humana.
  4. Comparação sempre com dono nomeado.
  5. Lacuna vira etapa visível com plano.
  6. Dado desfavorável presente sem drama.
  7. Aritmética refazível pelo leitor (ex.: verba dividida por conversas).
  8. Vocabulário do negócio do cliente (entra o tom de voz).
  9. Hero é a tese da direção (objetivo de RF-CMP-002), nunca métrica crua no título.
  10. Ganho colateral marcado como bônus.
  11. Proibida narrativa genérica: toda afirmação ancora num ponto de dado, num destaque do briefing ou numa comparação nomeada; frase sem âncora é rejeitada na rubrica (Ap.4, D1/D5).

Ap.6 Ficha de identidade visual do cliente

O Reports lê e popula a ficha existente account_identidade_visual do Hub (tipada):

CampoUso
logos[]Logo do cliente no artefato
cores[] (hex validado)Tokens --r-* da página
fontes[]Tipografia do relatório
tom_de_vozParâmetro direto da narrativa da IA
restricoesRestrições de marca

Ficha vazia bloqueia ou usa default declarado da agência com aviso (RF-ADM-006).

A ficha é o segundo elo da cadeia de identidade, não o primeiro: um relatório pode sobrepor para si mesmo a cor de acento e o logo do cliente, e a sobreposição do relatório vence a ficha, que vence o padrão da agência (RF-CUR-004, RN-020). A ficha continua sendo a fonte do cliente; a sobreposição é decisão pontual de uma peça e nunca altera o cadastro.

Ap.7 Modal de publicação e card de compartilhamento

O modal de publicação (onde o link nasce) contém:

ElementoDescrição
URL do linkToken secreto no caminho, copiável
SenhaOpcional, frase de 4 palavras, default sem senha
ExpiraçãoOpcional, default permanente
QR codeDo link
Notanoindex e revogável
Histórico de alteraçõesSomente interno, com aviso "somente a agência vê"
Card de compartilhamentoog:title e og:description editáveis, capa brandada, com preview de como aparece no WhatsApp; grava em share_card (og_title, og_description, cover_asset_id) no raiz do documento e é copiado para o snapshot.payload no publish (Ap.2)

Ap.8 Ponto de dado e binding número para bloco

Este apêndice fecha o mecanismo central do produto: como um número aparece no relatório carregando origem verificável, e como o gate de publicação enumera "número exibido" de forma determinística (RF-CMP-005 a 007, RF-REN-001).

Registro data_points. É um mapa no raiz do documento, { "<dp_id>": { ... } }. Cada entrada:

CampoTipoDescrição
valuenúmero cruo valor extraído, sem formatação
typeenumcurrency, number, percent, ratio ou integer
formattedtextoo valor já formatado para exibição (ex.: "R$ 17.420")
sourceobjetoa origem do valor (abaixo)

source (proveniência obrigatória):

CampoTipoDescrição
kindenumartifact (célula ou campo de um artefato do cliente), external (fonte da allowlist de pesquisa), goal (uma meta importada, de report_goal) ou derived (valor calculado de outros pontos de dado, como uma variação)
artifact_idreferênciao artefato de origem, quando kind = artifact
locatortextoonde no artefato: Planilha1!B12, um json-path, ou "página 3 do PDF" (quando kind = artifact); o id da linha de report_goal (quando kind = goal)
labeltextonome da fonte, obrigatório quando kind = external
formulatextoa operação e os pontos de dado operandos, obrigatório quando kind = derived (ex.: "(realizado menos planejado) dividido por planejado", operandos dp:d2, dp:d1)

Extração e locator por tipo de artefato. A IA nunca informa o valor de um número; ela no máximo PROPÕE, como hipótese, qual locator responde a um slot pedido pelo template. O extrator determinístico por código lê aquele locator direto do byte cru do artefato, e é o valor extraído (nunca o que a IA disse) que popula o value do ponto de dado. Se o locator proposto não existe ou não parseia, vira lacuna (bloco gap, fail-loud, RF-CMP-003), nunca um número inventado. É assim que o ADR-005 se sustenta na prática: a IA aponta onde olhar, o código lê e reverifica. A granularidade do locator por tipo:

Tipo de artefatolocatorMecanismo de extração
Planilha / CSVAba!Célula (ex.: Planilha1!B12)lê a célula pelo endereço
JSONjson-path (ex.: $.campanhas[0].gasto)resolve o path
Markdowntabela N, linha R, coluna C ou âncora de seção mais rótulolê a célula da tabela ou o valor rotulado
PDFpágina P mais âncora que aponta UM valor (rótulo mais posição), não só a páginaextrai o token numérico ancorado; se a página tem vários números e nenhuma âncora resolve um único, é lacuna
Imagemnão é fonte de dp no V1 (entra só como media); OCR não é determinístico o bastante para o ADR-005vira fonte numérica apenas se o protótipo da Fatia 1 provar reverificação confiável, e aí com locator = caixa mais valor reconferido (RF-ING-001, RF-CMP-005)

Binding número para bloco. Todo número que representa uma métrica medida (valor de artefato, meta, benchmark externo ou valor derivado) referencia um ponto de dado, NUNCA aparece como dígito escrito à mão. Em campos estruturados de props, o binding é { "dp": "<dp_id>" } no lugar do valor. Dentro de texto de narrativa (body_md), quando a análise precisa citar o número na frase (as regras de narrativa do Ap.5 pedem número mais comparação mais implicação na mesma frase), o binding é o token inline {{dp:<dp_id>}}, que o renderizador troca pelo formatted. Nos dois casos o número exibido vem de um ponto de dado com proveniência, nunca do texto cru do modelo. Exemplo de um cartão de KPI:

{ "label": "Investimento", "value": { "dp": "d1" }, "foot": "planejado R$ 16.000" }

com

"data_points": {
  "d1": { "value": 17420, "type": "currency", "formatted": "R$ 17.420",
          "source": { "kind": "artifact", "artifact_id": "a3", "locator": "Planilha1!B12" } }
}

Definição operacional de "número exibido" (o que o gate {F9-01} enumera de forma determinística). É todo {dp} em campo estruturado de props, mais todo token {{dp:id}} em qualquer campo de texto que a ficha do Ap.3 marque como editável pelo operador (o body_md e os demais, entre eles a caption de media). O perímetro é DERIVADO dessa ficha e nunca uma lista de campos escrita à parte, porque lista curada não reprova a ausência de nenhum item em particular e deixa passar o campo editável que nasceu depois dela (RF-CUR-011). O gate de proveniência (RF-CMP-007) confere três coisas objetivas: (1) todo {dp} e todo {{dp:id}} referenciado resolve para uma entrada de data_points com source completo; (2) nenhum valor presente em data_points (seu value ou seu formatted) aparece escrito como dígito literal fora de um token em algum desses campos de texto, isto é, nem a IA nem o operador podem reescrever à mão um número que deveria referenciar; (3) qualquer dígito que represente métrica em algum desses campos de texto que NÃO seja um token {{dp:id}} ligado a um dp: existente reprova o publish, mesmo que não coincida com nenhum valor de data_points (fecha o furo do número fabricado sem dp que um prompt editável poderia induzir). Datas, ordinais e faixas de segmentação do briefing continuam livres (não são métrica), e esse critério de métrica é o MESMO em todos os campos do perímetro: endurecê-lo num campo específico, reprovando ali a data ou o ordinal que passa nos outros, é violação de RF-CUR-011 e não zelo. Assim o gate distingue de forma determinística o número-métrica (sempre por token) do dígito-narrativa (livre), sem depender de julgamento.

Comportamento de render com dp não resolvido (antes do publish). No compositor e no preview, um {dp} em props ou um {{dp:id}} em body_md que referencia um id inexistente em data_points é renderizado como um placeholder visível e sinalizado no compositor (o mesmo tratamento de type de bloco desconhecido, Ap.2), nunca como string crua do token nem como número em branco. O gate de publicação (RF-CMP-007) reprova enquanto houver qualquer dp não resolvido, de modo que o estado quebrado só existe no rascunho, jamais no snapshot publicado.

Ap.9 Catálogo de modelos Globais semeados

A biblioteca Global (RN-013, RF-ADM-017) nasce com dois modelos, e não com uma lista imaginada de tipos de relatório. Cada um foi extraído da sequência de blocos de relatórios já entregues a cliente, e cada um declara quantos relatórios reais o sustenta. Os dois Globais são semeados pela operação da plataforma (company_id nulo, sem despromoção, RN-013 item 4). Um terceiro modelo existe, e nasce individual da agência de origem, não Global, pelo motivo escrito abaixo. Ficha completa aqui, cartões do Inventário em {ART-TPL-01} a {ART-TPL-03} (REGRA-INV-2).

Viés de amostra da mineração, registrado para não ser repetido. Os dois modelos confirmados são um diagnóstico de herança e um fechamento de campanha, e os dois são eventuais: acontecem quando uma conta é assumida ou quando uma campanha termina. Eles apareceram na mineração porque existem como ARTEFATO FORMAL, feitos com capricho e publicados. O relatório PERIÓDICO recorrente, que segundo a operação é a maior parte do volume, quase não apareceu, e não por ser raro: é que ele sai hoje em planilha e em slide, formatos que a mineração não alcança. Ou seja, o método encontrou o que estava escrito bonito, não o que acontece mais. A leitura correta do placar (4 mais 2 em 6) é "estes são os padrões formalizados", nunca "estes são os padrões do negócio".

Item de investigação FECHADO em 2026-08-03: o corte leadgen contra e-commerce. Esta investigação estava aberta aqui com a hipótese de que a divisão mais estrutural entre relatórios não era a cadência e sim o tipo de negócio, e de que os dois lados teriam "conjuntos de métricas, de âncoras e de leituras diferentes". O dono apontou a fonte que faltava (a planilha de gestão que a operação usa de verdade) e ela contradiz a parte cara da hipótese: o corte existe, e é estrutural, mas ele NÃO produz duas estruturas. Produz uma só, com vocabulário trocável.

O que a fonte mostra: o funil tem quatro etapas nos dois modelos, sempre na mesma ordem (Atenção, Interesse, Consideração, Ação); cada etapa carrega a mesma tripla nos dois (volume, custo unitário e taxa de conversão vinda da anterior, sem taxa na primeira); e o bloco de resultado é IDÊNTICO nos dois. O que muda é só o rótulo de cada etapa. Os nomes oficiais dos dois modelos, dados pelo dono, são Venda Consultiva e Venda Direta; a planilha ainda os chama pelos nomes antigos, e os corretos são estes.

A consequência é a que este parágrafo pedia e que agora está medida: não se cria tipo de modelo por vertical de negócio. O modelo de venda vira atributo do cliente (RF-ADM-023, RN-018), o funil canônico vira um perfil de funnel_simple (RF-REN-016), e a comparabilidade entre relatórios do mesmo cliente fica preservada, que é justamente o que a fragmentação por vertical teria destruído. Vocabulário e catálogo completo: Ap.10. A leitura que este apêndice pedia para não repetir continua valendo em outro ponto: a fonte é UMA planilha de gestão, não uma amostra de relatórios dos dois lados, então ela estabelece a ESTRUTURA que a operação usa para gerir, e não a frequência com que cada modelo aparece na carteira.

Convenção das sequências: cada nome é um type do Ap.3; x2 significa dois blocos do mesmo tipo em sequência; "em série" significa vários blocos do mesmo tipo em sequência, em número que varia por relatório.

Modelo 1, Diagnóstico de Herança (type de propósito: pesquisa; N igual a 4, confirmado)

CampoValor
Sustentado por4 relatórios reais entregues no mesmo dia, um por conta assumida
Cadência típicapontual (é um diagnóstico de entrada, não se repete por calendário)
Sequência de blocossection_intro, reliability, stat_cards, funnel_simple, line_chart, video_retention, data_table, donut, insights em série, rankings, data_table, action_plan, gap
Regra própriaO segundo bloco de abertura é a régua de confiabilidade, e no v3 ela é o tipo reliability, DERIVADO por código (RF-CMP-023) e não editável. Nos 4 relatórios reais esse parágrafo era digitado à mão e por isso variava de completude entre eles; no v3 ele passa a ser garantia de máquina, sempre presente e sempre coerente com as fontes, as lacunas e a base de comparação daquele relatório
Nota de ordemA ordem exata dos blocos do meio variou entre os 4 relatórios reais; o que se repetiu nos 4 foi a abertura (contexto em prosa mais régua mais stat_cards), a presença de todos os tipos acima, e o fecho (action_plan seguido de gap). O modelo fixa a abertura e o fecho e trata o miolo como ordem sugerida

Modelo 2, Fechamento de Campanha (type de propósito: campanha; N igual a 2, confirmado)

CampoValor
Sustentado por2 relatórios reais do mesmo cliente, duas campanhas distintas, mesmo período
Cadência típicapontual (a campanha define a janela, não o calendário)
Sequência de blocosfunnel_simple, stat_cards, video_retention, rankings, data_table, insights, gap
Regra própriaSem os blocos de série no tempo (line_chart, donut), por motivo de operação e não por esquecimento: nos dois relatórios reais não havia histórico do cliente contra o qual comparar. O modelo nasce assim de propósito; quando houver histórico, o operador acrescenta os blocos ou usa o Modelo 1
Nota de ordemOs 2 relatórios reais têm a sequência idêntica, na ordem acima

Modelo 3, Fechamento Periódico Multi-Vertical (type de propósito: fechamento; N igual a 1, hipótese, e nasce INDIVIDUAL da agência de origem, NÃO Global)

CampoValor
Sustentado por1 relatório real, e ele está no formato anterior ao contrato v3, sem lista de seções tipadas
EstadoHIPÓTESE, não padrão. Uma amostra não estabelece padrão, e a amostra que existe nem sequer está no formato que o sistema usa. A sequência abaixo é a leitura editorial daquele relatório traduzida para os tipos do Ap.3, não a transcrição de uma lista de seções que exista no dado
Escopo de nascimentoIndividual da agência de origem, e não Global. Global é a biblioteca que toda agência vê, e semear nela uma sequência inferida de uma única amostra em formato antigo apresenta uma hipótese a todo mundo como se fosse o jeito certo de fazer. Como individual, ela serve a agência que de fato produziu aquele relatório, sem contaminar as demais
Como vira GlobalQuando aparecer um segundo caso real do mesmo propósito no contrato v3, a sequência é reextraída do dado e o Super Admin promove o modelo a Global (RN-013, RF-ADM-016). Promover é gesto sob demanda do Super Admin, nunca automático por contagem
Cadência típicamensal (o relatório real cobria um mês dentro de um acumulado do ano)
Sequência propostasection_intro, reliability, stat_cards, line_chart, data_table (uma por vertical), insights em série, gap
Condição de promoção a padrãoQuando existirem 2 ou mais relatórios reais deste propósito no contrato v3, a sequência é reextraída do dado e este modelo deixa de ser hipótese. Até lá, o cartão do Inventário carrega o estado de hipótese, e ele não é apresentado ao operador como o jeito certo de fazer

Ap.10 Funil canônico e camada de resultado

Fonte: a planilha de gestão que a operação usa de verdade, apontada pelo dono em 2026-08-03 como a base a seguir. Ela fecha o item de investigação do Ap.9 e sustenta RF-ADM-023, RF-REN-016, RF-CMP-030, RF-CMP-031, RF-REN-017, RN-018 e ADR-022. Cartões do Inventário em {ART-FLD-03} e {ART-CFG-02} (REGRA-INV-2).

Ap.10.1 Vocabulário das quatro etapas por modelo de venda

A estrutura é uma só: quatro etapas, sempre nesta ordem, com a mesma tripla por etapa (volume, custo unitário e taxa de conversão vinda da etapa anterior). A primeira etapa não tem taxa, porque não existe etapa anterior a ela, e essa ausência é estrutural e não é lacuna (RF-CMP-030). O que troca entre os dois modelos é apenas o rótulo.

#EtapaVolume (Venda Consultiva)Volume (Venda Direta)Custo unitário (Consultiva)Custo unitário (Direta)Taxa de conversão
1AtençãoNovos leadsVisitas de páginaCusto por leadCusto por visitanão se aplica
2InteresseLead qualificado por marketingAdição ao carrinhoCusto por lead qualificado por marketingCusto por adição ao carrinhovinda de Atenção
3ConsideraçãoLead qualificado por vendasInício de checkoutCusto por lead qualificado por vendasCusto por início de checkoutvinda de Interesse
4AçãoVendasComprasCusto por vendaCusto por compravinda de Consideração

Ap.10.2 Catálogo da camada de resultado

São vinte métricas, a lista é nominal e fechada (RF-CMP-031), e ela é a MESMA nos dois modelos de venda (RN-018 item 4). Cada entrada declara quatro coisas: a classe de origem, a forma de cálculo quando a origem é derivada, o estado dessa forma, e a visibilidade default (RF-REN-017).

Classe de origem, valor fechado de três: artefato (extraída do arquivo do cliente por locator, Ap.8), parâmetro (importada como parâmetro de negócio do cliente, RF-ING-008, porque é fato da operação dele e não vem da plataforma de anúncios) e derivada (calculada por fórmula nomeada, kind: derived, Ap.8). Só a classe derivada tem estado de fórmula; artefato e parâmetro são definidas pela própria classe.

Estado da fórmula, valor fechado de dois, e a regra dele é dura: conferida (transcrita de uma nota do autor na planilha de gestão) e a conferir (a planilha NÃO define a fórmula). Uma métrica derivada em estado a conferir NÃO é emitida em relatório. Ela existe no catálogo para que a conferência tenha alvo nominal, não para ser usada antes de conferida. Nenhuma forma foi inferida: onde a planilha não define, a coluna diz que não define, e não uma aritmética plausível escrita por conta própria. Uma fórmula inferida não quebra nada, ela produz um número verdadeiro na forma e falso no valor, que é a classe de erro que ninguém reporta, e escrevê-la como proposta apenas empurra a inferência para quem ler depois.

Como um operando resolve, e por que fórmula conferida não basta. Emitir uma métrica derivada exige duas condições, não uma: a fórmula dela conferida E todos os operandos dela resolvidos. Um operando resolve por três caminhos e nenhum outro: uma entrada deste catálogo que já seja emitível; um ponto de dado de mídia já extraído do artefato (nominalmente o investimento total em anúncios e os volumes das quatro etapas do funil, Ap.10.1); ou um parâmetro de negócio importado (RF-ING-008). Um operando que não resolve por nenhum dos três torna a métrica não emitível mesmo com a fórmula conferida, e a métrica sai como lacuna declarada. É o caso de Margem média por cliente, operando do CAC Payback, que a planilha nomeia mas não define em lugar nenhum.

#MétricaClasseFormaEstadoVisibilidade
1Faturamentoartefatoreceita do período, lida do artefato por locatordefinida pela classecliente
2Ticket médioderivadaa planilha não definea conferircliente
3Retorno sobre investimento em anúncios (ROAS)derivadaa planilha não definea conferircliente
4Margem de contribuiçãoparâmetropercentual vigente do negócio do clientedefinida pela classeinterna
5Fee da agênciaparâmetrovalor vigente cobrado no períododefinida pela classeinterna
6Lucro BrutoderivadaFaturamento multiplicado pela Margem de Contribuiçãoconferidainterna
7Lucro operacional do períododerivadaa planilha não definea conferirinterna
8Margem finalderivadaa planilha não definea conferirinterna
9Retorno sobre o investimento em marketing (ROMI)derivada(Lucro Bruto menos Investimento Total em Anúncios) dividido pelo Investimento Total em Anúnciosconferidainterna
10Custo por receita (CPR)derivadao inverso do ROAS: quanto se gasta para faturar uma unidade de receitaconferidacliente
11LTV, o multiplicador de ciclosparâmetronúmero de ciclos de compra do cliente médio, vigente no negóciodefinida pela classeinterna
12Valor de LTV, em dinheiroderivadaa planilha não definea conferircliente
13Lucro bruto unitário de LTVderivadaa planilha não definea conferirinterna
14Lucro líquido unitário de LTVderivadaa planilha não definea conferirinterna
15Lucro bruto do período sobre LTVderivadaa planilha não definea conferirinterna
16Custo real de aquisição (Real CAC)derivadainclui o fee da agência, e a aritmética exata a planilha não escrevea conferirinterna
17Retorno sobre LTV (ROI LTV)derivadaa planilha não definea conferirinterna
18Índice de qualidade do leadderivadaa planilha não definea conferircliente
19Valor em aberto no pipelineartefatosoma das oportunidades abertas, lida do artefato do clientedefinida pela classecliente
20CAC PaybackderivadaCAC dividido pela Margem média por cliente, e o resultado é PRAZO em quantidade de ciclos de compra, nunca em diasconferida, porém não emitível: os dois operandos ainda não resolveminterna

Procedência das quatro formas conferidas. As quatro vieram de notas explicativas escritas pelo autor na planilha de gestão, lidas em 2026-08-03, e não de fórmula de célula: a exportação da planilha não expõe fórmula (zero referências de célula ou funções de soma). São transcrição de texto do autor, e é essa a proveniência declarada. As linhas 6 e 9 saem da MESMA nota, porque a nota do ROMI completa a cadeia dizendo de onde vem o Lucro Bruto. A linha 10 sai da nota que define o CPR pelo exemplo, e é o exemplo que fixa a leitura. A linha 20 sai da nota que também nomeia a unidade do resultado.

Uma tensão registrada para não ser resolvida por conveniência. A linha 10 está conferida e é definida como o inverso do ROAS, enquanto o próprio ROAS, na linha 3, segue a conferir. Isso não é contradição: a nota do autor fixa a aritmética do CPR pelo exemplo numérico, independentemente de como o ROAS venha a ser conferido. Mas se a conferência de {F20-01} apurar para o ROAS uma forma que não seja o inverso do CPR, as duas linhas passam a se contradizer e a divergência tem de ser resolvida contra a planilha, nunca ajustando uma à outra.

Contagem que sustenta este apêndice (medida em 2026-08-03, recontada após as notas da planilha). Universo: 20 entradas, e não as 19 da primeira redação: as notas revelaram o CAC Payback, que não estava na transcrição inicial da lista. O universo cresceu porque a fonte revelou um item, não porque o mesmo conjunto foi recontado. Critério, comportamental e não busca textual: a classe é declarada por entrada, e o estado da fórmula é conferida apenas quando existe nota do autor transcrita.

Placar por classe: 2 artefato mais 3 parâmetro mais 15 derivada, e 2 mais 3 mais 15 fecham em 20. Placar das derivadas: 4 conferidas mais 11 a conferir, e 4 mais 11 fecham em 15. Das 4 conferidas, 3 são emitíveis (linhas 6, 9 e 10, cujos operandos todos resolvem) e 1 não é (linha 20, bloqueada por operando), e 3 mais 1 fecham em 4.

Divergência de contagem registrada, porque ela quase entrou como lei. A conferência da planilha chegou com o número oito para as fórmulas que continuam pendentes, acompanhado de uma lista de dez nomes. Nenhum dos dois é o placar: recontando por classe, são onze. A lista de dez incluía o valor em aberto no pipeline, que é classe artefato e portanto não tem fórmula a conferir, e omitia Ticket médio e ROAS, que são derivadas e a planilha não define. Nove derivadas da lista mais as duas omitidas fecham em 11. O número original foi recontado contra o conjunto, e não adotado.

Por que boa parte não vem da plataforma de anúncios. Três entradas são fatos do negócio do cliente e entram por importação (RF-ING-008): margem de contribuição, fee da agência e o multiplicador de LTV. Toda derivada cuja cadeia de operandos passe por uma delas fica indisponível enquanto a importação não acontecer, e sai como lacuna declarada, nunca como zero (RF-CMP-031). O número exato de derivadas bloqueadas não é cravado aqui de propósito: a cadeia de operandos de 11 das 15 derivadas é justamente o que está a conferir, então qualquer total seria contado sobre fórmulas que ainda não existem. Ele fica determinado quando {F20-01} fechar. O que já está determinado é a dependência: a importação (Fatia 11 e RF-ING-008) é pré-requisito desta camada, não item paralelo.

Ap.10.3 Regras de leitura do dono do negócio

Vêm das notas da mesma planilha, são do dono do negócio e não do sistema, e por isso são regras de LEITURA (o que o número significa), não guardas de código. Entram aqui porque uma camada financeira sem elas é lida errado com números certos.

  1. O retorno sobre anúncios sozinho engana, e é por isso que esta camada existe. É possível ter retorno sobre investimento em anúncios positivo e ainda assim prejuízo, quando a margem do produto é baixa: o anúncio devolve mais faturamento do que custou, e mesmo assim a margem daquele faturamento não paga o custo da mídia. Esta é a justificativa inteira da camada de resultado, e não uma observação lateral. Um relatório que mostre apenas o retorno sobre anúncios pode estar certo em cada número e errado na conclusão.
  2. O LTV deve ser no mínimo três vezes o CAC. É a régua do dono para dizer se a aquisição se paga. Ela é leitura, não trava: o sistema não recusa publicar um relatório abaixo disso, ele mostra o número.
  3. Sinal do ROMI: positivo significa que a campanha pagou os anúncios e gerou lucro; zero significa empate; negativo significa prejuízo, porque a margem do produto não pagou o custo da mídia.
  4. Unidade do CAC Payback: o resultado é prazo de retorno expresso em quantidade de ciclos de compra, nunca em dias. Exibi-lo com sufixo de dias é erro de rótulo, não de cálculo.
  5. LTV é multiplicador, não dinheiro. A linha 11 é um número de ciclos (um LTV igual a 3 significa três ciclos, não três reais) e a linha 12 é o valor em dinheiro, que é uma métrica separada. As duas têm nomes parecidos e unidades diferentes, e trocá-las produz uma frase com número verdadeiro e rótulo falso, que é exatamente o defeito que a tupla de identidade do número exibido ({F18-03}) e a coerência de rótulo (RF-CMP-025) existem para pegar. Nenhuma das duas pode ser exibida com o rótulo da outra, e o dp: de cada uma carrega a própria unidade.

Companheiros da família MDS

Os quatro documentos companheiros deste DRS são materializados a partir desta especificação e publicados junto:

  1. Roadmap de Implementação (roadmap): abre com a ## Planejamento (Fase 0, cards P0- já concluídos na aprovação do DRS, incluindo a descoberta de integração da plataforma-mãe, projeto nasce em 40%), depois a ## Fase M, Mockup Navegável (cards M-NN, mockup já aprovado, vinculado no bloco 6), a Fase F1b (schema de dados, criação das tabelas antes das fatias que as consomem) e as fatias de construção do bloco 9, cada tarefa um card com o critério de verificação e o H1 do RF que cobre.
  2. Inventário de Artefatos (inventory): as tabelas do schema reports, a migration de registro do módulo, os slugs de permissão reports.*, o bucket de storage, o sandbox de parse isolado, o leitor, o ingestor de eventos, os templates por tipo, a ficha de identidade e os assets, cada artefato com a sua ficha técnica (Especificação) implementável.
  3. Jornadas dos Atores (journeys): por função, Super Admin, Admin de agência, Operador e Cliente final, cobrindo compor, curar, publicar, apresentar, comentar e administrar.
  4. Plano de Testes (tests): seção Alfa com um card TA- por RF (usando o H1) mais os gerais (visual, segurança, performance, mobile, regressão) e o trio de sanity; seção Beta com um card TB- por jornada.
Este documento descreve o ModularReports, módulo de páginas de resultados do ModularHub. É a referência única sobre o que o sistema deve fazer.
Documento ModularReports
Metodologia MDS · Modular Development Style · 15 blocos / 86 campos
Publicado por Modulareasy
Site modulareasy.com/metodologias/mds