Skip to content

Commit 3da5587

Browse files
committed
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.)
1 parent d154ee1 commit 3da5587

2 files changed

Lines changed: 22 additions & 3 deletions

File tree

lib/agent-engine/agent/inbound-turn.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,16 @@ async function executarTurnoDoAgente(
10291029
if (leadId === null) {
10301030
throw new Error('job de turno sem contact_id — o CHECK da fila deveria impedir');
10311031
}
1032+
// O RELÓGIO, declarado antes de qualquer guarda de janela.
1033+
//
1034+
// Ele já existia — 330 linhas ABAIXO, depois das duas guardas que mais
1035+
// dependem dele. O contrato de `InboundTurnDeps.clock` diz "a janela horária
1036+
// do gate anti-ban é avaliada nele", e as duas guardas chamavam `new Date()`
1037+
// cru: o relógio injetado não alcançava justamente o que ele existe para
1038+
// fixar. Em produção dá no mesmo; no CI, a hora real do runner decidia, e a
1039+
// suíte de invariantes ficava vermelha das 22h às 7h (fuso do tenant) — nove
1040+
// horas por dia em que um PR reprova por causa do relógio de parede.
1041+
const clock = deps.clock ?? ((): Date => new Date());
10321042
const contextKnobs = { historyLimit: deps.knobs.historyLimit, maxTokens: deps.knobs.maxContextTokens };
10331043
// Contexto do RUN em toda linha de log do turno (F2-16): job_id É o run id.
10341044
const runLog = withFields(deps.log, { job_id: job.id, tenant_id: tenantId, lead_id: leadId });
@@ -1074,7 +1084,7 @@ async function executarTurnoDoAgente(
10741084
// hora do envio, teria passado.
10751085
if (turnoVaiFalarComOLead(job)) {
10761086
const { knobs } = await loadChannelKnobs(pool, tenantId, input.channelSessionId, runLog);
1077-
const agora = new Date();
1087+
const agora = clock();
10781088
if (!janelaDeEnvioAberta(agora, knobs)) {
10791089
const abertura = proximaAberturaDaJanela(agora, knobs);
10801090
await rescheduleJob(pool, job.id, ctx.workerId, {
@@ -1166,7 +1176,7 @@ async function executarTurnoDoAgente(
11661176
// attempts (`rescheduleJob`) — quem escreveu 22h é atendido às 8h. O throw é
11671177
// o contrato de `JobSettledError`: o run já dispôs do job, main.ts no-opa.
11681178
if (job.kind === 'inbound_turn' && agentConfig?.janelaDeAtendimento != null) {
1169-
const esperaMs = msAteAJanelaAbrir(agentConfig.janelaDeAtendimento, new Date());
1179+
const esperaMs = msAteAJanelaAbrir(agentConfig.janelaDeAtendimento, clock());
11701180
if (esperaMs !== null) {
11711181
await rescheduleJob(pool, job.id, ctx.workerId, {
11721182
delayMs: esperaMs,
@@ -1408,7 +1418,6 @@ async function executarTurnoDoAgente(
14081418
const turnCrmCfg =
14091419
agentConfig !== null ? { ...deps.crmCfg, agentActorId: agentConfig.agentId } : deps.crmCfg;
14101420
const channel = (deps.channel ?? ((p: pg.Pool) => new WahaChannelAdapter(p, turnCrmCfg)))(pool);
1411-
const clock = deps.clock ?? ((): Date => new Date());
14121421
// STOP lido no turno (fonte: CRM via get_lead_context) — combinado com o cache
14131422
// durável leads.is_opted_out no gate 1 da cadeia (F2-13).
14141423
const optedOutThisTurn = openingContext.context.contact.is_blocked;

tests/invariants/agent-no-credential.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ describe("4B — turno sem credencial NENHUMA (nem env, nem BYOK)", () => {
122122
noProgressBlock: 5,
123123
},
124124
},
125+
// Terça, 15h BRT: dentro da janela anti-ban (7h-22h). Sem fixar o
126+
// relógio, este teste mede o motivo ERRADO quando a suíte roda fora da
127+
// janela — a guarda de horário vem ANTES da checagem de credencial no
128+
// fluxo, então o turno é adiado e nunca chega ao `credencial LLM` que a
129+
// asserção procura. Medido: rodando às 22:20 BRT, a mensagem vinha
130+
// `fora da janela anti-ban — job reagendado`. Os arquivos irmãos
131+
// (`limite-de-envios-por-turno`, `agent-send-template-turn`) já faziam
132+
// isso; este ficou de fora, e a suíte de invariantes reprovava das 22h
133+
// às 7h por causa do relógio de parede.
134+
clock: () => new Date("2026-07-28T18:00:00Z"),
125135
log,
126136
});
127137

0 commit comments

Comments
 (0)