-
Notifications
You must be signed in to change notification settings - Fork 603
Comparing changes
Open a pull request
base repository: melgarafael/DeskcommCRM
base: v1.4.0
head repository: melgarafael/DeskcommCRM
compare: v1.4.1
- 16 commits
- 25 files changed
- 5 contributors
Commits on Aug 25, 2026
-
fix(install): cópia irmã do repo era lida como re-execução e instalav…
…a por cima `decide_proxy` liberava a instalação quando o dono das portas 80/443 tinha o MESMO nome de projeto que o meu — tratando isso como "sou eu rodando de novo". Mas o nome do projeto é o basename da pasta, e toda cópia do repo se chama DeskcommCRM: duas árvores irmãs colidem no nome `deskcommcrm` e cada uma declarava a outra re-execução de si mesma. Era exatamente o caso que a varredura de portas foi escrita para pegar ("um Caddy — inclusive o de outro DeskcommCRM instalado na mesma VPS"), e ela tinha ponto cego nele. Medido numa VPS de produção em 2026-08-24: uma instalação de aula em /root/apagar7/DeskcommCRM passou por aqui, recriou os contêineres do CRM que estava no ar (/root/DeskcommCRM) com o .env dela e trocou o banco da produção — sem um aviso. O WhatsApp seguiu conectado gravando no banco errado, e o sintoma que chegou primeiro foi "minha senha parou de funcionar" (a conta é outra no outro banco), o que manda a investigação para o lado errado. Quem separa as duas é a ÁRVORE (label com.docker.compose.project.working_dir), não o nome. Árvore desconhecida cai no comportamento anterior de propósito: não dá para AFIRMAR cópia irmã, e fechar no escuro quebraria a re-execução legítima que o próprio kit ensina como caminho para corrigir uma resposta. O eco de `dono_das_portas` não mudou (é contrato com 7 fixtures) — a árvore vem de um `docker inspect` no ocupante já identificado. O teste que deveria vigiar isto existia e não podia falhar: "Caddy de OUTRO Deskcomm → bloqueia" usava projeto "outro" vs "crm", nomes DIFERENTES, que é justamente o que não difere no caso real. Os 3 casos novos usam o fixture real (mesmo projeto, árvores diferentes) e prendem os dois lados: a cópia irmã bloqueia, a re-execução de verdade segue passando. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Configuration menu - View commit details
-
Copy full SHA for 4312b40 - Browse repository at this point
Copy the full SHA 4312b40View commit details -
test(install): prende o CALL SITE, não só a regra de decide_proxy
Os casos de decide_proxy exercitam a função pura. O defeito pode voltar por dois caminhos independentes — a regra, ou deixar de passar a árvore na chamada — e um teste só da função fica verde enquanto o instalador sobe por cima da produção. Este roda o install.sh inteiro contra o `docker` dublê, reproduzindo a VPS de 2026-08-24: um DeskcommCRM no ar em /root/DeskcommCRM e o instalador rodando de outra cópia cuja pasta também se chama DeskcommCRM. Verifica que recusa, que nomeia a pasta da instalação que já existe, que ensina o update.sh e que NÃO chega a subir stack nenhuma. O par é o teste: o segundo caso é a MESMA VPS com o instalador rodando de dentro da árvore dona — re-execução legítima, que o kit ensina como caminho para corrigir uma resposta. Sem ele, bastaria bloquear tudo para o primeiro passar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for c865338 - Browse repository at this point
Copy the full SHA c865338View commit details -
fix(1.4.1): a release contou errado duas mudanças, e o gate que a gua…
…rda mede a grandeza errada Forward-fix da v1.4.0, cortada hoje às 20:29. Triagem de 5 lentes independentes sobre a prévia do merge; 12 achados sobreviveram a um cético que tentou derrubar cada um. Quatro foram reverificados à mão antes deste commit. **A tag é imutável, e o `agent.sh` roda a cada 5 minutos.** Da tag até aqui passaram ~14 ciclos: toda VPS de pé já leu o texto da 1.4.0. Editar a seção dela na `main` não alcança ninguém — por isso os consertos de texto saem como `[1.4.1]`, que é o único caminho até a tela de quem atualizou. ─── 1. O aviso das conexões duplicadas estava INVERTIDO ──────────────────────── O CHANGELOG dizia "a atualização mantém a mais antiga e marca a outra como conflito". O SQL do baseline faz o contrário: order by created_at desc nulls last, id desc -- baseline.sql:13923 and a.posicao > 1 -- baseline.sql:13933 Fica com o identificador limpo a MAIS RECENTE; a mais antiga recebe `-conflito-`. O comentário do próprio código, doze linhas acima, já dizia isso ("Fica com o identificador a sessão ativa MAIS RECENTE"), e o texto dizia o oposto — a duas telas de distância. Consequência: o aviso termina com "confira qual das duas deve continuar e apague a que sobra". Quem acreditasse na frase apagaria a conexão SEM o sufixo — que é justamente a que detém o `meta_phone_number_id` no provider e continua recebendo. Ação irreversível numa conexão de WhatsApp de produção. ─── 2. O domingo mudou para TODA instalação, sem uma linha de aviso ──────────── `allowSunday` foi de `false` para `true` (`pacing/defaults.ts:62`). A coluna `allow_sunday` é nullable, sem DEFAULT, e nenhuma migration faz backfill: quem nunca tocou a chave tem NULL, e o `??` passa a resolver para `true` no instante do `update.sh`. Quem faz prospecção e contava com o silêncio de domingo começa a disparar no domingo sem ter decidido nada. Medido: o bloco `⚠️ Requer atenção` da 1.4.0 tem ZERO ocorrências de "domingo" (controle: 5 no arquivo inteiro, fora do bloco) — e termina dizendo "Fora isso, nada exige ação sua". Duas afirmações de estado que a virada deixou falsas foram corrigidas junto: `baseline.sql:6756` ("domingo evitado por default") e `pacing/engine.ts:9`. A migration 0050 NÃO foi tocada: migration aplicada é histórico. ─── 3. `salir` não descadastrava, e a release disse que sim ──────────────────── O CHANGELOG anunciou que "`baja`, `salir` e `no quiero recibir` descadastram". Medido com as funções reais, com controle de vivacidade: "stop" pedido=true provavel=true "sair" pedido=true provavel=true "baja" pedido=true provavel=true "salir" pedido=false provavel=false ← a promessa "no quiero recibir" pedido=true provavel=true "tem como parar a dor?" pedido=false provavel=false ← controle `salir` é o `sair` em espanhol, que está na lista desde sempre — faltar era assimetria, não decisão. No canal em que denúncia de spam derruba o quality rating e faz a plataforma recusar definições novas. Não alarga a regra: continua valendo só a palavra SOZINHA. Os três controles negativos novos ("voy a salir ahora", "puedo salir mas temprano?", "el pedido va a salir hoy?") existem para que ninguém "conserte" isso com um `includes`. Sabotado removendo `salir` do Set: **3 falhas, exatamente as previstas** (os três positivos; os controles seguem verdes). 71/71 com ele. ─── 4. Um controle citado por um nome que não existe na tela ────────────────── A seção mandava procurar "Parar a IA no limite" (duas ocorrências). Zero ocorrências desse texto em `components|app|lib`; o rótulo real é `Parar a IA ao chegar em ${fmtCents(teto)}` (`BudgetCard.tsx:399`). Dois dos três rótulos citados batiam e o terceiro não, o que é pior que errar os três — o usuário confia na lista. ─── 5. E o import assume Brasil sem dizer ────────────────────────────────────── `normalizaTelefone("(11) 99999-8888")` → `+5511999998888`. O bullet da 1.4.0 é minucioso (separador, colunas em português, 500 linhas, `.xlsx` recusado) e cala na única transformação que altera o dado do cliente. A MESMA release adicionou opt-out em espanhol, o que prova que o público não-brasileiro existe. Comportamento correto e decidido; faltava a frase. ─── 6. O gate da própria release media a grandeza errada ─────────────────────── `tests/unit/changelog-cabe-na-tela-da-vps.test.ts` somava o offset ABSOLUTO do fim da seção no working tree — o que embute o `[Não lançado]` do momento. Num arquivo TAGUEADO essas duas coisas nunca coexistem: medido nas cinco tags do origin, `[Não lançado]` tem 20 bytes, sempre. Com 720 bytes de folga depois da 1.4.0, reproduzi com 3.645 bytes de changelog de um ciclo normal (o repo já teve 3.632 dois dias depois da v1.3.0): AssertionError: A seção [1.4.0] termina no byte 34288, além do corte de 30000 O `verify` — status check OBRIGATÓRIO — ficaria vermelho na `main` por uma condição que nenhum cliente enxerga, mandando enxugar uma seção já congelada cuja cópia taggeada está correta. O conserto que a mensagem sugere não mudaria nada para ninguém. Agora mede o arquivo **como ele fica na tag** (`comoFicaNaTag()`: o `[Não lançado]` esvazia e o conteúdo dele vira a seção numerada), e cobra as duas seções que podem estourar separadamente — a já numerada e o `[Não lançado]`, que é a única das duas que ainda dá para enxugar. Ganhou guarda de vacuidade: sem ela, um parser quebrado zeraria os `it.each` e a suíte ficaria verde por não haver caso. Previsões declaradas antes de rodar, ambas batidas: - `[Não lançado]` com 3.645 bytes → **5 verdes** (o gate antigo reprovava); - seção nova inflada além do teto → **1 falha**, a do tamanho. A mensagem de erro deixou de afirmar que a seção "já foi taggeada" — ela não sabe, e o conserto certo depende disso. Caminho de produção, com o extrator real sobre os 30.000 bytes crus: `requiresAttention` = 1.315 bytes, com os dois avisos inteiros; `body` termina em frase completa. (Uma primeira sonda minha disse que o aviso das conexões não chegava — era ela procurando "MAIS RECENTE" numa linha só, e o texto tem a frase quebrada pelo wrap. Falso negativo do instrumento, não do produto.) `typecheck` exit 0 · `lint` 0 erros · `lint:channels` ok · `pnpm test:unit` **478 arquivos, 5336 testes, verdes**. NÃO MEDIDO: nada disto foi visto numa VPS real nem pela tela de atualização num browser (DoD 12); `pnpm test:shell` não foi rodado; e não sei quantas instalações existem nem em que versão estão — sei que o texto errado alcança todas as que leram a v1.4.0, não quantas são.Configuration menu - View commit details
-
Copy full SHA for d154ee1 - Browse repository at this point
Copy the full SHA d154ee1View commit details -
docs: o invariante 8 valia para quem atualiza, não para quem instala
A doutrina afirmava "uma segunda cópia do repo na mesma VPS recusa mexer" e listava "os dois call sites (agent.sh e update.sh)". A afirmação estava incompleta nos dois pontos: o install.sh é um terceiro call site, e era justamente ele que NÃO honrava o invariante — quem instalava passava por cima da produção enquanto quem atualizava era barrado. Acrescenta o anti-exemplo medido de 2026-08-24 (o da instalação, não o da atualização) e nomeia onde o terceiro call site é verificado, com a medição que justifica haver teste de integração além do teste da regra. CHANGELOG em linguagem de quem opera a VPS: o sintoma que a pessoa vê é a senha parar de funcionar, não "colisão de projeto Docker". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 7c882f5 - Browse repository at this point
Copy the full SHA 7c882f5View commit details -
feat(onboarding): o passo do telefone pergunta COMO se conecta, em ve…
…z de assumir O produto conecta um número de três formas, e a tela de Conexões oferece as três. O wizard oferecia uma — e nem isso: ele não perguntava. `_client.tsx` montava e o primeiro efeito já disparava `POST /api/v1/onboarding/whatsapp/session`. A rota grava a linha em `channel_sessions` sem informar `provider`, e a coluna nasce com o default do transporte por código. Ou seja: **o canal nascia só de a pessoa pisar na rota**. Quem tem conta oficial na Meta já estava no caminho errado antes de clicar em coisa nenhuma, e só descobria depois, em outra tela, com o funcionário montado por cima. Medido na jornada, contando linhas do banco numa organização recém-criada: CANAIS CRIADOS SEM ESCOLHER: 0 (era 1) CANAIS APOS NAVEGAR PELOS TRES RAMOS: 0 CANAIS APOS ESCOLHER O CODIGO: 1 A do meio é a que importa: passear pelos três ramos não deixa canal pendurado. ── Três coisas que a medição decidiu, contra o palpite ───────────────────── 1. NÃO se aponta para Conexões. Era o desenho óbvio — perguntar e encaminhar — e ele é impossível: `app/app/layout.tsx:51` devolve `/app/*` para `/onboarding` enquanto `onboarded_at` for nulo. O botão voltaria ao próprio passo, que é o controle decorativo do PR #295. Os clientes de Conexões são montados AQUI, inteiros, sem cópia — os dois são "use client", não recebem prop e buscam o próprio estado. 2. A escolha NÃO é gravada. `cumprido` do passo é `Boolean(state.whatsapp)`: persistir no clique marcaria o passo como resolvido, e quem fechasse o navegador cairia no passo seguinte sem telefone e sem volta, porque o roteador só devolve o primeiro passo NÃO cumprido. Vive em `useState`, e o caso "dá para voltar e trocar de forma" é o que guarda isso. 3. O caminho oficial avisa ANTES. O `install.sh` não escreve `META_WEBHOOK_VERIFY_TOKEN`, então em instalação fresca esse canal envia e nunca recebe. Dizer isso depois de a pessoa buscar três credenciais no painel da Meta é tarde; a tela diz antes, e aponta o caminho que funciona hoje. ── Forma ─────────────────────────────────────────────────────────────────── `<label>` com radio nativo, copiado de `app/app/settings/atendimento/_form.tsx`. Não é `RadioGroup` porque esse componente não existe neste repo — instalá-lo criaria um quarto dialeto de escolha ao lado de três iguais. E não é `<Card>`: o wizard usa a div crua `rounded-lg border bg-background p-6`, 14 vezes. O QR ficou onde estava, de propósito. Extraí-lo obrigaria a apagar a linha 106 de `scripts/lint-channels.ts` e a reescrever três comentários corretos só para escapar de um regex — e o cabeçalho daquele arquivo registra que reescrever prosa correta para fugir do gate piora o código. ── Verificação ───────────────────────────────────────────────────────────── `pnpm test:unit` 5338 (+9), typecheck limpo, `pnpm lint` 0 erros, `pnpm lint:channels` ok nas duas direções da catraca. Pela TELA, em `next start` de produção contra Supabase local com o `baseline.sql`, sem WAHA, sem chave de IA e sem `META_*` — o estado de quem acabou de instalar: `wizard-do-funcionario.spec.ts` 13/13, evidência em `evidence/canal-no-onboarding.md`. `vps-fresh-onboarding.spec.ts` (a P0 da doutrina de QA Visual) precisou de um clique novo: ela esperava o código aparecer sozinho e teria quebrado EM SILÊNCIO, porque não roda no CI. Sabotagem: devolver o POST para a montagem reprova 4 dos 9 casos do teste novo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126ezJHDNKTi7J1EM2GpfvJConfiguration menu - View commit details
-
Copy full SHA for 6f82e97 - Browse repository at this point
Copy the full SHA 6f82e97View commit details -
fix(agente): o relógio injetável era declarado 330 linhas DEPOIS das …
…guardas que dependem dele O `invariants` do PR #319 falhou com 8 casos, e o diff dele nesses arquivos eram DUAS LINHAS DE COMENTÁRIO. A causa é anterior, e vinha reprovando a `main` inteira nove horas por dia sem ninguém ligar os pontos. ─── O defeito ───────────────────────────────────────────────────────────────── `InboundTurnDeps.clock` existe e o contrato dele é explícito (`inbound-turn.ts:620`): Relógio injetável (F2-13) — a janela horária do gate anti-ban é avaliada nele. Default `() => new Date()`; os testes fixam um instante dentro da janela para determinismo. Só que ele era declarado na linha **1411**, e as duas guardas que mais dependem dele estão nas linhas **1077** (janela anti-ban) e **1169** (horário de funcionamento) — antes de existir. As duas chamavam `new Date()` cru. O relógio injetado não alcançava justamente aquilo que ele existe para fixar. Em produção dá no mesmo: lá o `new Date()` É a hora certa. No CI, a hora de PAREDE decidia — e como a janela padrão é 7h–22h no fuso do tenant (`America/Sao_Paulo`), a suíte de invariantes reprovava das 22h às 7h. Nove horas por dia em que qualquer PR falha por motivo nenhum. A correlação está nos dados do próprio CI: TODAS as execuções verdes das últimas 24h rodaram entre 12:36 e 23:28 UTC (23:28 UTC = 20:28 em São Paulo, dentro da janela). A do #319 rodou às 01:13 UTC = 22:13 — fora. ─── A prova, sem tocar em relógio nenhum ────────────────────────────────────── Reproduzido na máquina local às 22h, que é a hora em que a `main` reprova: código da main, 22:19 BRT ......... 8 falhas — as MESMAS 8 do CI com o clock movido para o topo .... 1 falha + o teste que nunca injetou clock . 9/9 verdes, ainda 22:21 A segunda linha é o ponto. Consertar as duas guardas deixou UM caso de pé: `agent-no-credential.test.ts` nunca fixou o relógio — os arquivos irmãos (`limite-de-envios-por-turno`, `agent-send-template-turn`) já faziam, e ele ficou de fora. A guarda de horário vem ANTES da checagem de credencial no fluxo, então fora da janela o turno é adiado e nunca chega ao `credencial LLM` que a asserção procura: o teste media o motivo errado. Parar em "de 8 para 1, melhorou" deixaria o CI flaky pela terceira causa. Suíte de invariantes completa, às 22:30 (fora da janela): **113 arquivos, 847 testes, exit 0**. A mudança em `tests/invariants/**` é ADITIVA — nenhuma linha removida contém asserção, e a contagem de casos não muda (1 antes, 1 depois). O que entra é o `clock` fixo que os irmãos já tinham. `DESKCOMM_GOV_INVARIANTS_EDIT=1`. ─── A classe inteira, não só a instância ────────────────────────────────────── Varridos os 11 arquivos que aceitam clock injetável em `lib/` e `workers/`. Os outros quatro `new Date()` crus deles NÃO são o mesmo defeito, e cada um foi conferido: followup/turn-bridge.ts:62 .............. default de parâmetro, legítimo agent/followup-turn.ts:274 .............. default, e o arquivo não tem guarda de janela nenhuma (zero ocorrências de janelaDeEnvioAberta) agent/followup-flow-classify.ts:206 ..... default, aplicado no próprio uso followup/gatilho-caso.ts:397 ............ `completed_at` — carimbo do instante da gravação, não decisão de janela; a hora real é a certa `inbound-turn.ts` era o único com o padrão "guarda de janela decidindo por relógio que não é o injetado". ─── Dois cuidados de método que mudaram o resultado ─────────────────────────── 1. A primeira execução saiu `exit=0` com falhas na tela: era o exit do `tail` do pipe, não do script. Refeito com `set -o pipefail`. 2. A primeira suíte completa deu "53 arquivos falhando" e a razão era `No such container` — outra sessão rodava `test:db` ao mesmo tempo, e `scripts/test-db.sh` usa PORTA FIXA (`TEST_DB_PORT:-54329`). O Postgres sumiu debaixo dos testes. Medição descartada e refeita em `TEST_DB_PORT=54399`. (Que a porta seja fixa é defeito próprio do script, e o modo de falha se disfarça de teste quebrado — fica anotado, não é deste PR.)
Configuration menu - View commit details
-
Copy full SHA for 3da5587 - Browse repository at this point
Copy the full SHA 3da5587View commit details -
fix(changelog): a entrada nova empurrava a seção 1.4.0 para fora do c…
…orte da VPS `tests/unit/changelog-cabe-na-tela-da-vps.test.ts` reprovou: a seção [1.4.0] terminava no byte 30203 contra o teto de 30000 que o agent.sh aplica (`git show <tag>:CHANGELOG.md | head -c N`). Texto acrescentado ACIMA dela em [Não lançado] empurra o fim da seção, e o dono da VPS receberia o changelog cortado no meio de uma frase. Medido: a folga real era de 720 bytes e a entrada tinha 923. Enxugada para 464 — a seção agora termina em 29744, com 256 bytes de sobra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for 2686cc3 - Browse repository at this point
Copy the full SHA 2686cc3View commit details -
test(janela): a guarda dedicada do #323 — a que pega o defeito DE DIA
Terceira convergência independente desta rodada: o PR #323 (outra sessão) consertou exatamente as mesmas duas linhas deste PR, sem contato entre os dois. Isso é a evidência mais forte de que o defeito era real. Os consertos de código são equivalentes e ficou o daqui, por não duplicar: ele repete `(deps.clock ?? ((): Date => new Date()))()` nos dois pontos, o que somado à declaração que já existia na linha 1411 daria TRÊS lugares resolvendo o mesmo default. Aqui o `clock` é declarado uma vez, no topo da função. **Mas o teste dele é melhor que o meu conserto, e é ele que entra.** A matriz de sabotagem no cabeçalho do arquivo mostra o que eu não tinha visto: vizinho (3) "fora→adia" "dentro→corre" defeito à noite PEGAM passa (motivo PEGA errado) defeito de dia passam PEGA passa (motivo errado) Os casos que já existiam fixam um instante DENTRO da janela: com o defeito de volta eles só reprovam à noite — uma guarda que dorme das 7h às 22h, que é justamente quando o time trabalha. O caso "fora→adia" é o espelho, e de dia é a ÚNICA coisa entre o defeito e um CI verde. O segundo caso existe pela razão oposta: um gate que adiasse SEMPRE também satisfaria o primeiro, e "adia sempre" é outro jeito de o produto emudecer. Verificado contra o conserto DAQUI (declaração única), não só contra o dele: 3 arquivos, 6 casos, 23:01 BRT ............................ exit 0 sabotado (new Date() cru de volta), mesma hora ............ 5 falhas, 1 passa A previsão foi escrita antes de rodar e bateu: pegaram o "dentro→corre", os três do teto de envios e o `agent-no-credential`; passou só o "fora→adia" — pelo motivo errado, exatamente como a matriz dele prevê para o período noturno. O que este PR tem e o #323 não: o conserto de `agent-no-credential.test.ts`, que nunca injetou relógio. Sem ele o `invariants` segue vermelho fora da janela pela terceira causa — foi o caso que sobrou quando consertar as duas guardas levou de 8 falhas para 1. Crédito do arquivo de teste é da sessão do #323, que fica fechado apontando para cá.
Configuration menu - View commit details
-
Copy full SHA for c6bd41d - Browse repository at this point
Copy the full SHA c6bd41dView commit details -
Merge PR #319 — a 1.4.1, e o relógio de parede que reprovava a main n…
…ove horas por dia Forward-fix da v1.4.0 (cortada às 20:29) mais o conserto do defeito que a triagem encontrou no caminho. A 1.4.1: a seção da 1.4.0 inverteu o aviso das conexões duplicadas (o SQL mantém a MAIS RECENTE, o texto dizia a mais antiga), não avisou que o domingo passou a enviar em toda instalação, prometeu que 'salir' descadastra quando não descadastrava, e citou um rótulo de tela que não existe. A tag é imutável e o agent.sh roda a cada 5 min — só uma versão nova alcança quem já leu. O relógio: inbound-turn.ts declarava o clock injetável na linha 1411, e as duas guardas de janela que dependem dele estão nas 1077 e 1169. A suíte invariants reprovava das 22h às 7h no fuso do tenant. Reproduzido localmente às 22h: 8 falhas na main, 9/9 verdes com o conserto. O teste dedicado veio do #323 (convergência independente), que fica fechado apontando para cá. Cinco checks verdes, com o invariants rodando às 23:01 BRT — fora da janela, que é a hora em que a main reprovava.
Configuration menu - View commit details
-
Copy full SHA for abbc518 - Browse repository at this point
Copy the full SHA abbc518View commit details -
Configuration menu - View commit details
-
Copy full SHA for 855adae - Browse repository at this point
Copy the full SHA 855adaeView commit details -
fix(onboarding): "pode receber pelo canal oficial" media METADE da co…
…ndição Sobre o PR #321. O passo do telefone avisa quando a instalação ainda não consegue RECEBER pelo canal oficial — e a sonda que decide esse aviso conferia uma de duas variáveis: const oficialPodeReceber = Boolean(process.env.META_WEBHOOK_VERIFY_TOKEN); São dois segredos com papéis diferentes, e medido na ROTA inteira, não no helper (`app/api/v1/webhooks/meta/[token]/route.ts:63-66`): sem META_WEBHOOK_VERIFY_TOKEN → o handshake GET falha; o webhook nem é aceito sem META_APP_SECRET → o handshake devolve 200 e TODA mensagem inbound assinada devolve 401 `invalid_signature`, com ZERO ingestão O segundo é o caro, e é justamente o que a sonda não via: quem preenchesse só o verify token via a tela dizer que está pronto, cadastraria o número e não receberia nada — **sem erro em lugar nenhum**, que é o modo de falha que este aviso existe para impedir. E nenhum dos dois é escrito pelo `install.sh` (`git grep 'META_' -- '*.sh'` devolve vazio), então numa instalação nova os dois faltam: é exatamente quando o aviso importa. ─── Por que virou função, e não uma linha maior no `page.tsx` ───────────────── O teste que já existia cobre o COMPONENTE recebendo a prop (`onboarding-escolhe-como-conecta.test.tsx:83`, `montar({ oficialPodeReceber })`) — e o defeito estava no CALL SITE, que decide o valor da prop. Teste que guarda a função e não o call site não pegaria isto, e não pegou. A regra passa a morar em `lib/channels/meta/webhook.ts`, ao lado de `verifyMetaSignature`, que é quem consome o app secret. O `page.tsx` chama. `.trim() !== ""` e não `Boolean()`: o contrato do `.env` deste projeto é que vazio é ausente — o template gera `CHAVE=` —, e é assim que `preenchida()` em `lib/instalacao/ambiente.ts:61` já decide. `Boolean("")` é `false`, mas `Boolean(" ")` é `true`. A fonte é `Record<string, string | undefined>`, o mesmo tipo que `ambiente.ts` usa: `NodeJS.ProcessEnv` exige `NODE_ENV` e obrigaria todo teste a montá-lo. ─── A rede: 6 casos, sabotados nas duas direções ────────────────────────────── Previsões escritas antes de rodar, ambas batidas: voltar a exigir só o verify token .... previsto 3, medido 3 tirar o .trim() ...................... previsto 1, medido 1 (o caso "só espaços" — o único que `Boolean()` deixaria passar) ─── De quebra ──────────────────────────────────────────────────────────────── `evidence/canal-no-onboarding.md:35` dizia "passear pelos três ramos" para reproduzir a contagem 0 da linha 31. Visitar o terceiro ramo (o código) CRIA a sessão — a própria linha 32 do arquivo mostra `CANAIS APOS ESCOLHER O CODIGO: 1`. Quem reexecutasse a evidência mediria 1 onde o texto promete 0 e concluiria que a guarda quebrou. Corrigido para "os dois ramos de credencial". `tsc --noEmit` exit 0 · `lint` 0 erros · `lint:channels` ok · `pnpm test:unit` **5352 testes verdes**. NÃO MEDIDO: nada foi dirigido num browser contra ambiente fresco de verdade (Supabase pg17 + baseline + bootstrap-owner + build de produção + WAHA + Redis). A prova aqui é de função pura, componente em jsdom e leitura da rota. Co-authored-by: melgarafael <melgarafael@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 24041b8 - Browse repository at this point
Copy the full SHA 24041b8View commit details -
fix(install): o guarda de cópia irmã cobria 1 de ≥4 entradas — agora …
…é o invariante 8 Sobre o PR #320, que está certo no diagnóstico e conserta o caso que de fato aconteceu. O que a triagem mediu é que o alcance é menor do que parece. ─── O que o PR entrega, e onde ele não alcança ──────────────────────────────── O painel novo de cópia irmã (`install.sh:911`) vive dentro de DOIS aninhamentos: `if [ -z "${REVERSE_PROXY:-}" ]` (:855) e o ramo do `case` em que o irmão é o DONO das portas 80/443. Medido com o harness de VPS falsa do próprio kit, três entradas em que ele nunca é alcançado: (a) VPS com Traefik do painel (Coolify, Hostinger, Dokploy) — `decide_proxy` sai por `traefik` (:573) ANTES de comparar árvore. É o caminho que o CLAUDE.md descreve como o normal em VPS com proxy próprio; (b) pasta que já concluiu uma instalação — o próprio `install.sh` grava `REVERSE_PROXY="caddy"` no `.env` (:1413), e na rodada seguinte o `if [ -z … ]` é falso. **O instalador desligava o próprio guarda**, sem ninguém digitar nada; (c) portas 80/443 livres — `decide_proxy` devolve `caddy` na primeira linha. Nas três, `docker compose … up -d` sobe sobre o parque da produção com o `.env` da pasta nova: outro banco, outras chaves, e o sintoma que chega primeiro é a senha "parar de funcionar". ─── O conserto: ligar o guarda que já existia ───────────────────────────────── `recusar_projeto_de_outra_arvore` (`_common.sh:118`) já protegia `update.sh:33` e `agent.sh:45`, e não protegia o `install.sh` — a porta por onde o incidente entrou. Ele pergunta pelos CONTÊINERES do projeto, não pelo proxy, então vale nas três entradas acima. Fica logo depois de `PROJECT_DIR` (que a função precisa) e ANTES da coleta de config: recusar depois de arrancar sete respostas de quem instala é fazer a pessoa trabalhar para ouvir "não". Os dois mecanismos convivem de propósito: o painel do PR dá o diagnóstico melhor (nomeia as duas pastas, ensina o `update.sh`) quando é alcançado; o guarda é o que fecha a classe. ─── Os cinco controles, com o previsto escrito antes ────────────────────────── Harness real do kit (`montar_vps`/`rodar` de `test-validators.sh`): instalação NOVA (nenhum contêiner no ar) .... previsto SEGUE medido SEGUE re-executar na MESMA pasta .................. previsto SEGUE medido SEGUE instalação MOVIDA (pasta antiga sumiu) ...... previsto SEGUE medido SEGUE cópia irmã VIVA — o caso do incidente ....... previsto BLOQUEIA medido BLOQUEIA cópia irmã + DESKCOMM_ASSUMIR_PROJETO=1 ..... previsto SEGUE medido SEGUE O terceiro controle é o que impede o guarda de nascer vermelho: quem MOVE a instalação de pasta deixa contêineres apontando para um caminho que não existe mais, e o filtro "árvore viva" (`projeto_pertence_a_outra_arvore`) já trata isso. O quinto divergiu na primeira tentativa (previsto SEGUE, medido BLOQUEIA) e a causa era o MEU instrumento, não o código: pus `DESKCOMM_ASSUMIR_PROJETO` no `.env`, e o guarda roda antes de o `.env` ser lido. Pela via que a mensagem ensina — variável de ambiente — funciona. Isso virou uma linha na doutrina, porque quem ler a mensagem e escrever no arquivo vai achar que o escape quebrou. O caso (a) foi provado ponta a ponta: com o `coolify-proxy` como dono das portas, o instalador PARA em "Localizando o projeto", nomeia as duas pastas e não chega a perguntar nada. ─── A rede ──────────────────────────────────────────────────────────────────── `tests/shell/dono-do-projeto.test.sh` já prendia os call sites de `agent.sh` e `update.sh` — "guardar a função não basta se ninguém a chama". O terceiro entra com a mesma régua, mais a ORDEM (guarda antes da coleta). Sabotado removendo a chamada: **2 falhas, exatamente as previstas**. `docs/doctrine/packaging.md` afirmava "O terceiro call site é o `install.sh`" quando `grep -c 'recusar_projeto_de_outra_arvore' install.sh` devolvia **0** — a frase descrevia cobertura de teste e se lia como call site. Agora é verdade, e o texto passa a separar os dois mecanismos e a declarar o alcance de cada um. `pnpm test:shell` verde (o único gate que exercita o kit, e que NENHUM job de PR invoca) · `typecheck` exit 0 · `lint` 0 erros · `pnpm test:unit` 5338 testes. ─── O conflito de CHANGELOG ─────────────────────────────────────────────────── Resolvido somando: a entrada do #320 é trabalho ainda não lançado e fica sob `[Não lançado]`; a `[1.4.1]` do #319 fica abaixo, como release. A régua do `changelog-cabe-na-tela-da-vps` segue folgada — a seção mais nova é a `[1.4.1]`, curta, com ~26 KB de sobra. NÃO MEDIDO: nada numa VPS real — todos os cenários usam o dublê de `docker` do kit, que não estressa `docker inspect` real, label ausente por versão do compose, nem contêiner criado fora do compose. Também não medi a corrida entre o `install.sh` de uma árvore e o `agent.sh` da outra (cron de 5 min), que é o estado em que o incidente de 3 dias aconteceu. Co-authored-by: melgarafael <melgarafael@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 6e51ae9 - Browse repository at this point
Copy the full SHA 6e51ae9View commit details -
Merge PR #321 — o passo do telefone pergunta COMO se conecta
O primeiro acesso deixa de impor QR: quem tem conta oficial da Meta ou provedor parceiro escolhe o caminho dele. Na triagem: a tela avisava 'o canal oficial pode receber' checando UMA de duas variáveis. Medido na rota inteira, sem META_APP_SECRET o handshake devolve 200 e toda mensagem inbound morre em 401 invalid_signature com zero ingestão — quem preenchesse só o verify token cadastraria o número e nunca receberia nada, sem erro em lugar nenhum. A regra virou metaPodeReceber() em lib/channels/meta/webhook.ts, com 6 casos e sabotagem prevista-e-medida nas duas direções (3 e 1). Cinco checks obrigatórios verdes.
Configuration menu - View commit details
-
Copy full SHA for 05ac672 - Browse repository at this point
Copy the full SHA 05ac672View commit details -
Merge PR #320 — instalar de uma cópia irmã derrubava o CRM que estava…
… no ar O instalador confundia a instalação de outra pasta com ele mesmo sendo rodado de novo e subia por cima: o site seguia no ar usando o banco da pasta nova, e o primeiro sintoma era a senha 'parar de funcionar'. Na triagem: o painel novo vive dentro de dois aninhamentos e cobria 1 de pelo menos 4 entradas. Escapavam a VPS com Traefik de painel (Coolify/Hostinger), as portas 80/443 livres, e — a pior — a pasta que já concluiu uma instalação, porque o próprio install.sh grava REVERSE_PROXY no .env e na rodada seguinte desligava o próprio guarda. O conserto liga recusar_projeto_de_outra_arvore, que já protegia update.sh e agent.sh e pergunta pelos contêineres em vez do proxy. Cinco controles com previsão escrita antes, todos batendo; sabotagem do call site dá as 2 falhas previstas. Cinco checks obrigatórios verdes; pnpm test:shell verde.
Configuration menu - View commit details
-
Copy full SHA for 830f53e - Browse repository at this point
Copy the full SHA 830f53eView commit details -
docs(release): a 1.4.1 passa a descrever o que ela realmente contém
A seção `[1.4.1]` já existia, escrita antes de o onboarding entrar na `main`, e dizia de si mesma: "correção de texto de aviso, não de comportamento". Deixou de ser verdade quando o PR #321 mergeou. Isso importa porque a TAG aponta para o commit, não para a seção: cortar a v1.4.1 com aquele texto entregaria ao dono da VPS uma release que muda o primeiro acesso do produto anunciando-se como conserto de redação. A tela de atualização mostra exatamente essa seção (`git show <tag>:CHANGELOG.md`), então o texto É o que ele vai ler antes de decidir atualizar. O que entra: - **Adicionado** — o passo do telefone perguntando como a pessoa já usa o número (as três formas, cada uma levando ao formulário certo), e o aviso de que o caminho oficial precisa de duas configurações de servidor que a instalação não cria: sem elas o número envia e nunca recebe. - **Corrigido** — o guarda de instalação em cópia irmã, que estava em "[Não lançado]" e vai junto na mesma release. O conteúdo que já estava na seção fica VERBATIM: os dois avisos da 1.4.0 que precisavam de correção (domingo e `-conflito-`), o `salir`, a planilha e o rótulo do orçamento. Só a abertura foi reescrita, porque ela descrevia o escopo antigo. Fora do changelog de propósito: o conserto do relógio de parede (#319) não tem efeito em produção — lá `new Date()` É a hora certa; ele reprovava só o CI. E a sonda de canal oficial que media metade da condição (`24041b8d`) corrigiu código que nunca chegou a ser publicado. Verificado pelo caminho de produção: `changelog-cabe-na-tela-da-vps` verde, e a extração real sobre os 30.000 bytes que o agente envia devolve a seção inteira — com o onboarding, o instalador, o `salir` e os dois avisos, terminando em frase completa. Seção com 5.188 bytes; o corte é em 30.000. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126ezJHDNKTi7J1EM2GpfvJ
Configuration menu - View commit details
-
Copy full SHA for d453ac3 - Browse repository at this point
Copy the full SHA d453ac3View commit details -
Merge PR #330 — a 1.4.1 passa a descrever o que ela realmente contém
A seção existia escrita antes de o onboarding entrar. A tag aponta para o commit, não para a seção: cortar assim entregaria uma release que muda o primeiro acesso anunciando-se como conserto de redação.
Configuration menu - View commit details
-
Copy full SHA for bea0d50 - Browse repository at this point
Copy the full SHA bea0d50View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v1.4.0...v1.4.1