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-07-24
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). Quatro 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 num pipeline onde 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, e pode apresentar o rascunho em reunião antes de publicar.
  • 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 pontuais ou periódicos (fechamento de campanha, comparativo, trimestral, sazonal).
  • 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.


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, 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 templates, 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, offboarding, chrome
Ingestãoreports-ingest (ING)Upload, quota, parse isolado, TTL, assets, scrap de mercado, importação de metas
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
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.

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: 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 (resultado de campanha, comparativo de anúncios, trimestral, sazonal, etc) com os blocos padrão da agência para aquele tipo; é o gabarito que a IA preenche. CRUD completo pelo painel.
  • 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 editar, duplicar ou excluir o template é possível pelo painel sem tocar em código.
  • Notas: viola-se este requisito se a lista de blocos padrão de um template não puder ser alterada pelo painel. Se a decisão de produto {Q3} (templates de fábrica) for sim, o schema de template já comporta: company_id nulo mais uma flag is_factory; templates de fábrica têm company_id nulo, is_factory verdadeiro, são visíveis a todas as agências para leitura e duplicáveis, e o seed é condicionado à resposta de {Q3}.

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

  • Descrição: o operador salva um relatório existente como um template novo, nomeado, para reuso.
  • 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, E o conteúdo específico do cliente (números, textos) não é copiado para o template.
  • Notas: viola-se este requisito se o template salvo carregar dados do cliente original.

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).
  • 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.

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.

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

  • 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.
  • 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.
  • Notas: viola-se este requisito se o container de parse tiver rede de saída, ou se um dos vetores listados passar. 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).

C.6 Módulo reports-compose

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

  • Descrição: o primeiro passo do assistente coleta o cliente, o período e o tipo (template) do relatório.
  • Prioridade: Essencial
  • Atores: Operador
  • H1: Dado o wizard aberto, Quando o operador seleciona cliente, período e tipo e avança, Então o rascunho é criado vinculado a esse cliente e período com o template escolhido, E não é possível avançar sem cliente e período preenchidos.
  • Notas: viola-se este requisito se o wizard avançar sem cliente ou período.

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

  • Descrição: o segundo passo coleta o briefing canônico: KPI principal, intenção (o que quer mostrar), destaques (campanhas ou criativos estrela) e identidade do relatório (Ap.1).
  • Prioridade: Essencial
  • Atores: Operador
  • H1: Dado o passo de briefing, Quando o operador preenche KPI principal, intenção, destaques e identidade e avança, Então esses parâmetros ficam gravados no rascunho e disponíveis ao pipeline de IA, E o KPI principal e a intenção são obrigatórios para avançar.
  • Notas: viola-se este requisito se o pipeline compor sem o KPI principal e a intenção do operador.

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.

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. 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-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 reprova o job (fail-closed), nunca degrada aberto. 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 reprovando o job.
  • 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 dois provedores gratuitos em sequência (um primário e um fallback), 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.
  • 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.

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 de template

  • Descrição: ao compor o relatório N de um cliente, o relatório N-1 entra no contexto com a instrução de não repetir, apoiado por avaliação de similaridade e por um bloco nativo "versus período anterior".
  • Prioridade: Média
  • Atores: sistema
  • H1: Dado que existe um relatório anterior do mesmo cliente e tipo, Quando o relatório novo é composto, Então a narrativa é comparada por similaridade com a anterior, E uma repetição acima do limite de similaridade é sinalizada para o operador.
  • Notas: viola-se este requisito se dois relatórios consecutivos do mesmo cliente saírem com narrativa quase idêntica sem sinalização.

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).

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.

RF-CUR-004: Ajustar cores, logo e títulos do cliente

  • Descrição: no compositor, o operador ajusta as cores, o logo e os títulos do relatório para o cliente, dentro da whitelist de campos editáveis.
  • Prioridade: Alta
  • Atores: Operador
  • H1: Dado o compositor, Quando o operador troca a cor primária ou o logo do cliente, Então o preview reflete a mudança imediatamente, E o ajuste é gravado para ser copiado no snapshot na publicação.
  • Notas: viola-se este requisito se o ajuste de branding não for refletido no preview ou não for copiado no snapshot.

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.

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.

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.

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 de sessões da agência e do modo apresentar

  • Descrição: as métricas do link excluem as sessões logadas da própria agência e o modo apresentação, para não envenenar o engajamento com a própria reunião.
  • Prioridade: Alta
  • Atores: sistema
  • H1: Dado que um operador da agência abre o link ou apresenta em reunião, Quando as métricas são contadas, Então esses acessos não entram na contagem de visualizações do cliente, E só acessos externos ao link contam.
  • Notas: viola-se este requisito se um acesso da própria agência inflar a métrica de audiência. O leitor público é anônimo, então o sinal vem de um cookie de sessão da plataforma-mãe no mesmo domínio raiz: quando o operador logado abre o próprio link, o cookie de sessão do Hub está presente e o evento nasce com is_agency_session=true; o modo apresentar nunca emite evento de link.

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: 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.

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 por composição (referência de 60 segundos); 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.
  • 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).

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.

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 ||--|| REPORT_DRAFT : "tem rascunho 1:1"
    REPORT ||--|| SNAPSHOT : "tem snapshot unico"
    REPORT ||--o{ ARTIFACT : "usou (insumo, TTL)"
    REPORT ||--o{ COMMENT : "recebe"
    REPORT ||--o{ LINK_EVENT : "gera eventos"
    ACCOUNT_IDENTIDADE_VISUAL ||--o{ REPORT : "brandiza (copiado no publish)"
    CONTACT ||--o{ COMMENT : "autor (logado)"
    REPORT ||--o{ TEMPLATE : "instancia de"
    ACCOUNT ||--o{ REPORT_GOAL : "tem metas (planejado)"
    REPORT ||--o| REPORT_JOB : "estado da composição (evolução)"
    COMPANY ||--o{ AGENCY_DOMAIN : "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, title, period (daterange, para permitir query por período), 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), 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)
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 da agênciaid, company_id, name, type, default_blocks (jsonb), 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 (evolução, opcional no V1)Estado da composição quando ela deixa de ser síncrona (só existe quando a fila entrar)id, report_id, status (queued, composing, done, failed), progress, tokens, duration_ms, error 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

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)

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).

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 16 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
CompositorCompositorcompositor
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.

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.

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 reprova o job (fail-closed); 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-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 (última fatia; o modelo de identidade do cliente já nasce no schema do dia 1)

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

Quatro decisões de produto seguem no Questionário de Elicitação ({Q1} a {Q4}) e não travam a construção das ondas iniciais: como uma agência parceira nova começa, quem libera o acesso do cliente para comentar, se a biblioteca de templates vem com modelos de fábrica, e se um relatório compara períodos.


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)Por agênciaCRUD pelo admin (RF-ADM-009)
Teto de verbosidade por blocoGlobalDefinido no schema, revisável

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 (dois provedores gratuitos encadeados, primário e fallback). 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 de template (RF-CMP-016).
  • 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.


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).

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
IntençãoO que o operador quer mostrar (ex.: foco no público 25 a 34)Sim
DestaquesCampanhas ou criativos estrelaNão
Identidade do relatórioTítulo e identidade visual do clienteSim (título)
TipoTemplate (tipo de relatório)Sim

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.
  • schema_version: 3; leitor converte em leitura por funções puras; tipo desconhecido é ignorado em silêncio pelo leitor e sinalizado no compositor.
  • 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).
  • Whitelist de campos editáveis por tipo (Ap.3); cap estrutural de verbosidade.

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). 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. 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).

hero (tese editorial, sem métrica crua no título):

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úmerosim (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
summary[].valuenúmerosim (dp)nãovalor formatado
summary[].label, summary[].notetextonãosimcurto

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):

CampoTipoNumérico (dp)?Editável?Cap
title, descriptiontextonãosimdescription 1 parágrafo
rows[].metricatextonãosimcurto
rows[].planejadonúmerosim (dp, de report_goal)nãovalor formatado
rows[].realizadonúmerosim (dp, do artefato)nãovalor formatado
rows[].variacaonúmerosim (dp derivado)nãovalor formatado

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
captiontextonãosimlegenda curta

Ap.4 Rubrica 5D de qualidade da narrativa

Escala de 1 a 4 ancorada em comportamento, por bloco; 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:

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.

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 é tese editorial sem métrica crua.
  10. Ganho colateral marcado como bônus.

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).

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 props mais todo token {{dp:id}} em body_md. O gate de proveniência (RF-CMP-007) confere duas 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 no body_md, isto é, a IA não pode reescrever à mão um número que deveria referenciar. Dígitos literais no body_md que NÃO correspondem a nenhum ponto de dado (uma faixa de segmentação vinda do briefing como "público 25 a 34", uma data, um ordinal) são narrativa legítima e não reprovam, porque não são métrica medida. 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.


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