You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
O caso CA-I2 do worker de outbox falha de forma intermitente porque exige pendência ZERO para um único consumidor num cenário com dois consumidores — asserção que deixou de fazer sentido quando a pendência virou por consumidor (#800/#824, ADR-0064).
📍 Localização
Arquivo(s):tests/modules/contracts/worker/outbox-worker.integration.test.ts:184-186 (a asserção), :138-190 (o caso), :130-134 (o caso irmão CA-I1, que foi corrigido)
Módulo · camada:contracts · worker (teste de integração)
Regra/princípio violado:ADR-0064 — "A pendência de um evento no outbox é POR CONSUMIDOR, nunca da linha". O comentário na linha 183 cita#800/#824, e o caso irmão CA-I1 (linha 130-134) foi corretamente ajustado, porque lá há um consumidor só (consumer-i1). O CA-I2 ficou para trás.
Impacto: teste intermitente no gate. Não é defeito de produção — o SKIP LOCKED está fazendo exatamente o que deve. O custo é de confiança: um vermelho conhecido aparece em PR que não tem relação com contracts, e quem o vê precisa reinvestigar do zero para descobrir que não é regressão sua.
🔁 Reprodução / evidência
Medido em 06/09/2026, MySQL 8.4 via runner de integração (-p core-api-test, MYSQL_PORT=3307):
pnpm run test:integration # suíte contracts, 102 casos
topo da pilha do PR #983: FALHA · FALHA · passa
origin/dev (baseline): passa · FALHA · passa
Mensagem da falha:
✖ CA-I2: 2 workers paralelos não duplicam delivery (FOR UPDATE SKIP LOCKED)
AssertionError: não deve restar eventos pendentes
6 !== 0 <- o worker-2 levou os 6; para o worker-1 os 6 seguem pendentes, corretamente
⚠️Não é regressão do PR #983 — o diff daquele PR está confinado a src/modules/financial/** e não toca contracts. A falha reproduz na dev limpa.
✅ Critérios de aceite (testáveis)
CA1 — Dado o cenário do CA-I2 (6 eventos, dois workers com consumer ids distintos), Quando a suíte rodar 10 vezes seguidas, Então passa nas 10 — sem depender de qual worker venceu a disputa.
CA2 — Dado o mesmo cenário, Quando a asserção final for reescrita, Então ela afirma o invariante real: a UNIÃO dos entregues é 6, sem interseção entre os dois workers (o que as linhas 170-181 já fazem), e a pendência residual é avaliada para os dois consumidores juntos, não para um só.
CA3 — Dado um cenário em que o worker-2 processa TODOS os 6, Quando o caso rodar, Então ele passa — hoje é exatamente esse o caminho que falha.
CA4 — Dado o CA-I1 (um consumidor só), Quando a mudança for aplicada, Então ele continua exigindo pendência zero para consumer-i1 — a semântica por consumidor não pode ser afrouxada onde ela é legítima.
🧪 Definition of Done
Teste(s) cobrindo os CAs, com a repetição do CA1 demonstrada no PR.
Gate verde: pnpm run typecheck + pnpm run format:check + pnpm run lint + pnpm test, mais pnpm run test:integration rodado em repetição.
Sem regressão — contagem de testes ≥ baseline.
Não afrouxar a asserção para "passar": a repartição sem duplicação continua sendo o que o caso existe para provar (ver .claude/rules/testing.md, "Não afrouxe o teste para 'passar'").
🎯 Problema (uma frase, sem ambiguidade)
O caso
CA-I2do worker de outbox falha de forma intermitente porque exige pendência ZERO para um único consumidor num cenário com dois consumidores — asserção que deixou de fazer sentido quando a pendência virou por consumidor (#800/#824, ADR-0064).📍 Localização
tests/modules/contracts/worker/outbox-worker.integration.test.ts:184-186(a asserção),:138-190(o caso),:130-134(o caso irmãoCA-I1, que foi corrigido)contracts·worker(teste de integração)🔍 Atual vs. Esperado
SKIP LOCKEDreparta os 6 eventos: só passa quando oworker-1fica com todos.🧠 Por quê (causa-raiz + impacto)
worker-1eworker-2), depois exigefindPendingForUpdate('worker-1', 20).length === 0. Só que desde a [partners/workers] Dois consumidores disputam o mesmo par_outbox — projeção perde eventos silenciosamente (11 fornecedores, 1 na view) #800/[contracts/workers] contracts-outbox e contract-count-projection disputam o mesmo ctr_outbox — projeção perde eventos silenciosamente #824 a pendência é por consumidor — o claim faz anti-join comeventos_processadosporconsumer_id+event_id. Os eventos que oworker-2processou ficam marcados sob o id dele e, portanto, seguem pendentes para oworker-1, por desenho. A asserção só é verdadeira no caso particular em que oworker-1vence a disputa e leva os 6.#800/#824, e o caso irmãoCA-I1(linha 130-134) foi corretamente ajustado, porque lá há um consumidor só (consumer-i1). OCA-I2ficou para trás.SKIP LOCKEDestá fazendo exatamente o que deve. O custo é de confiança: um vermelho conhecido aparece em PR que não tem relação comcontracts, e quem o vê precisa reinvestigar do zero para descobrir que não é regressão sua.🔁 Reprodução / evidência
Medido em 06/09/2026, MySQL 8.4 via runner de integração (
-p core-api-test,MYSQL_PORT=3307):Mensagem da falha:
src/modules/financial/**e não tocacontracts. A falha reproduz nadevlimpa.✅ Critérios de aceite (testáveis)
CA-I2(6 eventos, dois workers com consumer ids distintos), Quando a suíte rodar 10 vezes seguidas, Então passa nas 10 — sem depender de qual worker venceu a disputa.worker-2processa TODOS os 6, Quando o caso rodar, Então ele passa — hoje é exatamente esse o caminho que falha.CA-I1(um consumidor só), Quando a mudança for aplicada, Então ele continua exigindo pendência zero paraconsumer-i1— a semântica por consumidor não pode ser afrouxada onde ela é legítima.🧪 Definition of Done
pnpm run typecheck+pnpm run format:check+pnpm run lint+pnpm test, maispnpm run test:integrationrodado em repetição..claude/rules/testing.md, "Não afrouxe o teste para 'passar'").🏷️ Classificação
bugmédiaScontracts:worker:outbox-ca-i2-pendencia-por-consumidor