From 769b4047fa2af89c699dbab621b5112e36444c8d Mon Sep 17 00:00:00 2001 From: LuccaXmacna Date: Thu, 29 Oct 2026 23:15:46 +0100 Subject: [PATCH 1/5] fix(baileys): persist late TC token responses in fresh transactions --- .github/workflows/check_code_quality.yml | 10 + .gitignore | 1 + Docker/scripts/patch-baileys-tctoken.cjs | 98 ++++++++++ Dockerfile | 2 + PLANO.md | 23 +++ docs/HANDOFF-TCTOKEN-1026.md | 13 ++ docs/tctoken-persistence-1026.md | 48 +++++ package.json | 3 + test/xmacna-baileys-compat.test.cjs | 4 +- test/xmacna-tctoken-patch.test.cjs | 37 ++++ test/xmacna-tctoken-persistence.test.mjs | 238 +++++++++++++++++++++++ 11 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 Docker/scripts/patch-baileys-tctoken.cjs create mode 100644 PLANO.md create mode 100644 docs/HANDOFF-TCTOKEN-1026.md create mode 100644 docs/tctoken-persistence-1026.md create mode 100644 test/xmacna-tctoken-patch.test.cjs create mode 100644 test/xmacna-tctoken-persistence.test.mjs diff --git a/.github/workflows/check_code_quality.yml b/.github/workflows/check_code_quality.yml index 7dce705b98..7840629a80 100644 --- a/.github/workflows/check_code_quality.yml +++ b/.github/workflows/check_code_quality.yml @@ -14,6 +14,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 services: + redis-tctoken: + image: redis:7.4-alpine + ports: ['16782:6379'] + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s --health-timeout 5s --health-retries 10 postgres: image: postgres:16-alpine env: @@ -28,6 +34,7 @@ jobs: DATABASE_PROVIDER: postgresql DATABASE_CONNECTION_URI: postgresql://evolution_test:local_ci_only@127.0.0.1:5432/evolution_test?connection_limit=64 XMACNA_INBOX_INTEGRATION: '1' + XMACNA_TCTOKEN_REDIS_URL: redis://127.0.0.1:16782 steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 @@ -35,9 +42,12 @@ jobs: node-version: '24' cache: npm - run: npm ci + - run: npm run patch:xmacna-tctoken + - run: npm run verify:xmacna-tctoken - run: npm run db:generate - run: npm run lint:check - run: npm run test:xmacna-compat + - run: npm run test:xmacna-tctoken - run: npm run test:xmacna-inbox - name: Prepare isolated PostgreSQL schema run: npx prisma db push --schema prisma/postgresql-schema.prisma --skip-generate diff --git a/.gitignore b/.gitignore index 768d8afa41..9d215e0794 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,6 @@ lerna-debug.log* .DS_Store *.DS_Store .tool-versions +.local/ /prisma/migrations/* diff --git a/Docker/scripts/patch-baileys-tctoken.cjs b/Docker/scripts/patch-baileys-tctoken.cjs new file mode 100644 index 0000000000..686e60a92f --- /dev/null +++ b/Docker/scripts/patch-baileys-tctoken.cjs @@ -0,0 +1,98 @@ +// Apply only to the audited rc14 bytes. No network, process startup or auth-state access. +const fs = require('node:fs'); +const path = require('node:path'); +const crypto = require('node:crypto'); +const { spawnSync } = require('node:child_process'); + +const MARKER = 'XMACNA_TCTOKEN_ISOLATED_TRANSACTION'; +const sha = (text) => crypto.createHash('sha256').update(text).digest('hex'); +const EXPECTED = { + 'lib/Utils/auth-utils.js': 'd85b07863507235407a3a1430fbb1cfae108c4023f746c50393d9933578caf15', + 'lib/Socket/messages-send.js': '81387f00b457ea28b481b1320bfdbf5ada766305f6f2bc7033931f49811519dc', + 'lib/Types/Auth.d.ts': '509e25a092ba7b4cd9b7be1f8a1730014ea3745fd02cc14e12d9009e4554958e', +}; +function once(text, needle, replacement) { + if (text.split(needle).length !== 2) throw new Error('rc14 anchor missing or ambiguous'); + return text.replace(needle, replacement); +} +function transform(file, text) { + if (file.endsWith('auth-utils.js')) { + const start = text.indexOf('export const addTransactionCapability ='); + const prefix = text.slice(0, start); + let block = text.slice(start); + block = once(block, ' return {\n get: async (type, ids) => {', + ' const transactionalStore = {\n get: async (type, ids) => {'); + block = once(block, ' isInTransaction,', + ` // ${MARKER}: response persistence must not reuse a finished send context.\n` + + ' transactionIsolated: (work, key) => txStorage.exit(() => transactionalStore.transaction(work, key)),\n' + + ' isInTransaction,'); + block = once(block, ' };\n};\n/**\n * Returns the authenticated user', + ' };\n return transactionalStore;\n};\n/**\n * Returns the authenticated user'); + return prefix + block; + } + if (file.endsWith('messages-send.js')) { + text = once(text, + ' .then(async (result) => {\n await storeTcTokensFromIqResult({', + ` // ${MARKER}: IQ stays detached; only its response takes a new transaction.\n` + + ' .then((result) => authState.keys.transactionIsolated(async () => {\n' + + ' await storeTcTokensFromIqResult({'); + return once(text, + ' ...indexWrite\n }\n });\n })\n .catch(err => {', + " ...indexWrite\n }\n });\n }, meId))\n .catch(err => {"); + } + return once(text, + ' transaction(exec: () => Promise, key: string): Promise;', + ' transaction(exec: () => Promise, key: string): Promise;\n' + + ` /** ${MARKER}: starts a fresh transaction outside this store's inherited context. */\n` + + ' transactionIsolated(exec: () => Promise, key: string): Promise;'); +} + +function apply(root = path.resolve(__dirname, '../../node_modules/baileys'), { check = false } = {}) { + const version = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version; + if (version !== '7.0.0-rc14') throw new Error(`Unsupported Baileys ${version}; review the patch before upgrading`); + const edits = []; + for (const [file, digest] of Object.entries(EXPECTED)) { + const target = path.join(root, file); + const current = fs.readFileSync(target, 'utf8'); + let baseline = current; + if (current.includes(MARKER)) { + // Reconstruct original bytes and compare the full patched file; a marker alone is never enough. + if (file.endsWith('auth-utils.js')) { + baseline = once(baseline, ' const transactionalStore = {\n get: async (type, ids) => {', ' return {\n get: async (type, ids) => {'); + baseline = once(baseline, + ` // ${MARKER}: response persistence must not reuse a finished send context.\n` + + ' transactionIsolated: (work, key) => txStorage.exit(() => transactionalStore.transaction(work, key)),\n', ''); + baseline = once(baseline, ' };\n return transactionalStore;\n};\n/**\n * Returns the authenticated user', ' };\n};\n/**\n * Returns the authenticated user'); + } else if (file.endsWith('messages-send.js')) { + baseline = once(baseline, + ` // ${MARKER}: IQ stays detached; only its response takes a new transaction.\n` + + ' .then((result) => authState.keys.transactionIsolated(async () => {\n', + ' .then(async (result) => {\n'); + baseline = once(baseline, " }, meId))\n .catch(err => {", ' })\n .catch(err => {'); + } else { + baseline = once(baseline, + `\n /** ${MARKER}: starts a fresh transaction outside this store's inherited context. */\n` + + ' transactionIsolated(exec: () => Promise, key: string): Promise;', ''); + } + } + if (sha(baseline) !== digest) throw new Error(`Unexpected bytes in ${file}; refusing a partial or stale patch`); + const patched = transform(file, baseline); + if (current !== baseline && current !== patched) throw new Error(`Invalid patched content in ${file}`); + if (file.endsWith('.js')) { + const parsed = spawnSync(process.execPath, ['--input-type=module', '--check'], { + input: patched, encoding: 'utf8', windowsHide: true, + }); + if (parsed.status !== 0) throw new Error(`Patched syntax failed in ${file}`); + } + if (check && current !== patched) throw new Error(`Patch not installed in ${file}`); + edits.push({ target, patched, changed: current !== patched }); + } + // Validate every file before writing any: build failure cannot silently retain only one half. + if (!check) for (const edit of edits) if (edit.changed) fs.writeFileSync(edit.target, edit.patched); + return { files: edits.length, changed: check ? 0 : edits.filter((edit) => edit.changed).length }; +} +if (require.main === module) { + try { console.log(JSON.stringify(apply(undefined, { check: process.argv.includes('--check') }))); } + catch (error) { console.error(error.message); process.exitCode = 1; } +} +module.exports = { apply, MARKER }; diff --git a/Dockerfile b/Dockerfile index 24c4e3bc7f..e70c85ae5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,10 @@ WORKDIR /evolution COPY ./package*.json ./ COPY ./tsconfig.json ./ COPY ./tsup.config.ts ./ +COPY ./Docker/scripts/patch-baileys-tctoken.cjs ./Docker/scripts/patch-baileys-tctoken.cjs RUN npm ci --silent +RUN node ./Docker/scripts/patch-baileys-tctoken.cjs COPY ./src ./src COPY ./public ./public diff --git a/PLANO.md b/PLANO.md new file mode 100644 index 0000000000..a56828f96e --- /dev/null +++ b/PLANO.md @@ -0,0 +1,23 @@ +# TC token e logouts — issue elysium 1026 + +Dono: wk-evolution-versao-sol. Responsável: Vesper/Argos. +Posse: branch fix/tctoken-persistence-1026-20261029 neste checkout isolado; evidências locais em C:/Users/lucca/argos/.local/evolution-versao-20261029/seguimento-1026. +Origem: xmacna/evolution-api, main de 23/10; não é afirmação de que main inteiro esteja implantado. + +Ordem de Argos, 29/10 às 21:58Z: “(1) siga com a atribuição dos logouts de 25/09 16:32 e 20/10 14:12, só leitura, pelos logs de acesso e auditoria retidos: quem ou qual rotina chamou /instance/logout; (2) siga com o teste de regressão offline e a correção focal da persistência (store e cache reais, token frio, resposta tardia, recriar o store), sem deploy e sem tocar no serviço. Resultados comentados na #1026 e ENTREGA no general2. O canário com número só vem depois, com a minha decisão.” + +- [x] Investigar atribuição dos logouts e registrar limite preciso: autor/rotina e chamada HTTP não comprovados. +- [x] Reproduzir falha com módulos rc14 reais, cache real, armazenamento persistente e store recriado. +- [x] Implementar correção focal sem aguardar IQ no mutex de envio; testar concorrência, isolamento e falhas. +- [x] Documentar instalação/rollback do patch sem deploy; publicação da branch na entrega. +- [ ] Comentar resultado sem dados de cliente na issue1026 e registrar ENTREGA no general2. + +Critério de pronto: regressão falha no rc14 original e passa com correção; token e índice sobrevivem ao cache/store recriados; callback usa transação nova; erros de IQ liberam dedup; código revisto e checks proporcionais. Nenhum deploy, serviço alterado ou canário. + +Verificação de logs: o mesmo texto de logout existe em quatro famílias de caminhos; não atribuir a chamada HTTP por esse texto. AWS em operações de leitura; sem send-command. Logs remotos recentes e metadados de retenção já coletados são reaproveitados. + +AGUARDA: Argos decidir canário somente depois da entrega; não executar nesta missão. + +Prova: rc14 original falha em quatro dos sete casos; candidato passa dez testes específicos, sem skips, mais 14 de compatibilidade e tsc. Redis 7.4.12 local encerrado após o ensaio. As quatro implementações de provider/cache testadas são iguais às fontes da imagem principal implantada. Evidências, relatório e limites na pasta local citada acima; documentação do patch em docs/tctoken-persistence-1026.md. + +AGUARDA: Argos/Rafael indicar eventual access/audit log histórico dos segundos solicitados, se existir fora das fontes examinadas. Não houve fonte suficiente para identificar o chamador; quatro famílias de código podem emitir a mesma mensagem de logout. diff --git a/docs/HANDOFF-TCTOKEN-1026.md b/docs/HANDOFF-TCTOKEN-1026.md new file mode 100644 index 0000000000..2716f0fda2 --- /dev/null +++ b/docs/HANDOFF-TCTOKEN-1026.md @@ -0,0 +1,13 @@ +# Handoff — correção focal de TC token + +Dono: wk-evolution-versao-sol; responsável Vesper/Argos. Sessão Codex real: 01a18995-3615-7b53-a519-7dbb059f940e. Retomar com `codex resume 01a18995-3615-7b53-a519-7dbb059f940e` neste checkout. + +Checkout: C:/Users/lucca/evolution-tctoken-1026. Repo: xmacna/evolution-api. Branch: fix/tctoken-persistence-1026-20261029. Mandato e cursor: PLANO.md. Código, instalação, testes e limites: docs/tctoken-persistence-1026.md. + +Provas privadas: C:/Users/lucca/argos/.local/evolution-versao-20261029/seguimento-1026/RESULTADO.md, ATRIBUICAO-LOGOUTS.md, REGRESSAO-RESULTADO.json, logs RED/GREEN e PROVEDORES-COMPARACAO.json. O patch só foi aplicado ao node_modules deste checkout isolado; nenhum serviço, conta WhatsApp ou ambiente de produção foi tocado. Redis local de teste encerrado após a validação; não há job ou watcher em execução. + +Resultado: rc14 original com quatro falhas de persistência; candidato com dez testes específicos, 14 de compatibilidade e TypeScript aprovados. IQ continua desacoplado; resposta grava em transação isolada, com o mutex do mesmo proprietário. Tipo/schema de registro preservados; patch recusa versão/bytes inesperados. Não foi construída imagem ARM64 ou validado banimento/entrega WhatsApp. + +Logouts: autor/rotina e chamada à rota HTTP permanecem não comprovados. Os logs retidos examinados não cobrem os segundos pedidos, e quatro caminhos compartilham o texto. Há busca complementar local com cobertura e ausência de recibo explicitadas; não ampliar isso para todas as máquinas/painéis. + +AGUARDA: revisão do candidato; fonte histórica adicional, se houver; decisão expressa de Argos para canário com número. PR e recibos de comentário/ENTREGA constam do PLANO e da pasta de evidências após a publicação. Não encerrar o próprio pane; responsável fecha pelo rito. diff --git a/docs/tctoken-persistence-1026.md b/docs/tctoken-persistence-1026.md new file mode 100644 index 0000000000..74fef27d30 --- /dev/null +++ b/docs/tctoken-persistence-1026.md @@ -0,0 +1,48 @@ +# Persistência do TC token após o commit do envio + +Desdobramento de [xmacna/elysium#1026](https://github.com/xmacna/elysium/issues/1026). Candidato local de correção, sem deploy, imagem publicada ou canário com conta WhatsApp. + +Quando a emissão assíncrona de TC token recebe a resposta depois do commit do envio, a promise ainda pode carregar o AsyncLocalStorage daquela transação. O `keys.set` original altera o buffer encerrado e não chega ao armazenamento/cache subjacentes. O erro foi reproduzido com o rc14 original usando módulos reais, cache real e armazenamento persistente. + +## Correção + +`Docker/scripts/patch-baileys-tctoken.cjs` modifica apenas três arquivos do `baileys@7.0.0-rc14`: função de transação, callback de resposta da emissão e declaração de tipos. Ele verifica integralmente os bytes auditados antes de modificar qualquer arquivo; recusa outra versão, drift e marker falso. Reaplicar é idempotente. Não acessa auth-state, segredos, serviço ou rede. + +A função adicional `transactionIsolated` sai do contexto herdado com `txStorage.exit` e abre uma transação nova. Somente o callback de persistência do IQ usa essa função. A operação de rede continua desacoplada, depois do envio, e fora do mutex de envio; não foi introduzida espera de cinco segundos ou busca de token antes da mensagem. + +A transação da resposta usa **o mesmo proprietário `meId` que a transação do envio**. Dessa forma, uma resposta rápida espera o commit anterior e lê estado fresco. Um mutex de nome diferente permitia que a limpeza de token vencido do envio sobrescrevesse um token recém-gravado; a regressão inclui esse caso. As chamadas simultâneas da mesma sessão também preservam o índice compartilhado de tokens. Transações normais e aninhadas mantêm o comportamento anterior. + +O controle em voo permanece ativo até o commit da resposta terminar, e o catch/finally originais continuam liberando-o em caso de falha. Não se alteraram seleção de destinatário, política de emissão, stanza, receipt, guard de grupo/protocol/peer, retry, timeout de rede ou comportamento de reconexão. + +## Instalação em checkout de desenvolvimento + +Usar Node 24 e o lockfile existente: + +```sh +npm ci +npm run patch:xmacna-tctoken +npm run verify:xmacna-tctoken +npm run test:xmacna-tctoken +``` + +O teste Redis é ativado por `XMACNA_TCTOKEN_REDIS_URL=redis://127.0.0.1:16782`. Usar somente um Redis local de teste. A suíte exige loopback e porta dedicada, usa chave sintética própria e a remove ao final; não faz FLUSHDB. Sem a variável, somente o caso Redis é pulado, explicitamente. A validação desta entrega executou **todos os dez testes, sem skips**. O workflow de qualidade passa a fornecer o Redis isolado e executar instalação, verificação e regressões. + +O Dockerfile canônico aplica o patch no builder, depois de npm ci. Uma reinstalação de dependências fora desse Dockerfile exige reaplicá-lo. Não se adicionou postinstall nem modificação automática do serviço vivo. Outras receitas de imagem precisam incorporar a mesma etapa explicitamente; este candidato não substitui o inventário dos demais patches da release implantada. + +## Testes e limites + +- Rc14 original: sete testes comportamentais executados, quatro falhas que demonstram o defeito, nenhum skip. +- Candidato: sete testes comportamentais e três de instalação/verificação, todos aprovados, nenhum skip. +- Casos: token frio, resposta após commit, recriação de provider/cache, 16 respostas concorrentes e índice completo, token mais novo preservado, erro de IQ e nova tentativa, resposta rápida contra limpeza antiga, rollback aninhado e Redis real. +- Armazenamento: provider de arquivos do Baileys; `useMultiFileAuthStatePrisma` da Evolution no caminho de chaves Redis, com CacheService e RedisCache reais. As quatro implementações de provider/cache comparadas são iguais às fontes extraídas da imagem principal implantada. +- Somente o transporte IQ e o repositório inicial de credenciais são fixtures. A importação de server.module é substituída no teste para não iniciar servidor nem carregar conexões do aplicativo. As chaves TC percorrem os serializers, cache e Redis reais; não há banco de credenciais real no ensaio. +- Node v24.22.2, Redis 7.4.12 local, Windows/WSL. A produção principal observada usa Node v24.19.0 em ARM64; não foi construído/testado um contêiner ARM64 nesta missão. +- Compatibilidade existente: 14 testes aprovados; tsc --noEmit aprovado. Um import de teste existente foi tornado portátil com pathToFileURL, após falhar pelo caminho Windows. + +As provas locais RED/GREEN e a auditoria de logouts estão no diretório interno `argos/.local/evolution-versao-20261029/seguimento-1026/`. Não foram incluídos dados de cliente ou registros de produção neste repositório. + +Esse resultado demonstra persistência no ensaio, não entrega real de WhatsApp nem causa ou solução de banimento. Canário com número, validação ARM64, comparação dos demais patches e decisão de release continuam com Argos/Lucca. + +## Retorno + +O patch não muda schema nem formato dos registros. No desenvolvimento, uma reinstalação limpa pelo lockfile restaura os arquivos originais; no futuro rollout autorizado, o retorno é à imagem/bundle anterior fixado, preservando os dados criados depois da virada. Não apagar auth-state nem restaurar chaves Signal antigas para desfazer somente código. diff --git a/package.json b/package.json index e1b836ee5b..03dc3e2448 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "dev:server": "tsx watch ./src/main.ts", "test": "tsx watch ./test/all.test.ts", "test:xmacna-compat": "node --test test/xmacna-baileys-compat.test.cjs", + "patch:xmacna-tctoken": "node Docker/scripts/patch-baileys-tctoken.cjs", + "verify:xmacna-tctoken": "node Docker/scripts/patch-baileys-tctoken.cjs --check", + "test:xmacna-tctoken": "node --test test/xmacna-tctoken-persistence.test.mjs test/xmacna-tctoken-patch.test.cjs", "test:xmacna-inbox": "tsx --test test/xmacna-s3-storage.test.ts test/xmacna-inbound-inbox.test.ts test/xmacna-inbound-sink-dispatch.test.ts test/xmacna-inbound-service-contracts.test.ts test/xmacna-baileys-message-processor.test.ts test/xmacna-chatbot-debounce.test.ts test/xmacna-outgoing-lid.test.ts test/xmacna-inbound-identity.test.ts test/xmacna-inbound-media-upload.test.ts test/xmacna-inbound-media-guard.test.ts test/xmacna-get-conversation-message.test.ts test/xmacna-chatwoot-group-participant.test.ts test/xmacna-n8n-individual-messages.test.ts test/xmacna-n8n-individual-service.test.ts", "test:xmacna-inbox:postgres": "tsx --test --test-concurrency=1 test/xmacna-inbound-inbox.postgres.test.ts test/xmacna-inbound-identity.postgres.test.ts", "inbound-inbox:maintenance": "tsx src/cli/inboundInboxMaintenance.ts", diff --git a/test/xmacna-baileys-compat.test.cjs b/test/xmacna-baileys-compat.test.cjs index beee5b91bc..bf9ff1e5ef 100644 --- a/test/xmacna-baileys-compat.test.cjs +++ b/test/xmacna-baileys-compat.test.cjs @@ -237,7 +237,9 @@ test('S3 wrapper presigns through @aws-sdk/client-s3 (minio and its stream-json test('Prisma config deepmerge keeps plain-object merge semantics on deepmerge-ts 8', async () => { const prismaConfigRoot = require.resolve('@prisma/config', { paths: [require.resolve('prisma/package.json')] }); - const { deepmerge } = await import(require.resolve('deepmerge-ts', { paths: [prismaConfigRoot] })); + const { deepmerge } = await import( + require('node:url').pathToFileURL(require.resolve('deepmerge-ts', { paths: [prismaConfigRoot] })).href + ); assert.deepEqual(deepmerge({ migrations: { path: 'a' }, earlyAccess: false }, { migrations: { seed: 'b' } }), { migrations: { path: 'a', seed: 'b' }, earlyAccess: false, diff --git a/test/xmacna-tctoken-patch.test.cjs b/test/xmacna-tctoken-patch.test.cjs new file mode 100644 index 0000000000..41defe9c0e --- /dev/null +++ b/test/xmacna-tctoken-patch.test.cjs @@ -0,0 +1,37 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const test = require('node:test'); +const { apply } = require('../Docker/scripts/patch-baileys-tctoken.cjs'); +const files = ['lib/Utils/auth-utils.js', 'lib/Socket/messages-send.js', 'lib/Types/Auth.d.ts']; +function fixture(t) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'xmacna-rc14-patch-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version: '7.0.0-rc14' })); + for (const name of files) { + const target = path.join(root, name); + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.copyFileSync(path.join(__dirname, '../node_modules/baileys', name), target); + } + return root; +} +test('a fully installed patch is verified and applying it again writes nothing', (t) => { + const root = fixture(t); + assert.deepEqual(apply(root, { check: true }), { files: 3, changed: 0 }); + assert.deepEqual(apply(root), { files: 3, changed: 0 }); +}); +test('an unexpected byte or fake marker aborts before any file is written', (t) => { + const root = fixture(t); + const target = path.join(root, files[1]); + fs.appendFileSync(target, '\n// unexpected drift\n'); + const before = files.map((file) => fs.readFileSync(path.join(root, file))); + assert.throws(() => apply(root), /Unexpected bytes|anchor missing/); + assert.throws(() => apply(root, { check: true }), /Unexpected bytes|anchor missing/); + files.forEach((file, i) => assert.deepEqual(fs.readFileSync(path.join(root, file)), before[i])); +}); +test('a different dependency version is rejected before touching the package', (t) => { + const root = fixture(t); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version: '7.0.0-next' })); + assert.throws(() => apply(root), /Unsupported Baileys/); +}); diff --git a/test/xmacna-tctoken-persistence.test.mjs b/test/xmacna-tctoken-persistence.test.mjs new file mode 100644 index 0000000000..4dabf46993 --- /dev/null +++ b/test/xmacna-tctoken-persistence.test.mjs @@ -0,0 +1,238 @@ +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { mkdtemp, readFile, rm, writeFile, mkdir } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import test from 'node:test'; +import { build } from 'esbuild'; +import { createClient } from 'redis'; + +const require = createRequire(import.meta.url); +const baileysLib = dirname(require.resolve('baileys')); +const { addTransactionCapability, makeCacheableSignalKeyStore } = await import( + pathToFileURL(join(baileysLib, 'Utils/auth-utils.js')) +); +const { useMultiFileAuthState } = await import(pathToFileURL(join(baileysLib, 'Utils/use-multi-file-auth-state.js'))); +const { resolveIssuanceJid, storeTcTokensFromIqResult, buildMergedTcTokenIndexWrite, readTcTokenIndex } = await import( + pathToFileURL(join(baileysLib, 'Utils/tc-token-utils.js')) +); +const logger = Object.fromEntries(['trace', 'debug', 'info', 'warn', 'error'].map((key) => [key, () => {}])); +const source = await readFile(join(baileysLib, 'Socket/messages-send.js'), 'utf8'); +const start = source.indexOf('resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid'); +const end = source.indexOf('\n }\n // Add message to retry cache', start); +assert(start >= 0 && end > start, 'rc14 issuance chain must be found, never silently skipped'); +// Execute the deployed issuance chain without constructing a socket. Only the IQ transport is replaced. +const issue = new Function( + 'authState', 'issuePrivacyTokens', 'resolveIssuanceJid', 'storeTcTokensFromIqResult', + 'buildMergedTcTokenIndexWrite', 'logger', 'tcTokenJid', 'issueTimestamp', 'inFlightTcTokenIssuance', + `const destinationJid=tcTokenJid,meId='synthetic-owner',getLIDForPN=async jid=>jid,getPNForLID=async jid=>jid; + const sock={serverProps:{lidTrustedTokenIssueToLid:true}};return ${source.slice(start, end).trim()}`, +); + +function deferred() { + let resolve, reject; + const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); + return { promise, resolve, reject }; +} +function layers(store) { + return addTransactionCapability(makeCacheableSignalKeyStore(store, logger), logger, { + maxCommitRetries: 1, delayBetweenTriesMs: 1, + }); +} +function response(token, timestamp) { + return { tag: 'iq', attrs: {}, content: [{ tag: 'tokens', attrs: {}, content: [ + { tag: 'token', attrs: { type: 'trusted_contact', t: String(timestamp) }, content: token }, + ] }] }; +} +function schedule(keys, iq, jid, timestamp, inFlight = new Set([jid])) { + return issue({ keys }, () => iq.promise, resolveIssuanceJid, storeTcTokensFromIqResult, + buildMergedTcTokenIndexWrite, logger, jid, timestamp, inFlight); +} + +async function fileKeys(t) { + const folder = await mkdtemp(join(tmpdir(), 'xmacna-tctoken-')); + t.after(() => rm(folder, { recursive: true, force: true })); + const open = async () => layers((await useMultiFileAuthState(folder)).state.keys); + return { keys: await open(), open }; +} + +test('cold token issued after send commit survives real cache and file-store recreation', { timeout: 5000 }, async (t) => { + const folder = await mkdtemp(join(tmpdir(), 'xmacna-tctoken-')); + t.after(() => rm(folder, { recursive: true, force: true })); + const initial = await useMultiFileAuthState(folder); + const keys = layers(initial.state.keys); + const jid = '991234@lid'; + const timestamp = Math.floor(Date.now() / 1000); + const token = Buffer.from('synthetic-trusted-contact-token'); + assert.equal((await keys.get('tctoken', [jid]))[jid], undefined); + const iq = deferred(); + let pending; + await keys.transaction(async () => { + pending = schedule(keys, iq, jid, timestamp); + await keys.set({ tctoken: { 'unrelated@lid': { token: Buffer.from('keep'), timestamp: String(timestamp) } } }); + }, 'synthetic-owner'); + // The send transaction is already complete, even though the IQ has no response yet. + iq.resolve(response(token, timestamp)); + await pending; + const live = (await keys.get('tctoken', [jid]))[jid]; + assert.ok(live, 'late token must reach the live store, not only a retired transaction buffer'); + assert.deepEqual(live.token, token); + const reopened = await useMultiFileAuthState(folder); + const cold = layers(reopened.state.keys); // New cache, new provider, same durable backing files. + const persisted = (await cold.get('tctoken', [jid]))[jid]; + assert.deepEqual(persisted?.token, token, 'token must survive provider and cache recreation'); + assert.equal(persisted.timestamp, String(timestamp)); + assert.equal(persisted.senderTimestamp, timestamp); + assert.ok((await readTcTokenIndex(cold)).includes(jid), 'token index must also persist'); + assert.equal((await cold.get('tctoken', ['unrelated@lid']))['unrelated@lid'].token.toString(), 'keep'); +}); + +test('concurrent late issuances preserve all tokens and the shared index after recreation', { timeout: 5000 }, async (t) => { + const { keys, open } = await fileKeys(t); + const timestamp = Math.floor(Date.now() / 1000); + const jobs = Array.from({ length: 16 }, (_, i) => ({ jid: `99${i + 100}@lid`, iq: deferred() })); + const pending = []; + await keys.transaction(async () => { + for (const job of jobs) pending.push(schedule(keys, job.iq, job.jid, timestamp)); + }, 'synthetic-owner'); + for (const job of jobs) job.iq.resolve(response(Buffer.from(job.jid), timestamp)); + await Promise.all(pending); + const cold = await open(); + const index = await readTcTokenIndex(cold); + assert.deepEqual(index.sort(), jobs.map(({ jid }) => jid).sort()); + for (const { jid } of jobs) assert.equal((await cold.get('tctoken', [jid]))[jid].token.toString(), jid); +}); + +test('a stale issuance response preserves a newer token already in the durable store', { timeout: 5000 }, async (t) => { + const { keys, open } = await fileKeys(t); + const jid = '991235@lid', timestamp = Math.floor(Date.now() / 1000), iq = deferred(); + let pending; + await keys.transaction(async () => { pending = schedule(keys, iq, jid, timestamp); }, 'synthetic-owner'); + await keys.set({ tctoken: { [jid]: { token: Buffer.from('newer'), timestamp: String(timestamp + 1) } } }); + iq.resolve(response(Buffer.from('older'), timestamp)); + await pending; + const cold = await open(); + assert.equal((await cold.get('tctoken', [jid]))[jid].token.toString(), 'newer'); +}); + +test('IQ failure releases the in-flight guard and a subsequent response can persist', { timeout: 5000 }, async (t) => { + const { keys, open } = await fileKeys(t); + const jid = '991236@lid', timestamp = Math.floor(Date.now() / 1000), failed = deferred(); + const inFlight = new Set([jid]); + let pending; + await keys.transaction(async () => { pending = schedule(keys, failed, jid, timestamp, inFlight); }, 'synthetic-owner'); + failed.reject(new Error('synthetic IQ timeout')); + await pending; + assert.equal(inFlight.size, 0); + assert.equal((await (await open()).get('tctoken', [jid]))[jid], undefined); + const retry = deferred(); + inFlight.add(jid); + await keys.transaction(async () => { pending = schedule(keys, retry, jid, timestamp, inFlight); }, 'synthetic-owner'); + retry.resolve(response(Buffer.from('retry'), timestamp)); + await pending; + assert.equal(inFlight.size, 0); + assert.equal((await (await open()).get('tctoken', [jid]))[jid].token.toString(), 'retry'); +}); + +test('a prompt response cannot be overwritten by the sending transaction expiry cleanup', { timeout: 5000 }, async (t) => { + const { keys, open } = await fileKeys(t); + const jid = '991237@lid', timestamp = Math.floor(Date.now() / 1000), iq = deferred(); + let pending; + await keys.transaction(async () => { + await keys.set({ tctoken: { [jid]: { token: Buffer.alloc(0), senderTimestamp: timestamp - 1 } } }); + pending = schedule(keys, iq, jid, timestamp); + iq.resolve(response(Buffer.from('prompt'), timestamp)); + // A slow outer commit must not wipe a response that finished earlier. + await new Promise((resolve) => setTimeout(resolve, 30)); + }, 'synthetic-owner'); + await pending; + assert.equal((await (await open()).get('tctoken', [jid]))[jid].token.toString(), 'prompt'); +}); + +test('regular nested transactions keep their original atomic behavior', { timeout: 5000 }, async (t) => { + const { keys, open } = await fileKeys(t); + await assert.rejects(keys.transaction(async () => { + await keys.transaction(async () => { + await keys.set({ tctoken: { '991238@lid': { token: Buffer.from('must-rollback'), timestamp: '1' } } }); + }, 'nested-owner'); + throw new Error('synthetic abort'); + }, 'outer-owner'), /synthetic abort/); + assert.equal((await (await open()).get('tctoken', ['991238@lid']))['991238@lid'], undefined); +}); + +test('late issuance persists through the actual Evolution Redis provider and cache classes', { + skip: !process.env.XMACNA_TCTOKEN_REDIS_URL, timeout: 15000, +}, async (t) => { + const url = new URL(process.env.XMACNA_TCTOKEN_REDIS_URL); + assert.ok(['127.0.0.1', 'localhost'].includes(url.hostname), 'integration requires an isolated local Redis'); + const admin = createClient({ url: url.href }); + admin.on('error', () => {}); + await admin.connect(); + let redis, fixtureKey, session; + t.after(async () => { + if (fixtureKey) await admin.del(fixtureKey); + if (redis?.isOpen) await redis.quit(); + await admin.quit(); + if (session) await rm(join(process.cwd(), 'instances', session), { recursive: true, force: true }); + }); + assert.ok(Number(url.port) >= 1024 && url.port !== '6379', 'use the dedicated test Redis port'); + process.env.CACHE_REDIS_ENABLED = 'true'; + process.env.CACHE_REDIS_URI = url.href; + process.env.CACHE_REDIS_PREFIX_KEY = 'xmacna-tctoken-test'; + process.env.CACHE_REDIS_TTL = '600'; + process.env.LOG_LEVEL = 'ERROR'; + const folder = join(process.cwd(), '.local', 'tctoken-provider-test'); + await mkdir(folder, { recursive: true }); + const output = join(folder, 'provider.cjs'); + const compiled = await build({ + stdin: { + contents: `export { default as open } from './src/utils/use-multi-file-auth-state-prisma'; + export { CacheService } from './src/api/services/cache.service'; + export { RedisCache } from './src/cache/rediscache'; + export { configService } from './src/config/env.config'; + export { redisClient } from './src/cache/rediscache.client';`, + resolveDir: process.cwd(), loader: 'ts', + }, + bundle: true, platform: 'node', format: 'cjs', packages: 'external', write: false, + plugins: [{ + name: 'isolate-credential-repository-only', setup(build) { + build.onResolve({ filter: /^@api\/server\.module$/ }, () => ({ path: 'credential-fixture', namespace: 'fixture' })); + build.onLoad({ filter: /.*/, namespace: 'fixture' }, () => ({ + contents: `export const prismaRepository={session:{findUnique:async()=>({creds:JSON.stringify('{}')})}};`, loader: 'js', + })); + }, + }], + }); + await writeFile(output, compiled.outputFiles[0].contents); + const provider = require(output); + redis = provider.redisClient.getConnection(); + session = `synthetic-${process.pid}`; + const cache = () => new provider.CacheService(new provider.RedisCache(provider.configService, 'tctoken-fixture')); + const open = async () => layers((await provider.open(session, cache())).state.keys); + const keys = await open(), iq = deferred(), jid = '991239@lid', timestamp = Math.floor(Date.now() / 1000); + let pending; + assert.equal((await keys.get('tctoken', [jid]))[jid], undefined); + await keys.transaction(async () => { pending = schedule(keys, iq, jid, timestamp); }, 'synthetic-owner'); + iq.resolve(response(Buffer.from('redis-token'), timestamp)); + await pending; + const cold = await open(); + assert.equal((await cold.get('tctoken', [jid]))[jid].token.toString(), 'redis-token'); + assert.ok((await readTcTokenIndex(cold)).includes(jid)); + const jobs = Array.from({ length: 16 }, (_, i) => ({ jid: `98${i + 100}@lid`, iq: deferred() })); + const pendingBatch = []; + await cold.transaction(async () => { + for (const job of jobs) pendingBatch.push(schedule(cold, job.iq, job.jid, timestamp)); + }, 'synthetic-owner'); + for (const job of jobs) job.iq.resolve(response(Buffer.from(job.jid), timestamp)); + await Promise.all(pendingBatch); + const recreated = await open(); + assert.deepEqual((await readTcTokenIndex(recreated)).sort(), [jid, ...jobs.map((job) => job.jid)].sort()); + for (const job of jobs) { + assert.equal((await recreated.get('tctoken', [job.jid]))[job.jid].token.toString(), job.jid); + } + // Inspect only this fixture's backing hash, never flush a database or touch another namespace. + const engine = new provider.RedisCache(provider.configService, 'tctoken-fixture'); + fixtureKey = engine.buildKey(session); + assert.equal(await admin.hLen(engine.buildKey(session)), 18); +}); From 4bd176421fbb63b33f9a9781098a74edf38890ba Mon Sep 17 00:00:00 2001 From: LuccaXmacna Date: Thu, 29 Oct 2026 23:18:27 +0100 Subject: [PATCH 2/5] docs: registrar entrega e retomada da correcao TC token --- PLANO.md | 4 +++- docs/HANDOFF-TCTOKEN-1026.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/PLANO.md b/PLANO.md index a56828f96e..c396a34335 100644 --- a/PLANO.md +++ b/PLANO.md @@ -10,7 +10,7 @@ Ordem de Argos, 29/10 às 21:58Z: “(1) siga com a atribuição dos logouts de - [x] Reproduzir falha com módulos rc14 reais, cache real, armazenamento persistente e store recriado. - [x] Implementar correção focal sem aguardar IQ no mutex de envio; testar concorrência, isolamento e falhas. - [x] Documentar instalação/rollback do patch sem deploy; publicação da branch na entrega. -- [ ] Comentar resultado sem dados de cliente na issue1026 e registrar ENTREGA no general2. +- [x] Comentar resultado sem dados de cliente na issue1026 e registrar ENTREGA no general2. Critério de pronto: regressão falha no rc14 original e passa com correção; token e índice sobrevivem ao cache/store recriados; callback usa transação nova; erros de IQ liberam dedup; código revisto e checks proporcionais. Nenhum deploy, serviço alterado ou canário. @@ -21,3 +21,5 @@ AGUARDA: Argos decidir canário somente depois da entrega; não executar nesta m Prova: rc14 original falha em quatro dos sete casos; candidato passa dez testes específicos, sem skips, mais 14 de compatibilidade e tsc. Redis 7.4.12 local encerrado após o ensaio. As quatro implementações de provider/cache testadas são iguais às fontes da imagem principal implantada. Evidências, relatório e limites na pasta local citada acima; documentação do patch em docs/tctoken-persistence-1026.md. AGUARDA: Argos/Rafael indicar eventual access/audit log histórico dos segundos solicitados, se existir fora das fontes examinadas. Não houve fonte suficiente para identificar o chamador; quatro famílias de código podem emitir a mesma mensagem de logout. + +Candidato em draft: https://github.com/xmacna/evolution-api/pull/7 . Resultado publicado e readback conferido: https://github.com/xmacna/elysium/issues/1026#issuecomment-6509599968 . ENTREGA registrada no general2, com recibos ISSUE-READBACK.json e ENTREGA-GENERAL2.json na pasta local da evidência. Checks locais aprovados; CI do PR permanece com o revisor até conclusão automática no GitHub. diff --git a/docs/HANDOFF-TCTOKEN-1026.md b/docs/HANDOFF-TCTOKEN-1026.md index 2716f0fda2..5081b9802a 100644 --- a/docs/HANDOFF-TCTOKEN-1026.md +++ b/docs/HANDOFF-TCTOKEN-1026.md @@ -10,4 +10,4 @@ Resultado: rc14 original com quatro falhas de persistência; candidato com dez t Logouts: autor/rotina e chamada à rota HTTP permanecem não comprovados. Os logs retidos examinados não cobrem os segundos pedidos, e quatro caminhos compartilham o texto. Há busca complementar local com cobertura e ausência de recibo explicitadas; não ampliar isso para todas as máquinas/painéis. -AGUARDA: revisão do candidato; fonte histórica adicional, se houver; decisão expressa de Argos para canário com número. PR e recibos de comentário/ENTREGA constam do PLANO e da pasta de evidências após a publicação. Não encerrar o próprio pane; responsável fecha pelo rito. +AGUARDA: revisão do candidato; fonte histórica adicional, se houver; decisão expressa de Argos para canário com número. PR em draft: https://github.com/xmacna/evolution-api/pull/7 . Comentário publicado e conferido na issue1026 e ENTREGA no general2; links e recibos no PLANO e na pasta de evidências. CI termina por evento no GitHub, sob acompanhamento do revisor; não há polling deste worker. Não encerrar o próprio pane; responsável fecha pelo rito. From 998a75dd3b9c8db05bbcf19d65680f51f54d1889 Mon Sep 17 00:00:00 2001 From: LuccaXmacna Date: Thu, 29 Oct 2026 23:37:46 +0100 Subject: [PATCH 3/5] docs: restringe vinculo e canario a operacao humana --- PLANO.md | 4 +++- docs/HANDOFF-TCTOKEN-1026.md | 2 ++ docs/tctoken-persistence-1026.md | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/PLANO.md b/PLANO.md index c396a34335..048b8c8256 100644 --- a/PLANO.md +++ b/PLANO.md @@ -1,5 +1,7 @@ # TC token e logouts — issue elysium 1026 +Regra vigente de Argos, 29/10 às 22:41Z, até segunda ordem: nenhum agente pareia, desconecta, faz logout ou lê QR de instância por script ou pelo Manager. Pareamento somente por uma pessoa, exclusivamente pelo Partenon, com Vesper avisado antes. Qualquer canário futuro também deve obedecer; os ensaios desta frente são offline, sem conta WhatsApp. Pausa de campanhas em lista é responsabilidade do ger-disparo-ativo-sol; não operar campanhas aqui. + Dono: wk-evolution-versao-sol. Responsável: Vesper/Argos. Posse: branch fix/tctoken-persistence-1026-20261029 neste checkout isolado; evidências locais em C:/Users/lucca/argos/.local/evolution-versao-20261029/seguimento-1026. Origem: xmacna/evolution-api, main de 23/10; não é afirmação de que main inteiro esteja implantado. @@ -16,7 +18,7 @@ Critério de pronto: regressão falha no rc14 original e passa com correção; t Verificação de logs: o mesmo texto de logout existe em quatro famílias de caminhos; não atribuir a chamada HTTP por esse texto. AWS em operações de leitura; sem send-command. Logs remotos recentes e metadados de retenção já coletados são reaproveitados. -AGUARDA: Argos decidir canário somente depois da entrega; não executar nesta missão. +AGUARDA: Argos decidir canário somente depois da entrega; não executar nesta missão. Eventual pareamento cabe exclusivamente a uma pessoa pelo Partenon, com Vesper avisado antes; nenhuma operação de vínculo por agente. Prova: rc14 original falha em quatro dos sete casos; candidato passa dez testes específicos, sem skips, mais 14 de compatibilidade e tsc. Redis 7.4.12 local encerrado após o ensaio. As quatro implementações de provider/cache testadas são iguais às fontes da imagem principal implantada. Evidências, relatório e limites na pasta local citada acima; documentação do patch em docs/tctoken-persistence-1026.md. diff --git a/docs/HANDOFF-TCTOKEN-1026.md b/docs/HANDOFF-TCTOKEN-1026.md index 5081b9802a..4ee8cc873d 100644 --- a/docs/HANDOFF-TCTOKEN-1026.md +++ b/docs/HANDOFF-TCTOKEN-1026.md @@ -1,5 +1,7 @@ # Handoff — correção focal de TC token +Regra vigente de Argos, 29/10 22:41Z, até segunda ordem: agentes não pareiam, desconectam, fazem logout ou leem QR de instância por script ou pelo Manager. Pareamento somente por uma pessoa pelo Partenon, com Vesper avisado antes. Vale também para eventual canário aprovado; esta missão permanece offline e sem deploy. Campanhas em lista são tratadas pelo ger-disparo-ativo-sol. + Dono: wk-evolution-versao-sol; responsável Vesper/Argos. Sessão Codex real: 01a18995-3615-7b53-a519-7dbb059f940e. Retomar com `codex resume 01a18995-3615-7b53-a519-7dbb059f940e` neste checkout. Checkout: C:/Users/lucca/evolution-tctoken-1026. Repo: xmacna/evolution-api. Branch: fix/tctoken-persistence-1026-20261029. Mandato e cursor: PLANO.md. Código, instalação, testes e limites: docs/tctoken-persistence-1026.md. diff --git a/docs/tctoken-persistence-1026.md b/docs/tctoken-persistence-1026.md index 74fef27d30..0a6afbbc20 100644 --- a/docs/tctoken-persistence-1026.md +++ b/docs/tctoken-persistence-1026.md @@ -43,6 +43,8 @@ As provas locais RED/GREEN e a auditoria de logouts estão no diretório interno Esse resultado demonstra persistência no ensaio, não entrega real de WhatsApp nem causa ou solução de banimento. Canário com número, validação ARM64, comparação dos demais patches e decisão de release continuam com Argos/Lucca. +Regra de Argos de 29/10/2026 22:41 UTC, até segunda ordem: agentes não pareiam, desconectam, fazem logout ou leem QR de instância por script ou Manager. Pareamento somente por uma pessoa pelo Partenon, com Vesper avisado antes. Uma futura aprovação de canário não dispensa essa regra. Os testes descritos aqui usam dados sintéticos e permanecem offline. + ## Retorno O patch não muda schema nem formato dos registros. No desenvolvimento, uma reinstalação limpa pelo lockfile restaura os arquivos originais; no futuro rollout autorizado, o retorno é à imagem/bundle anterior fixado, preservando os dados criados depois da virada. Não apagar auth-state nem restaurar chaves Signal antigas para desfazer somente código. From 6770e3bff0278a0f3edafeee1baabcaabb5a3e69 Mon Sep 17 00:00:00 2001 From: LuccaXmacna Date: Sun, 1 Nov 2026 14:55:38 +0100 Subject: [PATCH 4/5] docs: handoff de fechamento da frente Evolution --- HANDOFF.md | 44 ++++++++++++++++++++++++++++++++++++ PLANO.md | 4 +++- docs/HANDOFF-TCTOKEN-1026.md | 2 ++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 HANDOFF.md diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000000..025b21e876 --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,44 @@ +# Handoff — Evolution / TC token + +Fechamento solicitado por Vesper em 01/11/2026 14:00 UTC para liberar memória. Dono: wk-evolution-versao-sol; responsável: Vesper/Argos. O responsável fecha o pane depois de ler este arquivo. + +## Checkout e retomada + +- cwd: `C:/Users/lucca/evolution-tctoken-1026`. +- Repositório: `xmacna/evolution-api`. +- Branch: `fix/tctoken-persistence-1026-20261029`, upstream `origin/fix/tctoken-persistence-1026-20261029`. +- HEAD do candidato conferido antes do commit documental de fechamento: `998a75dd3b9c8db05bbcf19d65680f51f54d1889`. +- HEAD de fechamento: consultar `git rev-parse HEAD`; o valor exato depois do commit e push está na ENTREGA em `C:/Users/lucca/argos/inbox/general2.ndjson`. O hash do próprio commit não pode ser embutido nele. +- ID real da sessão Codex, conferido em CODEX_SESSION_ID e CODEX_THREAD_ID: `01a18995-3615-7b53-a519-7dbb059f940e`. + +```powershell +Set-Location C:/Users/lucca/evolution-tctoken-1026 +codex resume 01a18995-3615-7b53-a519-7dbb059f940e +``` + +## Pronto e publicado + +Correção focal de persistência TC token publicada na branch e no [PR 7, aberto em draft](https://github.com/xmacna/evolution-api/pull/7). O IQ continua desacoplado; a resposta tardia grava em transação nova, fora do contexto encerrado, com o mutex do mesmo proprietário. Instalador recusa versão ou bytes inesperados; não altera schema. Código e retorno documentados em `docs/tctoken-persistence-1026.md`. + +Validação retida: rc14 original falha em quatro dos sete casos; candidato passa dez testes específicos, 14 de compatibilidade e TypeScript. Cache/providers reais e Redis local foram exercitados; fixtures limitadas a IQ e repositório inicial de credenciais. Os quatro checks GitHub do HEAD acima estão concluídos com sucesso: Check Code Quality, CodeQL Analysis, Dependency Review e CodeQL. Não repetir testes somente para o fechamento documental. + +Investigação e comentários das issues 1025/1026 entregues. Atribuição histórica de logouts continua sem prova de autor/rotina ou chamada HTTP; a mensagem de aplicação aparece em quatro caminhos e a retenção não cobre os segundos solicitados. Provas privadas em `C:/Users/lucca/argos/.local/evolution-versao-20261029/seguimento-1026/`; mapa e diagnóstico em sua pasta pai. Nenhum deploy ou canário foi executado por este worker. Redis temporário foi encerrado; não há monitor ou job ativo mantido por esta frente. + +## Imagem pronta e suspensa — frente relacionada + +O registro da imagem ARM64 pertence ao ger-disparo-ativo-sol, em `C:/Users/lucca/elysium-worktrees/disparo-ativo-20261029/.local/disparo-ativo/EVOLUTION-PRONTO-30.md`, atualizado em 30/10 09:52 UTC. Fonte: [PR Meta 8](https://github.com/xmacna/evolution-api/pull/8). O registro declara build e smoke prontos, publicação suspensa, nenhum push da imagem ou update Evolution e nenhum canário enviado. Portanto, o código está publicado em branches/PRs; a publicação da imagem não está comprovada por esse registro. + +Referência da imagem local: `evolution-meta-receipts-1030-31716cf`, Linux/ARM64, identidade `sha256:acc9a3f0cb717f0411f9199ab845a969109aefa868e0c5944a63e43045371c6f`. Não presumir que o candidato de TC token do PR 7 esteja incluído na imagem do PR 8: conferir a composição no responsável antes de qualquer release dessa correção. + +Rollback dessa frente relacionado à baseline `evolution-api-candidate@sha256:4aaa5d80bfb46b66e786fe1691d1766878e02fb1739a8164fe83c31eb4f2b541`: ensaio offline de preparo, aplicação e retorno registrado em `rollback-exercise-30.log`, na mesma pasta. Não foi rollback real. O estado de cutover real ainda depende de autorização e preparo; usar o plano canônico do responsável, sem executar comandos live deste handoff. + +## O que falta e condição de retomada + +- **AGUARDA Lucca:** OK escrito para publicar/deployar. Vesper confirma o aviso prévio ao Rafael e a janela antes de qualquer mudança. O pedido de fechar esta sessão não autoriza deploy. +- **AGUARDA revisão:** avaliar PR 7 e sua inclusão no candidato correto; preservar patches e requisitos de ARM64, segurança e retorno. Imagem Meta e correção TC token são entregas distintas. +- **Canário pendente:** somente após autorização específica e sob o plano do responsável. Na frente Meta há previsão de um único envio interno com prova DELIVERED/READ; nenhum canário foi feito. Persistência offline não prova entrega nem causa/solução de banimento. +- **Rollback ensaiado offline:** evidência existe na frente da imagem. Antes do cutover autorizado, atualizar snapshot, confirmar digest anterior e preparar estado de avanço/retorno. Não declarar ensaio em produção ou retorno pronto no runtime sem essas etapas. +- **Vínculos, até segunda ordem:** nenhum agente pareia, desconecta, faz logout ou lê QR por script/Manager. Pareamento apenas por uma pessoa pelo Partenon, com Vesper avisado antes. A regra também vale para o canário. Campanhas em lista ficam com ger-disparo-ativo-sol. +- **Histórico administrativo:** Argos/Rafael podem indicar fonte adicional dos segundos de 25/09 16:32 e 20/10 14:12. Retomar por evidência nova, sem polling. + +Este worker para no checkpoint documental. Vesper lê a ENTREGA e fecha o pane pelo rito; nenhum fechamento próprio será executado. diff --git a/PLANO.md b/PLANO.md index 048b8c8256..3e1d1032e1 100644 --- a/PLANO.md +++ b/PLANO.md @@ -1,5 +1,7 @@ # TC token e logouts — issue elysium 1026 +Fechamento solicitado por Vesper em 01/11 14:00Z para liberar memória: preservar git limpo/push, HANDOFF.md com cwd/branch/HEAD/sessão/retomada e pendências, e uma ENTREGA no general2 com caminho e HEAD. Não fechar o próprio pane. Handoff canônico de saída: `HANDOFF.md` na raiz deste checkout; imagem relacionada permanece sob ger-disparo-ativo-sol, sem presumir inclusão deste PR nela. Deploy aguarda OK escrito do Lucca e aviso ao Rafael; canário pendente, rollback da imagem ensaiado apenas offline. + Regra vigente de Argos, 29/10 às 22:41Z, até segunda ordem: nenhum agente pareia, desconecta, faz logout ou lê QR de instância por script ou pelo Manager. Pareamento somente por uma pessoa, exclusivamente pelo Partenon, com Vesper avisado antes. Qualquer canário futuro também deve obedecer; os ensaios desta frente são offline, sem conta WhatsApp. Pausa de campanhas em lista é responsabilidade do ger-disparo-ativo-sol; não operar campanhas aqui. Dono: wk-evolution-versao-sol. Responsável: Vesper/Argos. @@ -24,4 +26,4 @@ Prova: rc14 original falha em quatro dos sete casos; candidato passa dez testes AGUARDA: Argos/Rafael indicar eventual access/audit log histórico dos segundos solicitados, se existir fora das fontes examinadas. Não houve fonte suficiente para identificar o chamador; quatro famílias de código podem emitir a mesma mensagem de logout. -Candidato em draft: https://github.com/xmacna/evolution-api/pull/7 . Resultado publicado e readback conferido: https://github.com/xmacna/elysium/issues/1026#issuecomment-6509599968 . ENTREGA registrada no general2, com recibos ISSUE-READBACK.json e ENTREGA-GENERAL2.json na pasta local da evidência. Checks locais aprovados; CI do PR permanece com o revisor até conclusão automática no GitHub. +Candidato em draft: https://github.com/xmacna/evolution-api/pull/7 . Resultado publicado e readback conferido: https://github.com/xmacna/elysium/issues/1026#issuecomment-6509599968 . ENTREGA registrada no general2, com recibos ISSUE-READBACK.json e ENTREGA-GENERAL2.json na pasta local da evidência. Checks locais aprovados; os quatro checks GitHub do candidato também estão concluídos com sucesso, conferidos no fechamento de 01/11. Atualização documental de fechamento será enviada na mesma branch; nenhum build/teste adicional solicitado. diff --git a/docs/HANDOFF-TCTOKEN-1026.md b/docs/HANDOFF-TCTOKEN-1026.md index 4ee8cc873d..25ad49dad2 100644 --- a/docs/HANDOFF-TCTOKEN-1026.md +++ b/docs/HANDOFF-TCTOKEN-1026.md @@ -1,5 +1,7 @@ # Handoff — correção focal de TC token +Fechamento por Vesper em 01/11 14:00Z: consultar o `HANDOFF.md` na raiz deste checkout, que consolida cwd, branch/HEAD, sessão real, imagem da frente relacionada, pendências e condições de retomada. Os quatro checks GitHub do candidato foram conferidos com sucesso; este arquivo preserva o checkpoint técnico anterior. + Regra vigente de Argos, 29/10 22:41Z, até segunda ordem: agentes não pareiam, desconectam, fazem logout ou leem QR de instância por script ou pelo Manager. Pareamento somente por uma pessoa pelo Partenon, com Vesper avisado antes. Vale também para eventual canário aprovado; esta missão permanece offline e sem deploy. Campanhas em lista são tratadas pelo ger-disparo-ativo-sol. Dono: wk-evolution-versao-sol; responsável Vesper/Argos. Sessão Codex real: 01a18995-3615-7b53-a519-7dbb059f940e. Retomar com `codex resume 01a18995-3615-7b53-a519-7dbb059f940e` neste checkout. From 130782cff8090d93812d0ed07eb8f9069ca64ccc Mon Sep 17 00:00:00 2001 From: LuccaXmacna Date: Sun, 1 Nov 2026 15:02:57 +0100 Subject: [PATCH 5/5] docs: checkpoint pre-reboot da frente TC token --- HANDOFF.md | 10 ++++++---- PLANO.md | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 025b21e876..bdca8c224d 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,14 +1,16 @@ # Handoff — Evolution / TC token -Fechamento solicitado por Vesper em 01/11/2026 14:00 UTC para liberar memória. Dono: wk-evolution-versao-sol; responsável: Vesper/Argos. O responsável fecha o pane depois de ler este arquivo. +CHECKPOINT_REBOOT — ordem do Lucca retransmitida por Vesper em 01/11/2026, prompt 13:58Z. Dono: wk-evolution-versao-sol; responsável: Vesper/Argos. Não fechar o próprio pane; o reboot encerra a sessão. Sem mensagem externa. + +Trabalho a retomar depois do reboot: **SIM, condicionado a revisão/novo evento e às autorizações abaixo**. Não há execução local pela metade nem tarefa para reiniciar automaticamente. Consulta de processos Windows desta frente não encontrou render, servidor ou navegador de teste pelos caminhos do checkout, testes TC token ou porta 16782. Redis temporário já foi encerrado no ensaio anterior. Nenhum processo de outra frente foi parado. ## Checkout e retomada - cwd: `C:/Users/lucca/evolution-tctoken-1026`. - Repositório: `xmacna/evolution-api`. - Branch: `fix/tctoken-persistence-1026-20261029`, upstream `origin/fix/tctoken-persistence-1026-20261029`. -- HEAD do candidato conferido antes do commit documental de fechamento: `998a75dd3b9c8db05bbcf19d65680f51f54d1889`. -- HEAD de fechamento: consultar `git rev-parse HEAD`; o valor exato depois do commit e push está na ENTREGA em `C:/Users/lucca/argos/inbox/general2.ndjson`. O hash do próprio commit não pode ser embutido nele. +- HEAD conferido antes deste checkpoint documental: `6770e3bff0278a0f3edafeee1baabcaabb5a3e69`. O candidato testado permanece o checkpoint `998a75dd3b9c8db05bbcf19d65680f51f54d1889`; os commits posteriores são documentação. +- HEAD final pré-reboot: consultar `git rev-parse HEAD`; o valor exato depois do commit e push está na ENTREGA cujo texto começa com CHECKPOINT_REBOOT em `C:/Users/lucca/argos/inbox/general2.ndjson`. O hash do próprio commit não pode ser embutido nele. - ID real da sessão Codex, conferido em CODEX_SESSION_ID e CODEX_THREAD_ID: `01a18995-3615-7b53-a519-7dbb059f940e`. ```powershell @@ -41,4 +43,4 @@ Rollback dessa frente relacionado à baseline `evolution-api-candidate@sha256:4a - **Vínculos, até segunda ordem:** nenhum agente pareia, desconecta, faz logout ou lê QR por script/Manager. Pareamento apenas por uma pessoa pelo Partenon, com Vesper avisado antes. A regra também vale para o canário. Campanhas em lista ficam com ger-disparo-ativo-sol. - **Histórico administrativo:** Argos/Rafael podem indicar fonte adicional dos segundos de 25/09 16:32 e 20/10 14:12. Retomar por evidência nova, sem polling. -Este worker para no checkpoint documental. Vesper lê a ENTREGA e fecha o pane pelo rito; nenhum fechamento próprio será executado. +Este worker para no checkpoint documental. Não há ação de produção autorizada na retomada automática. Vesper lê a ENTREGA; o reboot fecha o pane, sem fechamento próprio. diff --git a/PLANO.md b/PLANO.md index 3e1d1032e1..1a529c1180 100644 --- a/PLANO.md +++ b/PLANO.md @@ -1,5 +1,7 @@ # TC token e logouts — issue elysium 1026 +CHECKPOINT_REBOOT, ordem do Lucca retransmitida por Vesper em 01/11, prompt 13:58Z: parar com segurança o que for próprio, commit por caminho/push na branch, atualizar HANDOFF.md e registrar ENTREGA CHECKPOINT_REBOOT com caminho e retomada SIM/NAO. Nenhum processo próprio em execução foi encontrado na consulta Windows; nada ficou no meio. Retomada SIM, condicionada às pendências/autorizações do HANDOFF.md, sem reinício automático de tarefas. O reboot fecha o pane; não fechar nem enviar mensagem externa. + Fechamento solicitado por Vesper em 01/11 14:00Z para liberar memória: preservar git limpo/push, HANDOFF.md com cwd/branch/HEAD/sessão/retomada e pendências, e uma ENTREGA no general2 com caminho e HEAD. Não fechar o próprio pane. Handoff canônico de saída: `HANDOFF.md` na raiz deste checkout; imagem relacionada permanece sob ger-disparo-ativo-sol, sem presumir inclusão deste PR nela. Deploy aguarda OK escrito do Lucca e aviso ao Rafael; canário pendente, rollback da imagem ensaiado apenas offline. Regra vigente de Argos, 29/10 às 22:41Z, até segunda ordem: nenhum agente pareia, desconecta, faz logout ou lê QR de instância por script ou pelo Manager. Pareamento somente por uma pessoa, exclusivamente pelo Partenon, com Vesper avisado antes. Qualquer canário futuro também deve obedecer; os ensaios desta frente são offline, sem conta WhatsApp. Pausa de campanhas em lista é responsabilidade do ger-disparo-ativo-sol; não operar campanhas aqui.