@orkastery/cli 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,7 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.argvDoItem = argvDoItem;
37
+ exports.workersDaLane = workersDaLane;
37
38
  exports.chaveDoPerfilIntegracao = chaveDoPerfilIntegracao;
39
+ exports.picoDaArvoreParaPerfil = picoDaArvoreParaPerfil;
38
40
  exports.picoCpuPorFonte = picoCpuPorFonte;
39
41
  exports.esperaDoGrupo = esperaDoGrupo;
40
42
  exports.rodarEmParalelo = rodarEmParalelo;
@@ -59,7 +61,7 @@ const integracoes_locais_1 = require("./integracoes-locais");
59
61
  const TIMEOUT_BIN = '/usr/bin/timeout';
60
62
  const NICE_BIN = '/usr/bin/nice';
61
63
  const TIME_BIN = '/usr/bin/time';
62
- const CONTRATO_PERFIL_INTEGRACAO = 'ork.check.cpu-integracao/v1';
64
+ const CONTRATO_PERFIL_INTEGRACAO = 'ork.check.cpu-integracao/v2';
63
65
  const INTERVALO_CPU_MS = 100;
64
66
  /** O argv de um item: `nice` (se pedido e existir), `timeout(1)` (se existir) e o `argv` do item ou `bash -c`. */
65
67
  function argvDoItem(item, comGrupo = fs.existsSync(TIMEOUT_BIN), comNice = fs.existsSync(NICE_BIN)) {
@@ -84,6 +86,49 @@ function vagas(max) {
84
86
  livres++; },
85
87
  };
86
88
  }
89
+ /**
90
+ * Recursos exclusivos sem dependencia artificial. Cada pedido adquire o conjunto inteiro ou espera, o que evita
91
+ * deadlock entre listas cruzadas; pedidos disjuntos continuam livres para avancar.
92
+ */
93
+ function recursosExclusivos() {
94
+ const ocupados = new Set();
95
+ const fila = [];
96
+ const despachar = () => {
97
+ for (let i = 0; i < fila.length;) {
98
+ const pedido = fila[i];
99
+ if (pedido.nomes.some((nome) => ocupados.has(nome))) {
100
+ i++;
101
+ continue;
102
+ }
103
+ fila.splice(i, 1);
104
+ pedido.nomes.forEach((nome) => ocupados.add(nome));
105
+ pedido.pronto(() => {
106
+ pedido.nomes.forEach((nome) => ocupados.delete(nome));
107
+ despachar();
108
+ });
109
+ }
110
+ };
111
+ return {
112
+ tomar: (nomes) => new Promise((pronto) => {
113
+ fila.push({ nomes: [...new Set(nomes)], pronto });
114
+ despachar();
115
+ }),
116
+ };
117
+ }
118
+ /** A reserva que vira ORK_WORKERS quando a lane e efetivamente lancada. */
119
+ function workersDaLane(item, plano, cpus, totalDeLanes) {
120
+ if (!item.nome.startsWith('test:'))
121
+ return null;
122
+ const capacidade = (0, agendador_cpu_1.workersDaReserva)(cpus);
123
+ if (!plano)
124
+ return Math.max(1, Math.floor(capacidade / Math.max(1, Math.min(capacidade, totalDeLanes))));
125
+ if (item.papelCpu === 'hermetica')
126
+ return (0, agendador_cpu_1.workersDaReserva)(plano.cpusHermetica);
127
+ if (item.papelCpu === 'integracao')
128
+ return (0, agendador_cpu_1.workersDaReserva)(plano.cpusIntegracao);
129
+ // Outros fecham a cadeia depois que as reservas da varredura e da integracao voltaram.
130
+ return capacidade;
131
+ }
87
132
  function arquivoDoPerfilIntegracao() {
88
133
  const base = process.env.XDG_CACHE_HOME && path.isAbsolute(process.env.XDG_CACHE_HOME)
89
134
  ? process.env.XDG_CACHE_HOME : path.join(os.homedir(), '.cache');
@@ -124,22 +169,27 @@ function lerPerfilIntegracao(chave, cpus) {
124
169
  try {
125
170
  const p = JSON.parse(fs.readFileSync(arquivoDoPerfilIntegracao(), 'utf8'));
126
171
  return p?.contrato === CONTRATO_PERFIL_INTEGRACAO && p.chave === chave && p.cpus === cpus && p.completa
127
- && Number.isFinite(p.picoCpus) && p.picoCpus > 0 && p.picoCpus <= cpus ? p : null;
172
+ && Number.isFinite(p.picoCpusDaArvore) && p.picoCpusDaArvore > 0 && p.picoCpusDaArvore <= cpus ? p : null;
128
173
  }
129
174
  catch {
130
175
  return null;
131
176
  }
132
177
  }
178
+ /** Somente uma arvore completa e fisicamente possivel pode ensinar a reserva; lacunas de conteiner ficam na evidencia. */
179
+ function picoDaArvoreParaPerfil(medicao, cpus) {
180
+ const pico = medicao.picoCpusDaArvore;
181
+ return medicao.arvoreCompleta && !medicao.anomaliaDaArvore && pico !== null && pico > 0 && pico <= cpus ? pico : null;
182
+ }
133
183
  function gravarPerfilIntegracao(chave, cpus, medicao) {
134
- if (!chave || !medicao.completa || medicao.anomalia || medicao.picoCpus === null
135
- || !(medicao.picoCpus > 0) || medicao.picoCpus > cpus)
184
+ const picoCpusDaArvore = picoDaArvoreParaPerfil(medicao, cpus);
185
+ if (!chave || picoCpusDaArvore === null)
136
186
  return;
137
187
  const arquivo = arquivoDoPerfilIntegracao();
138
188
  try {
139
189
  fs.mkdirSync(path.dirname(arquivo), { recursive: true });
140
190
  const temporario = `${arquivo}.${process.pid}.tmp`;
141
191
  const perfil = { contrato: CONTRATO_PERFIL_INTEGRACAO, chave, cpus,
142
- picoCpus: medicao.picoCpus, completa: true, medidoEm: new Date().toISOString() };
192
+ picoCpusDaArvore, completa: true, medidoEm: new Date().toISOString() };
143
193
  fs.writeFileSync(temporario, JSON.stringify(perfil) + '\n', { mode: 0o600 });
144
194
  fs.renameSync(temporario, arquivo);
145
195
  }
@@ -271,16 +321,18 @@ function sondarCpuDaIntegracao(pid, registro, cpus) {
271
321
  const fontes = new Map();
272
322
  const motivos = new Map();
273
323
  const amostras = [];
324
+ const amostrasDaArvore = [];
274
325
  const primeiraCpuDosContainers = new Map();
275
326
  const ultimaCpuDosContainers = new Map();
276
327
  const amostrar = () => {
277
328
  const tabela = processosCpu();
278
- const atuais = {};
329
+ const arvore = {};
279
330
  for (const p of arvoreDeProcessos(pid, tabela)) {
280
331
  const x = tabela.get(p);
281
332
  if (x)
282
- atuais[`processo:${p}:${x.inicio}`] = x.cpuMs;
333
+ arvore[`processo:${p}:${x.inicio}`] = x.cpuMs;
283
334
  }
335
+ const atuais = { ...arvore };
284
336
  for (const nome of nomesDosContainers(registro)) {
285
337
  let fonte = fontes.get(nome);
286
338
  if (!fonte) {
@@ -304,7 +356,9 @@ function sondarCpuDaIntegracao(pid, registro, cpus) {
304
356
  else if (fonte)
305
357
  motivos.set(nome, 'contador de CPU do cgroup ilegivel');
306
358
  }
307
- amostras.push({ em: Date.now(), fontes: atuais });
359
+ const em = Date.now();
360
+ amostrasDaArvore.push({ em, fontes: arvore });
361
+ amostras.push({ em, fontes: atuais });
308
362
  };
309
363
  return {
310
364
  amostrar,
@@ -314,10 +368,14 @@ function sondarCpuDaIntegracao(pid, registro, cpus) {
314
368
  const containersMs = nomes.reduce((total, nome) => total + Math.max(0, (ultimaCpuDosContainers.get(nome) ?? 0) - (primeiraCpuDosContainers.get(nome) ?? 0)), 0);
315
369
  const ausentes = nomes.filter((nome) => !ultimaCpuDosContainers.has(nome));
316
370
  const pico = picoCpuPorFonte(amostras, cpus);
371
+ const picoDaArvore = picoCpuPorFonte(amostrasDaArvore, cpus);
372
+ const arvoreCompleta = arvoreMs > 0 && picoDaArvore.picoCpus !== null && !picoDaArvore.anomalia;
317
373
  return { arvoreMs, containersMs, totalMs: arvoreMs + containersMs, paredeMs,
318
- picoCpus: pico.picoCpus, completa: ausentes.length === 0 && !pico.anomalia,
374
+ picoCpus: pico.picoCpus, picoCpusDaArvore: picoDaArvore.picoCpus,
375
+ completa: arvoreCompleta && ausentes.length === 0 && !pico.anomalia, arvoreCompleta,
319
376
  containers: nomes, ausentes, motivosDosAusentes: ausentes.map((container) => ({ container,
320
- motivo: motivos.get(container) ?? 'conteiner terminou antes de uma amostra completa' })), anomalia: pico.anomalia };
377
+ motivo: motivos.get(container) ?? 'conteiner terminou antes de uma amostra completa' })),
378
+ anomalia: pico.anomalia, anomaliaDaArvore: picoDaArvore.anomalia };
321
379
  },
322
380
  };
323
381
  }
@@ -333,13 +391,16 @@ async function rodarEspecificacao(esp) {
333
391
  const perfil = esp.medicaoCpu?.cpusIntegracao === undefined ? lerPerfilIntegracao(chaveDoPerfil, cpus) : null;
334
392
  const medicao = esp.medicaoCpu ?? await (0, agendador_cpu_1.medirCpuDoGrupo)();
335
393
  if (perfil)
336
- Object.assign(medicao, { cpusIntegracao: perfil.picoCpus, fonteCpusIntegracao: 'medida' });
394
+ Object.assign(medicao, { cpusIntegracao: perfil.picoCpusDaArvore, fonteCpusIntegracao: 'medida' });
337
395
  else if (medicao.cpusIntegracao === undefined)
338
396
  Object.assign(medicao, { cpusIntegracao: null, fonteCpusIntegracao: 'conservadora' });
339
397
  const plano = usaAgendador ? (0, agendador_cpu_1.planejarCpuDoGrupo)(cpus, medicao) : undefined;
340
398
  const sinalDaReserva = plano && !plano.saturado ? path.join(esp.dir, 'reserva-devolvida.json') : undefined;
341
399
  const itens = plano ? aplicarPlano(esp.itens, plano, sinalDaReserva) : esp.itens;
342
- const limite = vagas(esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length);
400
+ const tetoInformado = esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length;
401
+ const limite = vagas(Math.max(1, Math.min(tetoInformado, (0, agendador_cpu_1.workersDaReserva)(cpus))));
402
+ const exclusivos = recursosExclusivos();
403
+ const totalDeLanes = itens.filter((item) => item.nome.startsWith('test:')).length;
343
404
  const porNome = new Map();
344
405
  const indice = new Map(itens.map((item, i) => [item.nome, i]));
345
406
  const vezDoItem = (item, i) => {
@@ -354,12 +415,14 @@ async function rodarEspecificacao(esp) {
354
415
  if (j !== undefined)
355
416
  await vezDoItem(itens[j], j);
356
417
  }
418
+ const soltarRecursos = await exclusivos.tomar(item.recursos ?? []);
357
419
  await limite.tomar();
358
420
  try {
359
421
  return await lancar(item, i);
360
422
  }
361
423
  finally {
362
424
  limite.soltar();
425
+ soltarRecursos();
363
426
  }
364
427
  });
365
428
  porNome.set(item.nome, vez);
@@ -434,15 +497,14 @@ async function rodarEspecificacao(esp) {
434
497
  catch { /* medida incompleta abaixo */ }
435
498
  }
436
499
  const medicaoCpu = medirIntegracao && sonda ? sonda.fechar(arvoreMs, t.duracaoMs) : undefined;
437
- if (medicaoCpu && !(arvoreMs > 0))
438
- medicaoCpu.completa = false;
439
500
  fim = { ...t, ...(medicaoCpu ? { medicaoCpu } : {}) };
440
501
  fs.closeSync(saida);
441
502
  fs.closeSync(erro);
442
503
  resolve(fim);
443
504
  };
444
505
  // Sem o `timeout(1)`, o proprio auxiliar mata pelo prazo, como o `spawnSync` do `exec` faria.
445
- const ambiente = { ...process.env, ...item.ambiente,
506
+ const workers = workersDaLane(item, plano, cpus, totalDeLanes);
507
+ const ambiente = { ...process.env, ...item.ambiente, ...(workers === null ? {} : { [agendador_cpu_1.ENV_WORKERS]: String(workers) }),
446
508
  ...(medirIntegracao ? { [agendador_cpu_1.ENV_CONTAINERS_DA_LANE]: registro } : {}) };
447
509
  const filho = (0, node_child_process_1.spawn)(bin, args, { cwd: item.cwd, env: ambiente, stdio: ['ignore', saida, erro],
448
510
  ...(comGrupo ? {} : { timeout: item.prazoMs, killSignal: 'SIGTERM' }) });
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CONTRATO_DA_SELECAO = void 0;
37
+ exports.comandoDaSelecao = comandoDaSelecao;
38
+ exports.planejarSelecaoNativa = planejarSelecaoNativa;
39
+ /** RM-081 F5: decisão conservadora da seleção nativa por mudanças no Agile. */
40
+ const path = __importStar(require("node:path"));
41
+ const leases_1 = require("./leases");
42
+ const manifest_1 = require("./manifest");
43
+ const util_1 = require("./util");
44
+ exports.CONTRATO_DA_SELECAO = 'ork.check.selecao/v1';
45
+ const LOCKFILES = new Set(['package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'poetry.lock',
46
+ 'Pipfile.lock', 'go.mod', 'go.sum', 'Package.resolved']);
47
+ function objeto(v) {
48
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : null;
49
+ }
50
+ function sha(v) {
51
+ return typeof v === 'string' && /^[0-9a-f]{40,64}$/i.test(v);
52
+ }
53
+ /** Um argumento shell fechado, como o filtro nominal da F3. */
54
+ function argumento(valor) {
55
+ return `'${valor.replace(/'/g, `'"'"'`)}'`;
56
+ }
57
+ function comandoDaSelecao(declaracao, base, arquivos) {
58
+ return declaracao.comando.includes('{base}')
59
+ ? declaracao.comando.replace('{base}', argumento(base))
60
+ : declaracao.comando.replace('{arquivos}', arquivos.map(argumento).join(' '));
61
+ }
62
+ function baseAtual(cwd, manifesto, commit) {
63
+ const r = (0, util_1.exec)('git', ['merge-base', commit, manifesto.worktree.base_branch], cwd);
64
+ const base = r.stdout.trim();
65
+ return r.ok && sha(base) ? base : null;
66
+ }
67
+ function arvoreLimpa(cwd) {
68
+ const r = (0, util_1.exec)('git', ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--', '.', ':(exclude).orkastery'], cwd);
69
+ return r.ok && r.stdout === '';
70
+ }
71
+ function arquivosMudados(cwd, anterior, atual) {
72
+ const r = (0, util_1.exec)('git', ['diff', '--name-only', '--no-renames', '-z', anterior, atual, '--', '.', ':(exclude).orkastery'], cwd);
73
+ if (!r.ok)
74
+ return null;
75
+ return [...new Set(r.stdout.split('\0').filter(Boolean).map((arquivo) => arquivo.split(path.sep).join('/')))].sort();
76
+ }
77
+ function lockfile(arquivo) {
78
+ const nome = path.posix.basename(arquivo);
79
+ return LOCKFILES.has(nome) || /^requirements(?:\.[^.]+)?\.txt$/.test(nome)
80
+ || /(?:^|[.-])lock(?:b|\.(?:json|ya?ml|toml))?$/i.test(nome);
81
+ }
82
+ function inteira(motivo, arquivos) {
83
+ return { modo: 'inteira', motivo, ...(arquivos?.length ? { arquivos } : {}) };
84
+ }
85
+ function todasInteiras(declaracoes, motivo, arquivos) {
86
+ return Object.fromEntries(Object.keys(declaracoes).map((lane) => [lane, inteira(motivo, arquivos)]));
87
+ }
88
+ /**
89
+ * Decide uma vez, antes dos comandos. O último `verify_run` é o cache: qualquer forma desconhecida,
90
+ * veredito vermelho, base diferente ou árvore suja devolve a suíte inteira.
91
+ */
92
+ function planejarSelecaoNativa(cwd, manifesto, eventos, commit, modo) {
93
+ const declaracoes = manifesto.check?.agile?.selecao ?? {};
94
+ if (modo !== 'agile' || Object.keys(declaracoes).length === 0)
95
+ return { lanes: {}, cache: null };
96
+ const base = baseAtual(cwd, manifesto, commit);
97
+ const limpa = arvoreLimpa(cwd);
98
+ const cache = base && limpa ? { contrato: exports.CONTRATO_DA_SELECAO, base, head: commit } : null;
99
+ const ultima = [...eventos].reverse().find((evento) => evento.tipo === 'verify_run');
100
+ if (!ultima)
101
+ return { lanes: todasInteiras(declaracoes, 'rodada-fria'), cache };
102
+ const anterior = objeto(ultima.selecao);
103
+ if (!anterior)
104
+ return { lanes: todasInteiras(declaracoes, 'cache-ausente'), cache };
105
+ if (!base || !limpa || ultima.veredito !== 'verdade sustentada' || anterior.contrato !== exports.CONTRATO_DA_SELECAO
106
+ || !sha(anterior.base) || !sha(anterior.head) || anterior.head !== ultima.commit) {
107
+ return { lanes: todasInteiras(declaracoes, 'cache-invalido'), cache };
108
+ }
109
+ if (anterior.base !== base)
110
+ return { lanes: todasInteiras(declaracoes, 'base-mudou'), cache };
111
+ if (!(0, util_1.exec)('git', ['merge-base', '--is-ancestor', anterior.head, commit], cwd).ok) {
112
+ return { lanes: todasInteiras(declaracoes, 'cache-invalido'), cache };
113
+ }
114
+ const mudados = arquivosMudados(cwd, anterior.head, commit);
115
+ if (!mudados)
116
+ return { lanes: todasInteiras(declaracoes, 'cache-invalido'), cache };
117
+ const manifestos = new Set([manifest_1.NOME_MANIFESTO, manifest_1.NOME_MANIFESTO_LEGADO]);
118
+ const mudancaNoManifesto = mudados.filter((arquivo) => manifestos.has(path.posix.basename(arquivo)));
119
+ if (mudancaNoManifesto.length)
120
+ return { lanes: todasInteiras(declaracoes, 'manifesto-mudou', mudancaNoManifesto), cache };
121
+ const lockfiles = mudados.filter(lockfile);
122
+ if (lockfiles.length)
123
+ return { lanes: todasInteiras(declaracoes, 'lockfile-mudou', lockfiles), cache };
124
+ const lanes = {};
125
+ for (const [lane, declaracao] of Object.entries(declaracoes)) {
126
+ if (!declaracao.mapeia.length) {
127
+ lanes[lane] = inteira('runner-sem-garantia');
128
+ continue;
129
+ }
130
+ const configuracao = mudados.filter((arquivo) => (declaracao.configuracao ?? [])
131
+ .some((glob) => (0, leases_1.caminhoBateComGlob)(arquivo, glob)));
132
+ if (configuracao.length) {
133
+ lanes[lane] = inteira('configuracao-mudou', configuracao);
134
+ continue;
135
+ }
136
+ const fora = mudados.filter((arquivo) => !declaracao.mapeia.some((glob) => (0, leases_1.caminhoBateComGlob)(arquivo, glob)));
137
+ if (fora.length) {
138
+ lanes[lane] = inteira('fora-do-mapeamento', fora);
139
+ continue;
140
+ }
141
+ if (!mudados.length) {
142
+ lanes[lane] = inteira('sem-mudancas');
143
+ continue;
144
+ }
145
+ lanes[lane] = { modo: 'selecionada', motivo: 'selecionada', arquivos: mudados,
146
+ base: anterior.head, comando: comandoDaSelecao(declaracao, anterior.head, mudados) };
147
+ }
148
+ return { lanes, cache };
149
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.taskCompleteFixadoDaFonte = taskCompleteFixadoDaFonte;
37
+ exports.taskCompleteFixadoDaSessao = taskCompleteFixadoDaSessao;
38
+ /** RM-075: prova somente leitura de `task_complete` no rollout já fixado pelo controller. */
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const node_crypto_1 = require("node:crypto");
42
+ const codex_controller_sensor_1 = require("./adapters/codex-controller-sensor");
43
+ const codex_events_1 = require("./adapters/codex-events");
44
+ const thread_1 = require("./thread");
45
+ const BLOCO = 256 * 1024;
46
+ const chaveDaFonte = (sessionId, despachoEm) => (0, node_crypto_1.createHash)('sha256').update(sessionId + '|' + despachoEm).digest('hex');
47
+ /** O parser percorre o descritor autenticado inteiro; linha parcial ou corrupção não prova terminal. */
48
+ function taskCompleteFixadoDaFonte(fonte, agoraMs = Date.now()) {
49
+ return (0, codex_controller_sensor_1.consumirRolloutControllerFixado)(fonte, (fd, tamanho) => {
50
+ const parser = new codex_events_1.ParserCodex();
51
+ let terminal = null;
52
+ for (let offset = 0; offset < tamanho;) {
53
+ const buffer = Buffer.alloc(Math.min(BLOCO, tamanho - offset));
54
+ const lidos = fs.readSync(fd, buffer, 0, buffer.length, offset);
55
+ if (lidos <= 0)
56
+ break;
57
+ for (const atual of parser.push(buffer.subarray(0, lidos), agoraMs))
58
+ terminal = atual;
59
+ offset += lidos;
60
+ }
61
+ if (parser.estado.invalido || parser.bytesPendentes > 0 || parser.estado.turnoAberto ||
62
+ terminal?.fonte !== 'rollout.task_complete' ||
63
+ (terminal.numeroTurno ?? 0) < parser.estado.turnos)
64
+ return null;
65
+ return { terminal, fonte };
66
+ });
67
+ }
68
+ /**
69
+ * Resolve somente a fixação correspondente ao despacho. Sem pin autenticado, controller ou cwd,
70
+ * devolve `null`: nunca descobre outro rollout para fabricar a prova.
71
+ */
72
+ function taskCompleteFixadoDaSessao(raiz, thread, sessao, eventos, agoraMs = Date.now()) {
73
+ if (sessao.runtime !== 'codex')
74
+ return null;
75
+ const registro = eventos.filter(e => e.tipo === 'session_sensor_registered' &&
76
+ e.sessionId === sessao.sessionId && e.despachoEm === sessao.despachadaEm).at(-1);
77
+ if (typeof registro?.controlador !== 'string')
78
+ return null;
79
+ const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessao.sessionId).at(-1);
80
+ if (despacho?.controlador !== undefined && despacho.controlador !== registro.controlador)
81
+ throw new Error('controlador registrado divergente do despacho');
82
+ const cwd = (despacho?.cwd ?? registro.cwd);
83
+ if (!cwd)
84
+ return null;
85
+ const dir = (0, thread_1.dirThread)(raiz, thread.id);
86
+ const chave = chaveDaFonte(sessao.sessionId, sessao.despachadaEm);
87
+ const esperado = {
88
+ dirSessoes: path.join(dir, 'sessoes'), sessionId: sessao.sessionId, cwd,
89
+ despachoEm: sessao.despachadaEm, agoraMs,
90
+ fixacao: path.join(dir, 'sessoes', `watcher-source-${chave}.json`),
91
+ vinculo: { thread: thread.id, fase: sessao.fase, promptSha256: sessao.promptSha256 },
92
+ };
93
+ const fonte = (0, codex_controller_sensor_1.lerFonteControllerFixada)(esperado.fixacao, esperado);
94
+ return fonte ? taskCompleteFixadoDaFonte(fonte, agoraMs) : null;
95
+ }