mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
211 lines
23 KiB
JSON
211 lines
23 KiB
JSON
{
|
||
"schema_version": 1,
|
||
"diagram_type": "architecture",
|
||
"meta": {
|
||
"title": "Marca própria — a fachada resolve em runtime, e o PDF de LGPD fica FORA dela",
|
||
"subtitle": "uma imagem Docker serve todas as marcas: banco acima do .env, e três identidades que não se misturam",
|
||
"output": "marca-propria.html",
|
||
"quality_profile": "standard"
|
||
},
|
||
"lanes": [
|
||
{ "id": "config", "label": "Fora do app (kit + .env + GoTrue)" },
|
||
{ "id": "humano", "label": "Telas, portas e quem recebe" },
|
||
{ "id": "escrita", "label": "Escrita (server actions)" },
|
||
{ "id": "banco", "label": "Banco" },
|
||
{ "id": "resolver", "label": "Resolvedor (lib/branding)" },
|
||
{ "id": "saida", "label": "Saídas — com DOM e sem DOM" }
|
||
],
|
||
"mainPath": [
|
||
"adminmarca",
|
||
"updatebranding",
|
||
"platformbranding",
|
||
"instalacao",
|
||
"resolver",
|
||
"css",
|
||
"layout",
|
||
"interface"
|
||
],
|
||
"nodes": [
|
||
{
|
||
"id": "installsh",
|
||
"lane": "config",
|
||
"col": 0,
|
||
"type": "service",
|
||
"label": "install.sh",
|
||
"sublabel": "envq APP_NAME/APP_LOGO_URL/APP_ACCENT_HEX/SUPPORT_EMAIL/RESEND_* (:1392-1414) · a cor tem validador v_hex na entrevista"
|
||
},
|
||
{
|
||
"id": "env",
|
||
"lane": "config",
|
||
"col": 1,
|
||
"type": "service",
|
||
"label": "lib/env.ts — .env",
|
||
"sublabel": "semente e PISO DE ROLLBACK: agent.sh reverte a imagem, nunca o banco"
|
||
},
|
||
{
|
||
"id": "marcaemails",
|
||
"lane": "config",
|
||
"col": 6,
|
||
"type": "service",
|
||
"label": "hostgator-setup-kit/marca-emails.sh",
|
||
"sublabel": "sem SUPABASE_ACCESS_TOKEN imprime o passo manual e SAI 0 — nunca derruba instalação"
|
||
},
|
||
{
|
||
"id": "gotrue",
|
||
"lane": "config",
|
||
"col": 7,
|
||
"type": "external",
|
||
"label": "GoTrue (Supabase Auth)",
|
||
"sublabel": "templates de confirmação e recuperação — processo de terceiro, nenhum TypeScript nosso roda ali"
|
||
},
|
||
|
||
{ "id": "registry", "lane": "humano", "col": 0, "type": "frontend", "label": "lib/navigation/registry.ts", "sublabel": "a PORTA: /app/settings/marca no grupo Configurações, sidebar:false (tarefa de uma vez)" },
|
||
{ "id": "adminmarca", "lane": "humano", "col": 1, "type": "frontend", "label": "/admin/marca", "sublabel": "marca da INSTALAÇÃO · tira de tons + estado do degrade (fallback_at/fallback_reason)" },
|
||
{ "id": "orgmarca", "lane": "humano", "col": 1, "type": "frontend", "label": "/app/settings/marca", "sublabel": "marca da ORGANIZAÇÃO · admin do tenant, sem platform_admin" },
|
||
{ "id": "telatenant", "lane": "humano", "col": 1, "type": "frontend", "label": "/app/settings/tenant", "sublabel": "campo Razão social — o único lugar onde legal_name deixa de ser cópia do nome fantasia" },
|
||
{ "id": "aba", "lane": "humano", "col": 8, "type": "frontend", "label": "A aba do navegador", "sublabel": "título (generateMetadata) + ícone (/icon) + cor da barra (themeColor)" },
|
||
{ "id": "interface", "lane": "humano", "col": 8, "type": "frontend", "label": "A interface pós-login", "sublabel": "sidebar, logo, botões, anel de foco — tudo pelo accent derivado" },
|
||
{ "id": "painelaudit", "lane": "humano", "col": 8, "type": "frontend", "label": "/admin/audit", "sublabel": "quem repintou isto, e quando — as duas ações estão nas DUAS listas de código" },
|
||
{ "id": "caixaentrada", "lane": "humano", "col": 8, "type": "frontend", "label": "A caixa de entrada do cliente", "sublabel": "convite, confirmação de conta, recuperação de senha, aviso de SLA" },
|
||
{ "id": "autenticador", "lane": "humano", "col": 8, "type": "frontend", "label": "O app autenticador, no celular", "sublabel": "issuer=<marca>. Trocar o issuer NÃO reescreve fator já cadastrado" },
|
||
{ "id": "titular", "lane": "humano", "col": 8, "type": "frontend", "label": "O titular de dados (Art. 18 II)", "sublabel": "recebe o link do export e o relatório — e nele lê o CONTROLADOR, não a marca de quem vende" },
|
||
|
||
{ "id": "updatebranding", "lane": "escrita", "col": 2, "type": "api", "label": "updateBranding (server action)", "sublabel": "gate is_platform_admin + admin client + invalidarMarcaDaInstalacao()" },
|
||
{ "id": "updateorg", "lane": "escrita", "col": 2, "type": "api", "label": "updateMarcaDaOrganizacao (server action)", "sublabel": "RPC atômica: SELECT→spread→UPDATE no mesmo jsonb perderia visibility_mode" },
|
||
{ "id": "rotalogo", "lane": "escrita", "col": 2, "type": "api", "label": "POST/DELETE /api/v1/marca/logo", "sublabel": "gate POR ESCOPO · tipo pelos BYTES (file.type só no details) · 512 KB por arte · claro/escuro opcionais · sobe → grava → só então apaga, com o prefixo asseverado" },
|
||
|
||
{ "id": "bucketlogos", "lane": "banco", "col": 3, "type": "database", "label": "storage/brand-logos (PÚBLICO)", "sublabel": "o 1º bucket público do repo · zero policy em storage.objects · <prefixo>/<uuid>.<png|jpg> · a URL é DERIVADA do caminho, nunca gravada" },
|
||
{ "id": "fnlogoorg", "lane": "banco", "col": 4, "type": "database", "label": "fn_definir_logo_da_organizacao (0158)", "sublabel": "assevera o prefixo contra p_org DENTRO do banco · merge no CAMPO · a 0157 ganhou forward-fix para não apagar logo_path" },
|
||
|
||
{ "id": "platformbranding", "lane": "banco", "col": 3, "type": "database", "label": "public.platform_branding", "sublabel": "singleton id=1 · sem organization_id · revoke de anon (o baseline dá GRANT ALL por default)" },
|
||
{ "id": "orgbranding", "lane": "banco", "col": 3, "type": "database", "label": "organizations.settings.branding", "sublabel": "jsonb com envelope {format, algo, semente_hex} · schema LOOSE: código velho sobre schema novo não pode lançar" },
|
||
{ "id": "legalname", "lane": "banco", "col": 3, "type": "database", "label": "organizations.legal_name + dpo_email", "sublabel": "identidade JURÍDICA — já existia, ninguém usava. DIRC respondeu I (Integrar), não campo novo" },
|
||
{ "id": "auditlog", "lane": "banco", "col": 4, "type": "database", "label": "api_audit_log", "sublabel": "platform_branding.updated (sem org) · org.branding_updated (com org)" },
|
||
|
||
{ "id": "instalacao", "lane": "resolver", "col": 4, "type": "service", "label": "lib/branding/instalacao.ts", "sublabel": "marcaDaInstalacao() · memo TTL 30s · sementeDoAmbiente() · registrarEstadoDaMarca() · NUNCA LANÇA" },
|
||
{ "id": "organizacao", "lane": "resolver", "col": 4, "type": "service", "label": "lib/branding/organizacao.ts", "sublabel": "resolverMarcaDaOrganizacao(organizationId)" },
|
||
{ "id": "resolver", "lane": "resolver", "col": 5, "type": "service", "label": "lib/branding/resolve.ts", "sublabel": "pilha org → instalação → .env → padrão do produto; cor recusada NÃO apaga a camada de baixo" },
|
||
{ "id": "rampa", "lane": "resolver", "col": 5, "type": "service", "label": "rampa.ts + contraste.ts", "sublabel": "1 hex → 11 stops nos 2 temas; piso por PAPEL × SUPERFÍCIE, não pela semente" },
|
||
{ "id": "css", "lane": "resolver", "col": 6, "type": "service", "label": "lib/branding/css.ts", "sublabel": "serialização com allowlist: emite --color-brand e a família do accent — NUNCA --color-bg" },
|
||
{ "id": "saida", "lane": "resolver", "col": 6, "type": "service", "label": "lib/branding/saida.ts", "sublabel": "marcaDaSaida(orgId|null) → um hex + uma frente legível · tema CLARO sempre · NUNCA LANÇA" },
|
||
{ "id": "barra", "lane": "resolver", "col": 6, "type": "service", "label": "lib/branding/barra-do-navegador.ts", "sublabel": "os 2 hexes do themeColor, importados de REGUA_DO_PRODUTO (eram cópia em 3 arquivos)" },
|
||
|
||
{ "id": "layout", "lane": "saida", "col": 7, "type": "frontend", "label": "app/layout.tsx", "sublabel": "<EstiloDaMarca/> no <head> + generateMetadata (título com template) + viewport" },
|
||
{ "id": "applayout", "lane": "saida", "col": 7, "type": "frontend", "label": "app/app/layout.tsx", "sublabel": "<EstiloDaMarcaDaOrganizacao/> escopado em [data-marca-org] — não alcança /login" },
|
||
{ "id": "marcacliente", "lane": "saida", "col": 8, "type": "frontend", "label": "lib/branding/contexto.tsx", "sublabel": "a marca atravessa por PROP do servidor (useMarcaDaInstalacao) — era branding(), que lê window.__PUBLIC_ENV__ no navegador e process.env no SSR: com logo no banco e APP_LOGO_URL vazio, o servidor desenhava <span> e o cliente <img> (React #418)" },
|
||
{ "id": "iconroute", "lane": "saida", "col": 7, "type": "api", "label": "app/icon.tsx", "sublabel": "ImageResponse em runtime · force-dynamic · /icon em PUBLIC_PATHS (sem isso, 307 para /login)" },
|
||
{ "id": "invite", "lane": "saida", "col": 7, "type": "service", "label": "lib/email/templates/invite.ts", "sublabel": "assunto + corpo + botão com a marca de quem convidou" },
|
||
{ "id": "lgpdemail", "lane": "saida", "col": 7, "type": "service", "label": "lib/lgpd/email-delivery.ts", "sublabel": "diz quem PROCESSOU a solicitação — era 'DeskcommCRM' em 100% dos casos, não em borda" },
|
||
{ "id": "slaalarm", "lane": "saida", "col": 7, "type": "service", "label": "lib/lgpd/sla-alarm.ts", "sublabel": "marca resolvida no cabeçalho; o #dc2626 do alerta FICA — é semântica de urgência, não marca" },
|
||
{ "id": "enrollmfa", "lane": "saida", "col": 7, "type": "api", "label": "app/actions/auth/enrollMfa.ts", "sublabel": "issuer (o campo que o celular grava), não friendlyName — medido contra GoTrue v2.188.1" },
|
||
{ "id": "resend", "lane": "saida", "col": 7, "type": "service", "label": "lib/email/resend.ts", "sublabel": "nome de exibição = marca; endereço = do OPERADOR (domínio verificado). Vazio = not_configured" },
|
||
{ "id": "lgpdworker", "lane": "saida", "col": 6, "type": "service", "label": "workers/lgpd-export-worker.ts + export-collector", "sublabel": "monta o ExportPayload; roda pelo scheduler (event-log-drain), nunca no contêiner worker" },
|
||
{ "id": "pdf", "lane": "saida", "col": 7, "type": "service", "label": "lib/lgpd/pdf-renderer.tsx", "sublabel": "DESLIGADO da marca de propósito — ver o card vermelho" }
|
||
],
|
||
"edges": [
|
||
{ "id": "e1", "from": "installsh", "to": "env", "label": "grava a marca no .env — e o arquivo é escrito com truncamento: chave que o script não grava some no update seguinte" },
|
||
{ "id": "e2", "from": "env", "to": "platformbranding", "label": "sementeDoAmbiente(): a 1ª leitura semeia a tabela e marca seeded_from_env" },
|
||
{ "id": "e3", "from": "env", "to": "resolver", "label": "camadaDoAmbiente() — a camada mais baixa da pilha, e a ÚNICA porta por onde um hex torto entra: lib/env.ts:201 não valida formato (o formulário e o CHECK barram os outros dois caminhos)" },
|
||
{ "id": "e4", "from": "registry", "to": "orgmarca", "label": "a porta declarada; sem entrada aqui, navegacao-completude.test.ts reprova o build" },
|
||
{ "id": "e5", "from": "registry", "to": "telatenant", "label": "porta de 1ª classe do grupo Configurações" },
|
||
{ "id": "e6", "from": "registry", "to": "interface", "label": "a sidebar inteira é renderizada a partir do registro" },
|
||
{ "id": "e7", "from": "adminmarca", "to": "updatebranding", "label": "Salvar (desabilitado enquanto o hex for inválido)" },
|
||
{ "id": "e8", "from": "orgmarca", "to": "updateorg", "label": "Salvar" },
|
||
{ "id": "e9", "from": "telatenant", "to": "legalname", "label": "Razão social — legal_name NASCE igual a display_name (bootstrap-owner.ts), então 'se vazio use X' nunca dispara" },
|
||
{ "id": "e10", "from": "updatebranding", "to": "platformbranding", "label": "update do singleton + invalidação do memo" },
|
||
{ "id": "e11", "from": "updateorg", "to": "orgbranding", "label": "RPC atômica (o jsonb é compartilhado com visibility_mode)" },
|
||
{ "id": "e12", "from": "updatebranding", "to": "auditlog", "label": "audit('platform_branding.updated') — resourceId null: a tabela tem UMA linha e resource_id é uuid" },
|
||
{ "id": "e13", "from": "updateorg", "to": "auditlog", "label": "audit('org.branding_updated') — com organization_id e resource_id = uuid da org" },
|
||
{ "id": "e14", "from": "auditlog", "to": "painelaudit", "label": "quem repintou isto? A resposta só existe aqui — não há event_log (nenhum handler consumiria)" },
|
||
{ "id": "e15", "from": "platformbranding", "to": "instalacao", "label": "leitura + memo com TTL de 30s" },
|
||
{ "id": "e16", "from": "orgbranding", "to": "organizacao", "label": "leitura por organizationId" },
|
||
{ "id": "e17", "from": "instalacao", "to": "resolver", "label": "camadaDaInstalacao()" },
|
||
{ "id": "e18", "from": "organizacao", "to": "resolver", "label": "camadaDaOrganizacao() — só depois da sessão; /login nunca a recebe" },
|
||
{ "id": "e19", "from": "resolver", "to": "rampa", "label": "semente → rampa, com reconciliação contra as cores semânticas do produto" },
|
||
{ "id": "e20", "from": "rampa", "to": "css", "label": "tokens derivados dos dois temas" },
|
||
{ "id": "e21", "from": "resolver", "to": "css", "label": "cssDaMarca(): allowlist de tokens" },
|
||
{ "id": "e22", "from": "css", "to": "layout", "label": "<style id=\"marca-instalacao\"> no <head>" },
|
||
{ "id": "e23", "from": "css", "to": "applayout", "label": "<style id=\"marca-organizacao\">, escopado" },
|
||
{ "id": "e24", "from": "barra", "to": "layout", "label": "themeColor claro/escuro a partir de uma fonte só" },
|
||
{ "id": "e25", "from": "barra", "to": "aba", "label": "a cor da barra do navegador é a do PRODUTO em toda marca — medido: a marca não emite --color-bg", "route": "highlight" },
|
||
{ "id": "e26", "from": "layout", "to": "aba", "label": "título com template, herdado por toda página" },
|
||
{ "id": "e27", "from": "applayout", "to": "interface", "label": "a cor da organização pinta /app e não vaza para o login" },
|
||
{ "id": "e28", "from": "layout", "to": "instalacao", "label": "registrarEstadoDaMarca(motivo): a recusa VOLTA para o banco — este é o laço de retorno", "route": "highlight" },
|
||
{ "id": "e29", "from": "instalacao", "to": "platformbranding", "label": "grava/limpa fallback_at + fallback_reason" },
|
||
{ "id": "e30", "from": "instalacao", "to": "adminmarca", "label": "a tela mostra o estado do degrade em português, não um log que ninguém abre" },
|
||
{ "id": "e31", "from": "resolver", "to": "saida", "label": "MarcaResolvida → MarcaDeSaida: um hex, uma frente legível, tema claro" },
|
||
{ "id": "e32", "from": "saida", "to": "iconroute", "label": "cor + inicial. NUNCA busca logo_url: seria requisição de saída disparada pelo <head> de toda página" },
|
||
{ "id": "e33", "from": "iconroute", "to": "aba", "label": "/icon responde 200 para quem NÃO logou — é a primeira tela que um comprador vê" },
|
||
{ "id": "e34", "from": "saida", "to": "invite", "label": "marcaDaSaida(orgId): classe A, a org já está resolvida nos dois chamadores" },
|
||
{ "id": "e35", "from": "saida", "to": "lgpdemail", "label": "marcaDaSaida(orgId) vindo do worker" },
|
||
{ "id": "e36", "from": "saida", "to": "slaalarm", "label": "marcaDaSaida(orgId) vindo do cron lgpd-sla-watcher" },
|
||
{ "id": "e37", "from": "saida", "to": "enrollmfa", "label": "marcaDaSaida(null): classe B, MFA é anterior à escolha de organização" },
|
||
{ "id": "e38", "from": "saida", "to": "resend", "label": "fromName = marca resolvida" },
|
||
{ "id": "e39", "from": "env", "to": "resend", "label": "RESEND_API_KEY + RESEND_FROM_EMAIL (agora dentro do Zod, não process.env cru)" },
|
||
{ "id": "e40", "from": "invite", "to": "resend", "label": "sendEmail()" },
|
||
{ "id": "e41", "from": "lgpdemail", "to": "resend", "label": "sendEmail()" },
|
||
{ "id": "e42", "from": "slaalarm", "to": "resend", "label": "sendEmail()" },
|
||
{ "id": "e43", "from": "resend", "to": "caixaentrada", "label": "sem RESEND_FROM_EMAIL não há envio silencioso: EmailNotConfigured vira pending_review e accept_url na tela" },
|
||
{ "id": "e44", "from": "enrollmfa", "to": "autenticador", "label": "otpauth://…?issuer=<marca da instalação>" },
|
||
{ "id": "e45", "from": "env", "to": "marcaemails", "label": "lê a marca do .env e substitui __APP_NAME__/__ACCENT__/__ACCENT_FG__ nos HTML" },
|
||
{ "id": "e46", "from": "installsh", "to": "marcaemails", "label": "chamado após o provision; e por update.sh, para o clone que já roda" },
|
||
{ "id": "e47", "from": "marcaemails", "to": "gotrue", "label": "PATCH /v1/projects/{ref}/config/auth — assunto, corpo, site_url e uri_allow_list" },
|
||
{ "id": "e48", "from": "gotrue", "to": "caixaentrada", "label": "confirmação de conta e recuperação de senha: o PRIMEIRO artefato que o cliente do revendedor recebe" },
|
||
{ "id": "e49", "from": "legalname", "to": "lgpdworker", "label": "select legal_name, display_name, dpo_email" },
|
||
{ "id": "e50", "from": "lgpdworker", "to": "pdf", "label": "ExportPayload com o controlador e o DPO resolvido (dpo_email || LGPD_DPO_EMAIL)" },
|
||
{ "id": "e51", "from": "lgpdworker", "to": "lgpdemail", "label": "link assinado do export" },
|
||
{ "id": "e52", "from": "legalname", "to": "pdf", "label": "o rodapé imprime o CONTROLADOR — a ÚNICA identidade do épico que não é marca", "route": "highlight" },
|
||
{ "id": "e53", "from": "pdf", "to": "titular", "label": "Relatório do Art. 18 II (Lei 13.709/2018)" },
|
||
{ "id": "e54", "from": "lgpdemail", "to": "titular", "label": "o e-mail com o link, dizendo quem OPEROU" },
|
||
{ "id": "e55", "from": "adminmarca", "to": "rotalogo", "label": "CampoDeLogo: o arquivo sobe na hora, FORA do Salvar — o ponteiro tem escritor próprio" },
|
||
{ "id": "e56", "from": "orgmarca", "to": "rotalogo", "label": "mesmo componente, escopo=organizacao — o gate troca, a ordem das verificações não" },
|
||
{ "id": "e57", "from": "rotalogo", "to": "bucketlogos", "label": "upload dos bytes FAREJADOS; o anterior só é apagado DEPOIS de gravar, e só sob o próprio prefixo (podeApagar)" },
|
||
{ "id": "e58", "from": "rotalogo", "to": "platformbranding", "label": "escopo=instalacao: upsert de logo_path (CHECK ^platform/<uuid>.(png|jpg)) + seeded_from_env=false" },
|
||
{ "id": "e59", "from": "rotalogo", "to": "fnlogoorg", "label": "escopo=organizacao: RPC com row_count de volta — a catraca da issue #144" },
|
||
{ "id": "e60", "from": "fnlogoorg", "to": "orgbranding", "label": "jsonb_set em {branding} preservando app_name/accent_hex — merge no CAMPO, não no objeto" },
|
||
{ "id": "e61", "from": "bucketlogos", "to": "resolver", "label": "logoDaCamada(): o ARQUIVO vence a URL colada; par de logos não mistura marcas entre camadas; urlPublicaDoLogo deriva a URL do caminho + host em runtime" },
|
||
{ "id": "e62", "from": "rotalogo", "to": "auditlog", "label": "reusa platform_branding.updated / org.branding_updated — código de ação novo não responderia pergunta nova" },
|
||
{ "id": "e63", "from": "layout", "to": "marcacliente", "label": "a MESMA marcaResolvida() do título e do CSS desce por prop, no payload do RSC — mesma rota de user/activeOrg" },
|
||
{ "id": "e64", "from": "marcacliente", "to": "interface", "label": "useMarcaDaInstalacao(): SSR e hidratação leem o mesmo objeto, então o logo do revendedor não troca de elemento no meio do caminho" }
|
||
],
|
||
"cards": [
|
||
{
|
||
"dot": "rose",
|
||
"title": "A não-ligação declarada: o PDF de LGPD NÃO recebe marca, e isso é decisão, não esquecimento",
|
||
"items": [
|
||
"não existe aresta saida → pdf, e a ausência é o conteúdo: `lib/branding/saida.ts` alcança e-mail, remetente, ícone e autenticador, e para no relatório do titular",
|
||
"o motivo é jurídico, não estético: o revendedor é OPERADOR; quem responde pelos dados é o CONTROLADOR, que é a organização (organizations.legal_name)",
|
||
"trocar 'DeskcommCRM' pela marca do revendedor ali PIORARIA o defeito — hoje o nome é obviamente do software; depois pareceria a declaração de quem responde legalmente",
|
||
"consequência boa e deliberada: a armadilha do @react-pdf deixa de existir para nós. Ele aceita var(--x) e oklch(), NÃO lança, e devolve PDF válido com a cor descartada (1514 bytes contra 1538 do hex) — qualquer prova do tipo 'gerei e abriu' passaria com a marca perdida",
|
||
"e o e-mail de LGPD (e54) leva marca DE PROPÓSITO: ele diz quem OPEROU. São dois papéis distintos na mesma jornada, e é por isso que as duas saídas carregam nomes diferentes",
|
||
"armadilha aberta: legal_name NASCE igual a display_name (scripts/bootstrap-owner.ts) — o caso ruim é o valor plausível e errado, não o nulo. Quem resolve é a tela (e9), não um fallback"
|
||
]
|
||
},
|
||
{
|
||
"dot": "amber",
|
||
"title": "Invariantes que a forma não mostra",
|
||
"items": [
|
||
"a imagem Docker é UMA SÓ para todas as marcas — por isso nada de NEXT_PUBLIC_*, nada de favicon em public/, nada de imagem por revendedor (update.sh grava APP_IMAGE incondicionalmente e substituiria a do revendedor em silêncio)",
|
||
"o BANCO está acima do .env: mudar a marca não pede reinício de contêiner. O .env é semente e piso de rollback, porque o agent.sh reverte a imagem e nunca o banco",
|
||
"resolver NUNCA LANÇA, em nenhuma das duas pontas (instalacao.ts, saida.ts): branding() é chamado em app/layout.tsx, e um throw ali é 500 em TODAS as telas — e um e-mail de LGPD tem SLA legal de D+7",
|
||
"o schema do jsonb é LOOSE com catchall, exceção declarada ao .strict() do repo: rollback põe código velho sobre schema novo POR CONSTRUÇÃO (update.sh aplica o baseline antes de puxar a imagem)",
|
||
"cor recusada não apaga a camada de baixo — a pilha continua descendo até o padrão do produto, e o motivo fica escrito em fallback_reason",
|
||
"app/icon.tsx precisa de force-dynamic: sem isso o build congela o ícone dentro da imagem pré-buildada, e o defeito é invisível em dev, em teste e na Vercel — só aparece na VPS do revendedor",
|
||
"/icon precisa estar em PUBLIC_PATHS: o matcher do proxy só exclui caminho COM extensão, então GET /icon respondia 307 para /login (medido em produção)",
|
||
"as duas ações de auditoria da marca estão nas DUAS listas de código (union + painel), e desde 2026-08-14 há gate cobrando que a divergência não cresça"
|
||
]
|
||
},
|
||
{
|
||
"dot": "sky",
|
||
"title": "Fronteiras deliberadas — o que continua NÃO sendo configurável, com a razão medida",
|
||
"items": [
|
||
"DOMÍNIO por organização: fora. Zero coluna no schema, o proxy é NOOP nesse eixo, e no Edge não há banco para consultar antes de decidir o host",
|
||
"FONTE por tenant: fora. next/font resolve em BUILD time e a imagem é pré-buildada — um seletor de fonte seria controle decorativo",
|
||
"X-Deskcomm-Signature e sb-deskcomm-auth NÃO são renomeados: o primeiro é contrato de fio com receptores de terceiros, o segundo é a sessão de quem está logado. 'Completar o whitelabel' ali derruba integração de cliente em silêncio",
|
||
"lib/email/templates/ai-budget-alarm.tsx segue com a nossa marca: não tem rota de cron, não tem linha no docker/scheduler/entrypoint.sh e, desde a limpeza do teto de orçamento (0159), não tem chamador nenhum — workers/ai-budget-checker.cron.ts, o único, foi apagado. Dívida D1, com a condição de saída escrita",
|
||
"quem envia e-mail é o APP (via event-log-drain, batido pelo scheduler), NUNCA o contêiner worker — que roda o agent-engine e não importa e-mail nenhum. Quem for provar reiniciando 'o worker' reinicia o processo errado"
|
||
]
|
||
}
|
||
]
|
||
}
|