118 Commits
Author SHA1 Message Date
Rafael Melgaço d54f4b13c8 Merge pull request #1956 from melgarafael/docs/spec-cobranca-do-revendedor
docs(adr): ADR-0004 — cobrança do revendedor, e a spec que a detalha
2026-09-30 13:31:44 -03:00
melgarafael d74d4457e3 Merge remote-tracking branch 'origin/main' into feat/jev-onda-3
Destrava o #1747 (706 commits atras), a pedido do dono.

- A migration da onda 3 sai de 0433 (20260926210000) para 0500
  (20260930170000): estava fora de ordem, com a main ate a 0499. Arquivo,
  rotulos do baseline, linha do MANIFEST (depois da 0499), testes e
  comentarios.
- A lista do CHECK de agent_inbox_items.kind na migration ganha os cinco
  kinds da proposta (0464/0466/0475): rodando depois delas, a lista antiga os
  apagaria. Igual a do baseline.
- Conflitos: baseline partiu da main com o delta do PR reaplicado (kinds na
  lista unica; apendice antes da VARREDURA anon); InboxKind, rotulos e destino
  da Central ficam com os kinds do Jev e os da proposta; mapa vivo com as
  arestas da main (e121-e123) e as do Jev renumeradas e124-e129; selo das
  traducoes do white-label regravado sobre o original mesclado; e2e.yml com os
  dois comentarios.

O caminho da migration dentro de tests/invariants/jev-aviso-fecha-sozinho-e-e-unico.test.ts
muda no commit seguinte, separado: o guard freeze-invariants barra edicao de
invariante dentro da resolucao.
2026-09-30 11:34:09 -03:00
melgarafaelandClaude Opus 5.5 46b9d9971f docs: a promessa de não cobrar é do projeto, não de quem revende
A VISION separa "nós não vendemos assinatura" de "quem instala pode
cobrar os próprios clientes" (ADR-0004). A spec 19 diz que o console de
agência segue sem faturamento do operador, e troca a afirmação "não
existe tabela de plano ou assinatura", que envelheceria com as tabelas
da cobrança, pela régua da doutrina. O plano da LP marca "sem cobrança
por usuário" como promessa do projeto.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 18:25:40 -03:00
melgarafaelandClaude Opus 5.5 2c35c27168 fix(marca): os cabeçalhos novos do webhook de saída nascem com nome neutro
Os quatro cabeçalhos que o #1830 acrescenta ao webhook de saída saem como
X-Webhook-Delivery, X-Webhook-Attempt, X-Webhook-Timestamp e
X-Webhook-Signature, e não com a marca do produto. Nome de protocolo vira
contrato no primeiro release e não se renomeia mais; com a marca, toda
instalação de marca própria mandaria a marca de origem ao sistema do cliente
em quatro nomes a mais, e a MARCA_CONGELADA cresceria quatro entradas
PROTOCOLO — a lista que só encolhe. Mesmo precedente de d89d2e55e.

X-Deskcomm-Event e o X-Deskcomm-Signature legado ficam: já são contrato
publicado. O vetor de referência não muda (nenhum nome de cabeçalho entra no
HMAC nem no uuid). Os três white-label*.md voltam ao que está na main — a
linha dos "dois nomes técnicos" volta a valer como está. A única mudança que
sobra no branding.test.ts é a contagem da guarda: o teste novo de ponta a
ponta confere o legado mais uma vez.

O fragmento ganha a linha de crédito do autor.

Sonda: git grep -inE 'x-deskcomm-(delivery|attempt|timestamp|signature-2)'
dá 0 nesta árvore e 91 no head do #1830 (6a746b335).

Refs: #1830 #1529

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:25:17 -03:00
in100tiva 6a746b3359 feat(webhooks): o webhook de saída assina a hora e identifica a entrega
Quem recebe o webhook de saída (ação call_webhook) só conseguia
conferir que o corpo veio de quem tem o segredo. A assinatura era o
HMAC só do corpo, sem carimbo de tempo, então uma requisição capturada
continuava válida para sempre. Não havia id de entrega, e occurred_at
era a hora do envio, de modo que cada Reenviar montava outro corpo com
outra assinatura. O receptor não tinha como recusar uma repetição nem
como reconhecer uma retentativa como a mesma entrega.

Toda entrega passa a levar:
- X-Deskcomm-Delivery: uuid v5 de evento + regra + posição da ação +
  lista de ações sem segredo; é o mesmo nas retentativas e no Reenviar;
- X-Deskcomm-Attempt: continua a contagem no Reenviar;
- X-Deskcomm-Timestamp;
- X-Deskcomm-Signature-2, só com segredo:
  t=<ts>,v1=HMAC(segredo, "<ts>.<delivery>.<corpo>").
O X-Deskcomm-Signature legado continua byte a byte igual. occurred_at
passa a ser event.created_at, e o envelope ganha delivery_id.

A lista de ações entra no id porque a posição sozinha não identifica a
ação. Depois de remover ou reordenar ações, a que herdasse a posição de
outra sairia com o id de uma entrega que o receptor já processou, e ele
a descartaria em silêncio. Com a lista no id, editar a regra gera um id
novo: na dúvida o receptor recebe de novo, mas nunca deixa de receber.

O Reenviar tira a posição da ação antes de filtrar os webhooks e começa
o Attempt no número seguinte ao maior já registrado em todos os runs de
(org, regra, evento). O resultado grava detail.resent_from_run_id no
jsonb que já existe. Não há migration nem dependência nova: o uuid v5
usa node:crypto e é conferido contra o vetor do RFC 9562.

Entram também:
- o guia docs/integracao/webhooks-de-saida.md: janela de 300 s pelo t
  assinado, comparação em tempo constante, deduplicação, e exemplos em
  Node e Python executados por teste contra um vetor fixo;
- o apontamento do guia na tela da ação;
- os quatro cabeçalhos novos como PROTOCOLO na catraca de marca;
- notas de estado real na spec 07 e no guia de marca própria;
- o fragmento capacidade_nova, que avisa da mudança de significado do
  occurred_at.

Refs #1529
2026-09-27 23:10:54 -03:00
melgarafael f1f32a5d81 Merge remote-tracking branch 'origin/main' into feat/jev-onda-3 2026-09-26 22:44:54 -03:00
Lucas Pereira 135a7d9885 Merge remote-tracking branch 'origin/main' into feat/EPIC-13-controle-callback-agente 2026-09-26 22:45:51 +02:00
Lucas Pereira e2846a66a0 feat(EPIC-13): permita controlar novos retornos do agente 2026-09-26 22:45:47 +02:00
melgarafaelandClaude Opus 5.5 a32cce1728 Merge origin/main into feat/jev-onda-3
Conflitos:
- lib/ai/inbox-destino.ts: os dois kinds do Jev (daqui) + o `other` com
  `ai_provider_credential` (da main).
- supabase/baseline.sql: lado da main inteiro (0428, 0432) e o bloco dos avisos
  do Jev reaplicado depois dele, antes do `notify pgrst` do fim da região.

A main ganhou a SUA 0426 (motivo de perda). A dos avisos do Jev vira 0433
(o próximo livre contra a main e os 12 PRs abertos; carimbo 20260926210000,
depois do maior da main, 0432 às 200200): arquivo, MANIFEST (linha movida para
depois da 0432), apêndice do baseline e a prosa que citava o número. Os
comentários de invariantes e o caminho que um deles lê vão num commit próprio.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:32:05 -03:00
melgarafaelandClaude Opus 5.5 7dbd810bfa docs(ia): a spec da tela de agentes diz o que pausar de fato grava
docs/specs/12-spec-ai-agents-ui.md ainda afirmava a premissa que o H7
tirou dos comentários: o badge cinza era "published_version_id == null —
pausado/nunca publicado", e "Despausar" era republicar a versão
(ai_agent.republished). Hoje pausar grava só paused_at, o ponteiro fica
(app/api/v1/ai/agents/[id]/pause/route.ts), despausar é
unpauseAgentAction limpando paused_at (audit ai_agent.updated com
metadata.unpaused), e ai_agent.republished não existe em lugar nenhum
do repositório (git grep vazio).

O badge passa a nomear paused_at nos três estados e aponta a régua viva,
estadoDoAgente em lib/ai/agents/no-ar.ts, em vez de repeti-la.

Sonda (git grep -nEi "pausad.{0,60}published_version_id|published_version_id.{0,60}pausad"
fora de docs/handoff/): antes 1 linha (esta), depois só a linha corrigida.
Sem teste novo: é prosa de spec, e um regex sobre ela mede a palavra, não
a afirmação. Suíte unitária: 1437 arquivos, 14739 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:03:19 -03:00
Lucas Pereira 27434b9994 fix(EPIC-13): torne a criação de agenda idempotente 2026-09-26 18:41:32 +02:00
Rafael Melgaço 24664ef6c6 Merge pull request #1592 from raphaelmartins/codex/inbox-call-button
feat(inbox): mostrar botão de ligação na conversa
2026-09-24 19:36:15 -03:00
melgarafaelandClaude Opus 5.5 14b1513328 test(inbox): catraca de largura ignora o discador novo do cabeçalho
O #1592 pôs o DialButton no ConversationHeader. O DialButton chama
useVoiceCall, que lança fora do VoiceCallProvider; na aplicação o
provider envolve todo /app, mas tests/unit/inbox-header-nao-trava
renderiza o cabeçalho sem ele e quebrava 4 casos. Este teste só mede
a largura, então o discador sai da conta por vi.mock.

A spec 18 (§5.1 e checklist) dizia que o botão Ligar vivia só no
Customer 360; agora cita também o cabeçalho da conversa na Inbox.

Refs: #1592

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pUk4A1WWHegBZNbHsqDnp
2026-09-24 18:25:27 -03:00
melgarafael 3a890b42ce Merge remote-tracking branch 'origin/main' into triagem/1610-webhook-reentrega 2026-09-24 18:01:00 -03:00
Elias GervannoandClaude Opus 5.5 4d90532ed7 fix(waha): mensagem que chega com o banco indisponível não se perde mais
A ingestão do webhook WAHA engolia a falha do banco nos quatro pontos em
que a mensagem depende de uma escrita (contato, conversa, mensagem
recebida e mensagem enviada pelo celular): `console.error` + `return`, e
a rota devolvia 200. O WAHA riscava o evento achando que entregou, e a
mensagem do cliente sumia — medido em produção: três perdidas em 14/09
num dia de `statement timeout`, duas na troca de banco de 24/09.

- `lib/waha/falha-transitoria.ts`: a régua. Erro de dado, integridade,
  schema, regra de negócio e pedido malformado são permanentes; o resto
  (timeout, conexão, PostgREST sem banco, 5xx, sem código) é transitório.
  A ingestão passa a lançar a classe certa nos quatro pontos.
- `lib/waha/desfecho-do-webhook.ts`: as duas rotas gravam o desfecho no
  arquivo (`processed` / `error`), que até aqui nascia e morria
  `received`. Falha transitória responde 503 + Retry-After: o WAHA
  reentrega qualquer erro 15x com espera max(2s, Retry-After), e a
  reentrega é segura porque o 23505 vira dedup.
- `app/api/v1/cron/webhook-replay`: relê a cada minuto o arquivo marcado
  `transitoria:`, até 20 tentativas; depois `dead` e um aviso `event_dead`
  na Central com título próprio (`MENSAGEM_QUE_NAO_ENTROU`), que o dreno
  de handlers exclui do seu dedupe. Para na terceira falha seguida da
  rodada para não martelar um banco que ainda está voltando.

É o `process-pending-webhooks` que o PRD 03 e a Spec 03 prometiam; os
dois passam a dizer o nome e a régua reais. Sem migration: `processed`
e `dead` já estavam no CHECK, `event_dead` já existia.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:51:43 -03:00
webtecnica 7ef5b2b944 docs(specs): as Specs 01 e 11 passam a declarar o prefixo dsk_ do token
A Spec 01 e a Spec 11 descreviam o plaintext do token de servidor como
`tok_<env>_<base62 32>` (7 linhas, 9 ocorrências) enquanto o código exige
`dsk_` (`lib/mcp/auth.ts:123`) e emite `dsk_<8 hex>_<base64url 32 bytes>`
(`app/api/v1/settings/api-tokens/route.ts:64`, `lib/tenants/api-key.ts:142`).
`tok_` nunca existiu no código — o CLAUDE.md já registra isso desde 17/09/2026
(PR #1128, que corrigiu CLAUDE.md, AGENTS.md e Spec 09 e deixou as Specs 01 e 11
de fora de propósito, como a issue aponta).

Condição de fechamento 1 da issue #1129: o esquema com AMBIENTE no prefixo é
abandonado, as 7 linhas passam a `dsk_` e a Spec 01 registra POR QUE o ambiente
saiu do prefixo — o produto não tem `live`/`test` por token (a separação é a
organização, e o estado da credencial vive em `revoked_at`/`expires_at`), e o
desenho implementado (EPIC-09, CLAUDE.md, código) nunca teve ambiente. O código
de emissão e validação não muda: trocar o prefixo invalidaria todo token já
emitido nas instalações e contradiziria o CLAUDE.md que o próprio #1128 corrigiu.

Entra junto o comentário de `lib/messaging/ritmo-do-envio-por-token.ts`, que
citava `Bearer tok_...` — com ele, `git grep tok_` zera em `lib/`, `app/` e nas
duas specs.

Gate: tests/unit/formato-do-token-de-servidor-bate-com-a-spec.test.ts — 4 casos
que prendem as três pontas: a validação recusa o prefixo antigo como
`malformed` e aceita `dsk_`, as DUAS portas de emissão montam o formato, e as
duas specs declaram `dsk_` e já não declaram `tok_`.
2026-09-24 11:42:16 -03:00
melgarafaelandClaude Opus 5.5 deb416062b fix(waha): recusa do contrato do webhook deixa rastro e não perde o arquivo
Três cantos da issue #290, nas duas rotas do webhook do canal por QR:

1. A rota por token recusava no estágio 1, ANTES de arquivar, a `session`
   do corpo, que ali não resolve nada (quem resolve é o token do caminho).
   Ganha um estágio 1 próprio (`lerRoteamentoWahaPorToken`: evento + id).
2. A recusa do estágio 2 gravava `status: "received"` no arquivo, igual a um
   evento que deu certo. O contrato passa a ser conferido antes do INSERT e a
   linha nasce `error`, com `contrato_violado: <campos>` (nomes, nunca valores).
5. A recusa do estágio 1 da rota por token (pública, antes do gate de
   assinatura) sai como `warn`; a da rota global segue `error` (o Caddy do kit
   barra quem não é o WAHA).

O comentário do 400 deixa de se apoiar numa premissa não medida sobre
retentativa do provider (item 3, lado WAHA). Primeiro teste de rota da rota
por token: tests/unit/recusa-do-webhook-waha-deixa-rastro.test.ts.

Porte do trabalho de 17/09 (branch fix/290-webhook-waha-recusa-deixa-rastro),
que estava com identidade de autor inventada.

Refs #290

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9fyW7pXJCsyDPtu5PoQpN
2026-09-23 15:49:17 -03:00
melgarafaelandClaude Opus 5 63fccdaaa1 fix(banco-externo): filtro sem resultado volta vazio, e o audit conta erro como falha
Ajustes nossos sobre a fatia do @vgamkt (commit anterior), separados para o
diff dele ficar legível.

- O filtro que não casa nada devolve VAZIO. A versão do PR reexecutava a
  consulta sem o filtro e entregava até 100 linhas ao modelo (C-013), pensando
  num catálogo de produtos. A tool é genérica: numa tabela de clientes, o CPF
  que não casa entregaria os registros de outras pessoas. A resposta agora
  ensina o modelo a repetir com um trecho menor do termo. Provado por
  sabotagem: com o fallback de volta, o caso novo recebe a linha
  "de-outra-pessoa" e reprova.
- `motivoDoVazio` nas duas tools (#484): o erro devolvido como texto
  (`acesso_negado`, `tabela_nao_encontrada`...) e a consulta sem linha passam
  a ser `success: false` no audit, em vez de "nenhuma falha" no painel.
- `tests/unit/valor-de-filtro-nao-vai-ao-audit.test.ts`: roda a tool real
  pelos dois ingressos e prova que o valor do filtro não chega ao audit.
  Sabotado nos dois: auditar `args` crus no runtime ou no `/api/mcp` reprova
  o caso respectivo.
- Espanhol dos rótulos novos no dicionário; spec 20 e mapa vivo passam a
  descrever a camada do agente como entregue; fragmento em `.changes/`.

Refs: #1130
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G7fFaatqXpzHA77xP9onS
2026-09-22 12:40:20 -03:00
Rafael MelgaçoandClaude Opus 5 f55a826736 Merge pull request #963 from saraivabr/saraiva/social-native
Prospecção nativa, agente por conversa, canais sociais e painel de mensagens
rápidas (@saraivabr). 14.148 linhas em 161 arquivos, aberto em 16/set.

Os cinco obrigatórios verdes, zero vermelhos.

O QUE FICOU DE FORA, E NÃO É RECUSA: o atalho flutuante de mensagens
(`FloatingInbox`) foi RECORTADO e volta em PR próprio, com o crédito do autor.

A razão, medida: montar o dock fazia a PÁGINA INTEIRA aparecer duas vezes no
DOM, em seis casos de e2e, em telas sem relação entre si — agenda, construtor de
fluxos, tipos de evento, distribuição de atendimento. Provado por ablação (uma
tag comentada, num run só: seis violações viram zero).

E o mecanismo NÃO é do dock. O segundo nó é `<div hidden id="S:0">`, a caixa de
estadiamento do streaming SSR do React, filha direta do `<body>` e fora da
árvore da casca. Num stream correto todo `id="S:N"` tem um `$RC("B:N","S:N")`
que o consome; o documento fecha sem emitir o dele, e a caixa fica órfã com uma
cópia inteira da página. Medido em dois artifacts independentes
(`S:0=1  B:0=1  $RC=0`), e os `loading.tsx` que produzem esses boundaries já
estão na `main` — o dock não. Ele revela, não causa.

Está na issue #1374, com o comando que reproduz. Um PR de contribuidor não
carrega dívida da casa, e o dock não fica preso a ela: volta assim que o
mecanismo tiver desfecho.

DEFEITOS ACHADOS E CONSERTADOS NO CAMINHO, nenhum deles do autor:

- A cascata de anonimização de LGPD: o apêndice do baseline redefine a função
  inteira, e `create or replace` faz valer só a última. Um merge da `main`
  apagou em silêncio o passo `sales` que ela tinha ganhado — anonimizar
  devolveria SUCESSO com o texto da comanda ainda legível. O invariante pegou.
- A reserva do rodapé: `AppShell` trocou `p-6` por `p-6 pb-20` enquanto o
  `InboxLayout` desconta `2*var(--space-6)` do próprio calc. 24+80=104 gastos
  contra 48 descontados: 56px de rolagem morta no Inbox, com o campo de envio
  abaixo da dobra, em TODA instalação. Consertado pelo contrato do rodapé
  (`lib/ui/rodape-ocupado.tsx`), não por esticar o calc.
- Migrations renumeradas 0359-0362 -> 0368-0371: o 0359 tinha sido tomado pela
  cascata da comanda do #819. Troca por par completo (`carimbo_NNNN_slug`), não
  por `sed` em "0359" — que teria reescrito as três referências da migration
  alheia.
- O índice de identidade social nascia sem `where is_merged_into is null`,
  reprovado pela cerca que entrou hoje no #1328 (@webtecnica).
- Três chaves duplicadas no dicionário, vindas da prévia do merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHzAhMNW4yRWfgR5Ack89m
2026-09-20 04:50:25 -03:00
VANDER GUSTAVO ALVESandClaude Opus 5 b029d39b45 feat(banco-externo): cadastrar e explorar um PostgreSQL de outro sistema (recorte do #1130)
Trabalho de @vgamkt, recortado do PR #1130 (`feat/fluxos-de-atendimento`, 94
commits, 2130 atrás da main, 33 conflitos medidos). Esta fatia é a do BANCO
EXTERNO, e foi escolhida porque é 100% ADITIVA: `git ls-tree -r origin/main |
grep -c external-db` devolve 0, e nenhum dos 33 conflitos toca estes caminhos.

Recria, sobre a main de hoje, o estado final dos commits dele que constroem a
frente — 73a274eff, 2d4113022, f994fa15b, d750a2b80, 7e8e2bbf1, 8f145f189,
8ecfc4c30, 3cdf99e12, 7f8ca9309, fee7d4eac, 42e99715a — em vez de cherry-pick
um a um, que arrastaria os arquivos de costura de 94 commits.

─── O que entrou

- `lib/external-db/` — núcleo. Guarda de destino própria (o `pg` abre TCP cru e
  não passa pelo allowlist HTTP): RFC1918 PERMITIDO de propósito (Postgres na
  LAN é caso real, e quem cadastra é `admin`), link-local/metadata, loopback,
  CGNAT, multicast e reservadas SEMPRE bloqueados, IPv6 normalizado antes de
  classificar. Pool por conexão invalidado por `updated_at`, `BEGIN READ ONLY`
  com `statement_timeout`/`lock_timeout`, introspecção ao vivo, e o SELECT
  montado no servidor com identificadores validados contra o catálogo e valores
  parametrizados. Testes co-localizados.
- `app/api/v1/external-db/` — CRUD, teste de conexão, catálogo e leitura. A org
  vem de `requireRole` (cookie/JWT), nunca do corpo; o admin client filtra
  `organization_id` à mão. Listar é `viewer`, configurar é `admin`.
- `app/app/integracao-dados/` — lista, cadastro/edição e explorador paginado.
- Migrations 0372 e 0373 + apêndice idempotente no `baseline.sql` + MANIFEST.

─── O que NÃO entrou, e por quê

As tools do agente (`crm_describe_external_data`, `crm_query_external_data`) e o
`redigirParaAuditoria` ficaram no #1130. Elas são CONSUMIDORAS deste núcleo e se
apoiam em `lib/ai/runtime/tools.ts`, `lib/mcp/server.ts`, `lib/mcp/types.ts` e
`lib/atendimento/fronteira-server.ts` — os quatro se moveram na main desde que o
PR foi escrito. A fatia que entrou se sustenta sozinha (cadastro → API → tela,
com log, porta na navegação e mapa vivo), então a camada do agente entra como um
segundo recorte sem retrabalho neste. A spec e o mapa dizem isso por escrito, e
`max_filters`/`max_response_bytes` ficam declarados como ainda sem consumidor.

─── O que eu mudei do trabalho dele, e por quê

- Migrations renumeradas de 0233/0234 para 0372/0373: os dois números já são de
  outra coisa na main (0234 é chamada de voz). O teto não é a main: 0367 está
  tomado por dois PRs em voo (#1359, #1363) e 0368-0371 por branches locais que
  o pre-commit conhece e a varredura de PRs não via. Medido em TODAS as refs
  (`git log --all --diff-filter=AMR -- supabase/migrations`), não só na main.
  O conteúdo SQL não mudou.
- Spec renumerada de 18 para 20 (18 e 19 já existem na main).
- `placeholder="db.exemplo.com"` virou `meusistema.com`:
  `tests/unit/branding.test.ts` tem a categoria AMOSTRA fechada, e esse domínio
  já está declarado lá — medido, reprovava.
- Do dicionário de i18n entraram só as 56 chaves que ESTA fatia usa, nunca o
  arquivo dele (a main tem 6.556 chaves e a branch dele 5.403 — substituir
  apagaria trabalho).
- O mapa vivo perdeu a faixa do agente e ganhou a não-ligação declarada.

Refs: #1130
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHzAhMNW4yRWfgR5Ack89m
2026-09-20 02:57:08 -03:00
melgarafael 152446ec66 Merge remote-tracking branch 'origin/main' into cb/963
# Conflicts:
#	supabase/baseline.sql
2026-09-19 12:39:38 -03:00
melgarafaelandClaude Opus 5 3ef9ed4423 Merge origin/main into cb/963 — 13 conflitos, cada um com a razão
- vercel.ts: a main APAGOU (o produto saiu da Vercel). Deleção aceita, não
  ressuscitado.
- lib/ai/pontos/resolver.ts e heranca-de-provider-nos-pontos-auxiliares:
  união — "prospecting_agent_setup_chat" (PR) e "case_chat" (main) são pontos
  diferentes; perder um tiraria a herança de provider daquele ponto.
- lib/audit/actions.ts: o lado do PR já continha a linha da main, mais dois
  verbos novos; ficou o do PR (a união seria idêntica).
- lgpd-pdf-controlador/meet/replies: fixtures — cada lado acrescentou campos à
  MESMA estrutura. Ficam todos: fixture incompleta faz o teste passar sobre o
  que não existe. No "meet", o início de appointment_notices aparecia nos dois
  lados; entrou uma vez só.
- lib/lgpd/export-collector.ts (4 conflitos): união. O relatório do titular
  leva prospecting_candidates (PR) E casos/eventos/demandas/chat/passagens/
  avisos (main) — o que se apaga a pedido dele é o que se entrega a pedido dele.
- tests/invariants/rls-completude-varredura: união das tabelas declaradas.
  Tabela que sai da lista é tabela sem prova de isolamento.
- docs/testing/user-journey-map.md e roteamento-por-canal.architecture.json:
  união (JSON revalidado: 14 nós, 18 arestas).
- lib/i18n/dicionario.ts: união, e depois a varredura com a chave NORMALIZADA
  (sem aspas) achou TRÊS duplicatas — "minutos", "Não foi possível concluir a
  operação." e "Configuração salva." —, cada uma existindo nos dois lados, com
  tradução idêntica. Saíram as cópias do PR (confirmadas pelo contexto das
  linhas vizinhas), porque chave repetida é TS1117 e derruba os quatro
  workflows de uma vez.
- supabase/baseline.sql: DERIVADO, não remendado. Parti do baseline da main e
  inseri o apêndice do PR (355 linhas) ANTES do bloco da varredura de anon,
  porque os blocos dele criam função e função depois da varredura nasce exposta
  em quem ATUALIZA. Provado pelo portão do repo: varredura-anon-e-o-ultimo-bloco
  5/5.

DESKCOMM_GOV_INVARIANTS_EDIT=1: o hook acusa tests/invariants/rls-completude-
varredura.test.ts, e a mudança é SÓ ACRÉSCIMO — as quatro tabelas que a main
declarou (config_aviso_de_caso, entregas_de_aviso_de_caso,
organization_extensions, extension_operations) mais calendar_locations,
chegando por este merge. Nenhuma linha removida, nenhum caso afrouxado: é a
união das duas listas, e tabela que saísse dela seria tabela sem prova de
isolamento.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:13:38 -03:00
melgarafael 510695de27 Merge remote-tracking branch 'origin/triagem/1114-onda1' into feat/provisionadora-de-modulo 2026-09-19 09:41:08 -03:00
melgarafaelandClaude Opus 5 3086908c68 docs(adr-0002): o segundo invariante da onda 2 e o que ficou sem medir
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 09:30:04 -03:00
melgarafaelandClaude Opus 5 98844fc275 docs(adr-0002): onda 2 implementada — ordem real, aviso ao PostgREST, disputa de trava
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 08:52:35 -03:00
melgarafaelandClaude Opus 5 9bfc04680f docs(adr-0002): o desenho da onda 2 — D3 (instalar cria as tabelas) e D6 (reaplicar falha alto)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 08:19:11 -03:00
melgarafael b83fb9b8cc Merge remote-tracking branch 'origin/main' into feat/casos-vivos 2026-09-19 02:14:53 -03:00
melgarafael bf50fffe87 Merge remote-tracking branch 'origin/main' into feat/casos-vivos
# Conflicts:
#	docs/testing/user-journey-map.md
2026-09-18 22:14:41 -03:00
melgarafael 76acb899dd Merge remote-tracking branch 'origin/main' into feat/marketplace-de-extensoes 2026-09-18 22:11:25 -03:00
melgarafael 4ad74af0c0 Merge remote-tracking branch 'origin/main' into feat/casos-vivos
# Conflicts:
#	tests/unit/atrito-par-eficiencia-dano.test.ts
2026-09-18 20:44:35 -03:00
Rafael Melgaço 5f0a5eab63 Merge pull request #1236 from melgarafael/triagem/963-fatia-a-pareamento
feat(canais): conectar o WhatsApp por código de pareamento (fatia A do #963)
2026-09-18 20:43:06 -03:00
melgarafael 2b77296873 Merge remote-tracking branch 'origin/main' into feat/marketplace-de-extensoes
# Conflicts:
#	supabase/baseline.sql
2026-09-18 20:41:50 -03:00
Saraiva cd3ec34e79 feat(channels): connect WhatsApp with a pairing code 2026-09-18 19:09:05 -03:00
Rafael MelgaçoandClaude Opus 5 6c96dbca77 fix(#778): a reserva chega a quem já instalou, e efeito que falha libera a chave
Ajuste da triagem sobre o trabalho de @webtecnica, sem reescrever nada dele:

- baseline.sql: a constraint idempotency_keys_recibo_ou_reserva sai do CORPO
  (reprovava baseline-reaplicavel) e vai para o apêndice, SOMADA aos dois
  `alter column ... drop not null`. Sem eles o update.sh de uma VPS já
  instalada passava pelo `create table if not exists` sem efeito, as colunas
  seguiam NOT NULL e toda reserva falhava com 23502 (medido em pg17 na triagem).
- migration renumerada 0314 -> 0321 (o 0314 é do #1123), mesmo timestamp, nos
  três artefatos e nas citações do PR; MANIFEST volta a dizer o QUÊ/PORQUÊ.
- lib/api/idempotency.ts: efeito que lança vence a reserva na hora e propaga,
  como a rota promete (route.ts: "propaga sem gravar recibo"); caso (12).
- route.test.ts: o dublê de idempotency_keys aprende `update`.
- tipos de idempotency_keys anuláveis; espanhol da mensagem nova; AGENTS.md e
  spec 01 §7.3 deixam de dizer que a corrida está aberta; fragmento.

Refs: #1189 (trabalho de @webtecnica)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wvMod96K62qoXnmY5oYBF
2026-09-18 18:04:52 -03:00
Pessoa 713662d4d6 Merge remote-tracking branch 'origin/main' into feat/casos-vivos
# Conflicts:
#	docs/architecture/escalacao-ciclo-humano.architecture.json
#	supabase/baseline.sql
2026-09-18 17:51:42 -03:00
Pessoa d6d5963d7c Merge remote-tracking branch 'origin/main' into feat/marketplace-de-extensoes
# Conflicts:
#	.github/workflows/e2e.yml
2026-09-18 12:53:31 -03:00
Rafael Melgaço 8436b031a9 Merge pull request #1133 from Gervanno/feat/handoff-devolve-ao-agente-apos-prazo
feat(atendimento): a conversa parada com humano volta ao agente sozinha depois do prazo
2026-09-18 12:37:52 -03:00
Pessoa a09cdc75a5 Merge remote-tracking branch 'origin/main' into feat/marketplace-de-extensoes 2026-09-18 12:37:33 -03:00
PessoaandClaude Opus 5 d2e99e0078 docs(extensoes): a doutrina, a spec e o fragmento acompanham o contrato novo (DoD 16 e 17)
O DoD manda procurar a afirmação de estado sobre o comportamento que o PR muda. Duas
estavam vencidas, e as duas foram apontadas por quem escreveu a skill — eu tinha o aviso
e ele ficou parado enquanto eu mexia em código.

DOUTRINA
- O não-negociável 2 dizia "hoje, `tasks.open`". Onde a afirmação podia virar comando, virou:
  a prosa agora manda rodar o grep em `lib/extensions/capacidades.ts`, e escreve a RÉGUA de
  admissão de uma porta nova em vez do inventário, que envelhece a cada ADR.
- Não-negociável 6-bis, novo: versão nova não troca o conjunto de portas. Enquanto havia uma
  permissão só, a troca era impossível por construção; com a lista fechada ela passou a ser
  possível, e por isso precisou virar recusa explícita.

SPEC
- Os tipos do bloco TypeScript, a frase sobre concessão e a linha do laço de retorno.
- O parágrafo que prometia `extension_permissions_changed` "quando o contrato admitir outra
  permissão" agora diz que a recusa EXISTE — e guarda, entre parênteses, a premissa que
  venceu. Prosa de estado tem prazo; apagar o rastro faria a próxima pessoa achar que nunca
  houve promessa.

FRAGMENTO DE RELEASE
- Declara o efeito no operador, incluindo o que dói: pacote da versão anterior deixa de ser
  compatível, porque foi o autor que escreveu até onde garantia. Diz também o que NÃO
  acontece — nada é desinstalado sozinho e nenhuma configuração se perde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 08:10:10 -03:00
PessoaandClaude Opus 5 9113c5fae2 feat(escalacao): o cartão "por que a IA passou para você", dentro da conversa
O contexto da passagem existia desde a onda 10, mas vivia num aviso da
Central — uma lista da organização inteira, que atualiza a cada 60s e leva
para a ficha do contato. Quem assume trabalha na CONVERSA, e é lá que o
cartão passa a aparecer: motivo em português, resumo, o que a IA tentou (lista
numerada), o que o cliente quer, e se o cliente foi avisado — com o porquê
quando não foi.

Sete estados, e a decisão de qual mostrar mora numa função PURA fora do
componente: o gate de vocabulário proíbe código de motivo dentro de
`components/`, e com razão — é o mesmo texto que a Central e o export usam.

A rota lê com o client de SESSÃO. É isso que faz a regra de visibilidade valer
para o TEXTO da passagem, que carrega o que o cliente disse.

Cobrador: passagem sem ninguém reconhecer há mais de 24h reabre o aviso, e
para na terceira cobrança — sem o contador, o vigia viraria spam diário sobre
a mesma linha. A auditoria só registra quando houve cobrança de verdade.

Laço de retorno (invariante 7): "o cliente repetiu tudo depois da passagem?"
vira número na tela de métricas, com a régua declarada (limiar de semelhança
0,7, janela de 24h, denominador = passagens em que o cliente voltou a falar).
Ausência de dado é `null`, nunca `0` — zero é uma afirmação.

Consertado de passagem: o merge anterior deixou marcadores de conflito
commitados no mapa vivo, e o JSON não parseava. Resolvido pela união dos dois
lados, com as arestas colidentes renumeradas.

Verificado: suíte completa 1.003 arquivos / 10.252 casos / ZERO falhas;
typecheck, lint, lint:channels, lint:role-rank, colisão de migration,
test:shell e release:conferir verdes; `test:db` do invariante novo com install
+ update + 7/7; 9 sabotagens batendo a previsão exata, restauração conferida
por md5.

PENDENTE: `test:db` completo e em pg17, `pnpm build`, e a jornada em tela —
as duas linhas J27.5/J27.6 seguem declaradas como NÃO COBERTAS, porque são da
onda 12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqKEskriDoLeY45kmFtCgE
2026-09-18 04:41:48 -03:00
PessoaandClaude Opus 5 f7523adc5a Merge branch 'feat/casos-vivos' into feat/casos-vivos-numero-interno
Conflito único no dicionário: as ondas 8 e 10 acrescentaram chaves DIFERENTES
no fim do mesmo bloco. Ficaram as duas. typecheck zerado depois.

Overrides pelo mesmo motivo dos merges anteriores: o hook não distingue merge
de commit novo, e o que ele acusa (migration e invariantes) veio pronto dos
commits que este merge traz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqKEskriDoLeY45kmFtCgE
2026-09-18 03:49:58 -03:00
PessoaandClaude Opus 5 66b966ba07 feat(escalacao): os 13 caminhos de passagem passam a entregar contexto de verdade
Antes: o resumo era do turno ANTERIOR, "o que a IA já tentou" não existia, o
motivo chegava como `Motivo: requested_human` em inglês de máquina, e a
segunda passagem do mesmo cliente era DESCARTADA em silêncio pelo dedup do
aviso.

Agora, os treze caminhos — pedido explícito, suspeita de descadastro, a
ferramenta do modelo, teto de gasto, o escalar de um caso, o orquestrador do
CRM, sentimento, os legados, o MCP e o runtime nativo — gravam a mesma
passagem, montada por uma função só.

A ferramenta que a IA usa ganhou três campos, com descrições que ENSINAM o
modelo: por que está passando, o que já tentou e o que o cliente quer. Onde
não há IA, o piso é determinístico: checkpoint + as mensagens pendentes do
turno + o motivo traduzido.

Três correções de verdade a quem assume:
- o aviso da Central virou CURTO e aponta para a CONVERSA, não para a ficha do
  contato: aquele corpo é entregue a qualquer atendente da organização, e o
  conteúdo da conversa não cabe ali;
- a segunda passagem acrescenta ADENDO em vez de sumir;
- "o cliente JÁ FOI avisado" passou a sair do status real da mensagem, e não
  da ausência de exceção — antes o produto afirmava isso mesmo quando o envio
  tinha falhado.

Divergência do plano, medida: esta onda TEM schema (migration 0293). A onda 9
não entregou as funções de reconhecimento, e sem escritor as colunas nasciam
mortas.

Verificado: typecheck, lint, lint:channels, lint:role-rank, colisão de
migration e release:conferir zerados; `test:db` com o baseline aplicando em
install E update e o invariante novo 10/10; suíte com 10.110 verdes (os 7
vermelhos são o Upstash local documentado no CLAUDE.md e estado transitório de
outra sessão — remedidos isolados, 55/55). 8 sabotagens, 7 batendo com a
previsão escrita antes; a que desviou está registrada com a previsão de pé.

Dois defeitos achados e consertados: um `.trim()` sobre linha de banco não
validada que derrubaria a rota de escalação, e uma semente de teste engolida
em silêncio por um unique parcial.

PENDENTE: pg17, build, test:shell, e o cartão na conversa (onda 11) — que é o
que fecha as duas linhas "não coberto pela tela" da jornada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqKEskriDoLeY45kmFtCgE
2026-09-18 03:49:01 -03:00
PessoaandClaude Opus 5 9c7f2a0faa feat(casos): a tela do aviso no WhatsApp — configurar, testar e ver o que saiu
A tela vive em IA › Aviso no WhatsApp (admin), e o rótulo não é "Avisos" de
propósito: "Alertas" já é a Central, na mesma seção, e duas portas com nome
parecido na mesma lista é como se perde gente.

Ela faz três coisas: escolhe a conexão e o número, manda um aviso de teste DE
VERDADE, e mostra as 20 últimas entregas com destino mascarado, o motivo da
falha em português e o link para o caso. Sem a lista, o mecanismo seria
invisível — e mecanismo invisível é pior que ausente.

O seletor filtra por CAPACIDADE (aceita mensagem livre), nunca por nome de
provedor: é o que a doutrina de canais exige, e o gate confirma.

Onze estados de alerta, não sete: só canal oficial conectado, mesma conexão
que atende clientes, sem endereço público, casos desligados no agente, agente
assistido, atendimento externo, conexão arquivada, e o descarte de mensagens
do número interno ("é o esperado"). Cada um com texto de gente.

O aviso de teste paga no contador anti-banimento e NÃO escreve no registro de
entregas — a garantia é de tipo, provada por sabotagem contra o typecheck. E
a recusa volta 200 com o motivo traduzido, em vez de erro cru.

Segunda porta no fim do wizard de instalação: numa VPS nova a tela de Casos
está vazia, e ninguém descobriria o aviso por lá.

Duas correções ao plano, medidas: a frase do descarte não diz "nos últimos 7
dias" porque o contador é acumulado — esse número não existe no schema; e o
laço de retorno mede os dois grupos a partir da abertura do caso, porque medir
o grupo avisado a partir do envio enviesa a favor do aviso por construção.

Verificado: typecheck, lint (372 warnings — o mesmo número das ondas 1 a 7),
lint:channels e release:conferir zerados; suíte com 10.163 verdes; 10
sabotagens previstas antes e medidas, com as 2 previsões erradas declaradas.
Dois defeitos meus achados na releitura: um `||` que destravava o botão sem
endereço público, e a conexão arquivada que sumia da lista sem explicação.

PENDENTE, declarado: e2e e prova em tela (onda 12); o item na Central no
primeiro caso sem configuração precisa de um kind que ainda não existe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqKEskriDoLeY45kmFtCgE
2026-09-18 03:36:15 -03:00
Pessoa ef4f632856 docs(doutrina): a auth de servidor é direção, e o prefixo documentado não existia
A doutrina prometia `Authorization: Bearer tok_...` em CLAUDE.md, AGENTS.md e
na Spec 09. O código exige `dsk_` (lib/mcp/auth.ts:93), e `tok_` tem ZERO
ocorrências em lib/ e app/ — quem seguia o texto recebia 401 sem entender.

Também trocada a afirmação de alcance por direção mais comando. O texto
descrevia auth dual como se valesse em toda a /api/v1, quando ela é habilitada
rota por rota pelo helper lib/api/auth-dual.ts. Em vez de um número que
envelhece, o texto agora traz `git grep -ln "auth-dual" -- app/api/v1`.

Acrescentado o que a doutrina não dizia e custou 26 rotas duplicadas a um
contribuidor (PR #1008): chamar o helper na rota não basta, porque o proxy.ts
global roda antes do handler e só reconhece cookie — sem entrada em
lib/auth/public-paths.ts, todo bearer leva 401 antes de chegar lá.

Decisão do dono do produto em 17/09/2026 (documento de decisão 28, opção A):
converter as rotas que cada integração precisar, conforme aparecerem, em vez de
namespace paralelo por cliente.

AGENTS.md entra junto por decisão, não por inércia: ele já enumera as
superfícies não-cookie, e omitir as rotas de /api/v1 que aceitam bearer tornava
esse inventário falso para o agente que lê só ele.

Fora de escopo, e por isso não tocado: Spec 01 (§api-tokens) e Spec 11 definem
o FORMATO do token como `tok_<env>_<base62>`, com o prefixo exibido na UI. Ali
a divergência é de desenho contra implementação, não frase solta, e reescrever
desenho de spec em silêncio seria pior que a divergência. Vai para issue.
2026-09-17 19:47:35 -03:00
Elias GervannoandClaude Opus 5 0ee5a61db3 feat(atendimento): a conversa parada com humano volta ao agente sozinha depois do prazo
A regra IA-06 (o bot não reassume depois da passagem a humano até alguém
clicar "Devolver") continua sendo o padrão. O que entra é um prazo
OPCIONAL por organização — Configurações › Distribuição de atendimento,
`settings.routing.handoff_return_after_minutes`, 5 min a 24 h, null =
nunca — e o cron `handoff-devolucao` (a cada 5 min) que devolve ao agente
a conversa que está com humano de forma durável e ficou o prazo inteiro
sem NENHUM sinal humano: o maior entre a passagem, o assumir e a última
mensagem que saiu (inclusive pelo celular).

Motivo, medido numa instalação real: 12 de 31 conversas ativas do dia
estavam em handoff formal, nenhuma foi devolvida, e o cliente que
escreveu de novo ficou sem resposta — humano ocupado, IA proibida. O
botão existe; ninguém clica.

O cron não tem regra própria de devolução: cada conversa vencida passa
por `devolverAtendimentoAoAgente`, a MESMA função do botão e da tool do
agente, com uma origem `automatica` que o rastro registra — a
autorização do contato entra como `automacao:devolucao_apos_prazo` (não
"retomada_manual"), a atividade na linha do tempo diz "após N min" como
ato do sistema, e o audit leva `automatica: true`.

O que fica de fora, e por quê (lib/escalacao/devolucao-automatica.ts):
sessão sem agente publicado nem roteador ativo (devolver para ninguém
tira a conversa da fila humana e a deixa muda); conversa encerrada; a
pausa por resposta pelo celular (silêncio finito, vence sozinha); e
conversa sem nenhum carimbo de tempo.

A mescla do PATCH de /settings/routing saiu para
`mesclarSettingsDeAtendimento`, que a rota e o teste leem — o teste era
uma cópia da rota. Cliente antigo que omite a chave preserva o prazo em
vigor, como já valia para `visibility_mode`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:34:09 -03:00
PessoaandClaude Opus 5 51cf0c9de9 fix(casos): o caso só nasce do motor, e a passagem de dono também
Três tabelas nasceram graváveis por QUALQUER membro da organização, pelas
duas origens: o `ALTER DEFAULT PRIVILEGES … GRANT ALL ON TABLES TO
"authenticated"` do baseline, que vale para toda tabela criada depois dele,
e uma policy `for all`/`for insert` sem papel mínimo.

O que se pagava: um `viewer` escrevia, pelo PostgREST e com o JWT dele, o
título/resumo/bloqueio que a equipe lê para decidir um caso — e, com o aviso
de caso por WhatsApp (próximas ondas), esse texto sairia no celular da equipe
pelo número da empresa, com o link legítimo ao lado. Em
`conversation_assignment_events`, um INSERT forjado faz o histórico afirmar
que alguém assumiu um atendimento que ninguém assumiu.

A migration 0279 fecha as duas origens nas três tabelas e reserva
`ai.case_opened`/`ai.case_closed` no `emit_event` (a reserva sozinha não
fecha nada: ela só barra quem tem `auth.uid()`; quem fecha a forja da linha
é o revoke). SELECT continua aberto nas três — a tela, o MCP e o motor leem.

Censo no cabeçalho da migration: nenhum caminho legítimo escreve por
`authenticated`. O motor usa `pg.Pool`, o cron usa service role, e
claim/transfer/release passam por `fn_conversation_assign`, que é
`security definer` — o controle positivo do invariante novo prova isso.

O baseline deixou de CRIAR as três policies largas para derrubá-las no fim:
criar e derrubar no mesmo arquivo faz a regra antiga valer entre os dois
pontos de toda instalação.

`tests/invariants/gov-3-assignment-events.test.ts` ganhou uma ponte mínima:
ele traduzia "barrado" em zero linhas (negação por RLS), e agora o Postgres
barra antes, por privilégio. As asserções não mudaram.

PENDENTE, e declarado: `pnpm test:db` não foi exercitado — o Docker local
está devolvendo 500 em `docker exec` sob a carga desta máquina (208 arquivos
morreram no setup, 1 asserção). Verde hoje: typecheck, lint e os seis gates
estáticos do baseline (ordem do apêndice, derivação da cadeia, e o gate que
proíbe o baseline construir o que ele mesmo derruba).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqKEskriDoLeY45kmFtCgE
2026-09-17 19:09:31 -03:00
PessoaandClaude Opus 5 bf4a520ed7 chore: o vercel.ts sai, e com ele a trava que o obrigava
Decisao do dono, hoje: "Vercel.ts sai". O arquivo era o inventario de crons
para quem hospedasse uma copia do CRM na Vercel, e
`tests/unit/cron-routes-scheduled.test.ts` obrigava toda rota de cron nova a ser
cadastrada nele — custo de manutencao pago a um destino que o produto nao usa
desde que o projeto foi desvinculado do GitHub.

O que sai: o `vercel.ts`, o caso do gate que o lia, o check do
`scripts/qa-wave-13-08.ts` que afirmava que o `maxDuration` morava nele (agora le
a propria rota, onde o valor esta), e a dependencia `@vercel/config`, cujo unico
importador era o arquivo apagado.

O que FICA vigiado, e e a cerca que importa: rota de cron sem linha no crontab do
`scheduler`, e linha de crontab apontando para rota que nao existe. A primeira
direcao foi provada por sabotagem (1 de 4 casos reprovou, o previsto). Fica
tambem a guarda da copia `CRON_SECRET` -> `INTERNAL_CRON_SECRET` do `lib/env.ts`,
que era a unica que existia — com a asercao consertada: ela casava `CRON_SECRET`
dentro da propria string `INTERNAL_CRON_SECRET` e passava mesmo sem a leitura da
variavel.

O runbook `vercel-hobby-relogio.md` existia em torno do arquivo apagado e do
plano gratuito daquela plataforma. Virou `relogio-http.md` e passou a tratar do
que realmente resolve: instalacao sem agendador de minuto, que bate o relogio
HTTP de fora. Todas as citacoes ao nome antigo foram atualizadas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 16:44:09 -03:00
PessoaandClaude Opus 5 73857d4881 docs: a Vercel deixa de aparecer como onde o CRM roda e valida
O projeto do CRM na Vercel foi desvinculado do GitHub por decisao do dono: a
plataforma fica so com a landing page, em outro repositorio. Medido com a mesma
sonda nos dois lados — `gh api repos/.../commits/<sha>/status` devolve `Vercel`
no commit f65d04667 (antes) e vazio em a8c9e1ad3, 7168961ad e no head do #1081
(depois).

Com isso, dezenas de afirmacoes do repositorio publico viraram falsas. As que
falavam no presente sairam:

- mensagem automatica ao contribuinte, molde de PR, CONTRIBUTING e os espelhos
  na skill de contribuir prometiam um check "Vercel" vermelho que nao aparece
  mais. No lugar, a mensagem passa a dizer onde olhar o que trava o merge — os
  checks marcados Required no proprio PR, com a ressalva de que `--required` so
  lista os que ja reportaram;
- runbooks de operacao mandavam abrir o painel da plataforma e redeployar a
  main; passam a descrever o `.env` da instalacao e a recriacao dos conteineres;
- `lib/env.ts` mandava, no boot, ajustar variavel "na Vercel";
- specs e PRDs descreviam topologia, crons e guarda de segredo na plataforma;
- comentarios de codigo ancoravam decisoes vivas em premissa morta.

Registros datados (pesquisa, pitch, epicos, handoffs) nao foram reescritos:
ganharam uma linha dizendo que sao registro da fase hospedada.

O `vercel.ts` fica: ele serve quem hospeda um fork, e o gate
`tests/unit/cron-routes-scheduled.test.ts` continua reprovando divergencia de
rotas entre ele e o crontab do scheduler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:00:03 -03:00
PessoaandClaude Opus 5 e24a2b94e3 docs(extensoes): as jornadas das extensões viram J25 e J26
O lote 12 da triagem criou na main a J24 "O vocabulário de etiquetas da
organização", já citada em `evidence/triagem-16set-l12/` e em
`tests/e2e/qa-l12-tags-radar.spec.ts` (J24.1 a J24.6). O merge da main
juntou as duas J24, e `tests/unit/numero-de-jornada-e-unico.test.ts`
reprovou — o git une as duas seções sem reclamar.

A J24 da main fica como está. As das extensões passam a J25 (instalar e
usar) e J26 (atualizar, desfazer, remover e reinstalar), e as citações
acompanham só onde são das extensões: as duas seções do mapa, a doutrina,
a spec, o mapa de arquitetura, a spec de tela e a fixture do catálogo.
Nenhum PR aberto cria jornada nova (medido com controle positivo: dos
três que tocam o mapa, só o #1016 acrescenta seção).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 08:32:57 -03:00
PessoaandClaude Opus 5 1e6d4f381a Merge origin/main (lote 12 e release 1.30.0) no PR de extensões; migration vira 0271
A main andou 191 commits e trouxe a própria 0263
(`etapa_de_perda_grava_o_motivo`) e a 0264 (`vocabulario_de_tags`). A
migration das extensões colidia no NNNN e passa a ser
`20260917120000_0271_extensoes_declarativas.sql` — número E timestamp
trocados juntos. 0271 é o primeiro número livre medido na main (até
0264) e nos 69 arquivos de migration dos PRs abertos (reservados até
0270). A renumeração é escopada ao que é das extensões: nome do arquivo,
cabeçalho, os quatro rótulos do bloco no baseline, a linha do MANIFEST
(movida para depois da 0264), o teste que procura o arquivo e três
comentários. As citações da 0263 da main (lote de perda) não foram
tocadas.

Nenhuma das 13 funções das extensões é redefinida pela 0263/0264 da main
nem por migration posterior, então a nova posição na cadeia não muda
qual corpo vence. O bloco no baseline continua byte a byte igual à
migration (47.853 bytes) e já estava depois da 0264 e antes da varredura
de anon, que precisa ser o último bloco.

Conflitos, os dois de apêndice, resolvidos com os dois lados:
- `.github/workflows/e2e.yml`: as três specs de extensões ficam na parte
  3, junto das duas de QA do lote 12. A main mediu a parte 3 em 22 min
  contra o teto de 30; as três somam ~1,5 min na última rodada local.
- `lib/i18n/dicionario.ts`: 251 linhas das extensões e 80 da main, sem
  chave repetida entre os blocos.

DESKCOMM_GOV_MIGRATION_EDIT=1 — o pre-commit acusou duas colisões que
não existem na árvore deste commit, medidas antes de usar a exceção:
(1) a 0263 da main contra o NOME ANTIGO da migration das extensões na
ponta da branch, que é exatamente o que este commit renomeia; (2) a 0264
da main contra a branch local `fix/887-prelude-acl-de-tabelas`, que tem
o MESMO arquivo (blob `10d0346f` nos dois lados). A autoridade é o gate
de CI `pnpm checar:colisao-de-migration`, rodado logo depois deste
commit.

DESKCOMM_GOV_INVARIANTS_EDIT=1: dos cinco invariantes que o merge toca,
quatro chegam da main idênticos a ela. O quinto,
`vocabulario-banco-x-typescript.test.ts`, difere da main só por 14
linhas ACRESCENTADAS por esta branch — as colunas `kind` e `status` do
recibo das extensões entrando no gate — e pelo número do comentário
(0263 → 0271). Nenhuma linha da main foi removida ou alterada.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 08:20:52 -03:00