agentic-workflow-manager 9.12.2 → 9.12.3
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.
- package/dist/src/commands/job/index.js +61 -0
- package/dist/src/commands/watch/apply.js +51 -2
- package/dist/src/commands/watch/generations.js +1 -1
- package/dist/src/commands/watch/index.js +12 -2
- package/dist/src/commands/watch/supervisor.js +106 -23
- package/dist/src/core/journal/requests.js +6 -0
- package/dist/src/core/journal/types.js +40 -3
- package/dist/tests/commands/watch/plan-lifecycle.test.js +47 -1
- package/dist/tests/commands/watch/reopened-196.test.js +749 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +23 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -251,6 +251,67 @@ function registerJobCommand(program) {
|
|
|
251
251
|
assertAuthenticatedCwd(repo, branch);
|
|
252
252
|
process.stdout.write(JSON.stringify((0, query_1.queryPs)(repo, branch), null, 2) + '\n');
|
|
253
253
|
});
|
|
254
|
+
job.command('ack')
|
|
255
|
+
.description('consulta read-only el acuse durable de una request por ID exacto')
|
|
256
|
+
.argument('<requestId>')
|
|
257
|
+
.action((requestId) => {
|
|
258
|
+
if (!/^req-[A-Za-z0-9-]{1,124}$/.test(requestId))
|
|
259
|
+
throw new Error('requestId invalido');
|
|
260
|
+
const repo = process.cwd();
|
|
261
|
+
const branch = branchOf(repo);
|
|
262
|
+
assertAuthenticatedCwd(repo, branch);
|
|
263
|
+
const read = (0, store_1.readJournal)(repo, branch);
|
|
264
|
+
if (read.corrupt || !read.state) {
|
|
265
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', journal: read.corrupt ? 'corrupt' : 'absent' }) + '\n');
|
|
266
|
+
process.exitCode = 1;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
(0, lock_1.verifyBranchInvariant)(repo, read.state.branch);
|
|
270
|
+
const ack = (0, requests_1.ackFor)(read.state, requestId);
|
|
271
|
+
if (ack) {
|
|
272
|
+
process.stdout.write(JSON.stringify({ requestId, state: ack.outcome === 'applied' ? 'applied' : 'rejected',
|
|
273
|
+
outcome: ack.outcome, ...(ack.resultRef ? { resultRef: ack.resultRef } : {}) }) + '\n');
|
|
274
|
+
if (ack.outcome !== 'applied')
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const problem = read.state.requestProblems.find(item => item.requestId === requestId
|
|
279
|
+
|| (item.kind === 'corrupt' && path_1.default.basename(item.file) === `${requestId}.json`));
|
|
280
|
+
if (problem) {
|
|
281
|
+
process.stdout.write(JSON.stringify({ requestId, state: problem.kind === 'corrupt' ? 'unverifiable' : 'rejected', reasonCode: problem.kind }) + '\n');
|
|
282
|
+
process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
let pending;
|
|
286
|
+
try {
|
|
287
|
+
pending = (0, requests_1.listPendingRequests)(repo, branch).find(item => item.requestId === requestId
|
|
288
|
+
|| (item.corrupt && path_1.default.basename(item.file) === `${requestId}.json`));
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', reasonCode: 'request-directory' }) + '\n');
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// The supervisor may have consumed the request between the first
|
|
296
|
+
// journal snapshot and the queue read. Recheck before saying unknown.
|
|
297
|
+
const latest = (0, store_1.readJournal)(repo, branch);
|
|
298
|
+
if (latest.corrupt || !latest.state) {
|
|
299
|
+
process.stdout.write(JSON.stringify({ requestId, state: 'unverifiable', journal: latest.corrupt ? 'corrupt' : 'absent' }) + '\n');
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const latestAck = (0, requests_1.ackFor)(latest.state, requestId);
|
|
304
|
+
if (latestAck) {
|
|
305
|
+
process.stdout.write(JSON.stringify({ requestId, state: latestAck.outcome === 'applied' ? 'applied' : 'rejected',
|
|
306
|
+
outcome: latestAck.outcome, ...(latestAck.resultRef ? { resultRef: latestAck.resultRef } : {}) }) + '\n');
|
|
307
|
+
if (latestAck.outcome !== 'applied')
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
process.stdout.write(JSON.stringify({ requestId, state: pending?.corrupt ? 'unverifiable' : pending ? 'pending' : 'unknown' }) + '\n');
|
|
312
|
+
if (!pending || pending.corrupt)
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
});
|
|
254
315
|
job.command('list').action(() => {
|
|
255
316
|
const repo = process.cwd();
|
|
256
317
|
const branch = branchOf(repo);
|
|
@@ -155,6 +155,14 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
155
155
|
const capabilities = (0, capabilities_1.readCapabilities)((0, capabilities_1.validateRuntimeKey)(envelope.runtime), checkedAt);
|
|
156
156
|
return { checkedAt, policy: policy.state === 'approved' ? policy.policy : undefined, capabilities: capabilities.state === 'current' ? capabilities.receipt : undefined };
|
|
157
157
|
} }, p.at ?? now());
|
|
158
|
+
const routingAttempt = reserved.state.routingAttempts?.find(item => item.id === reserved.attemptId);
|
|
159
|
+
if (!routingAttempt)
|
|
160
|
+
throw new Error('routing reservation produced no attempt');
|
|
161
|
+
const priorReservationAck = routingAttempt.reservationRequestId === undefined ? undefined
|
|
162
|
+
: reserved.state.appliedRequests[routingAttempt.reservationRequestId];
|
|
163
|
+
if (!priorReservationAck || priorReservationAck.outcome !== 'applied' || priorReservationAck.resultRef !== routingAttempt.id) {
|
|
164
|
+
routingAttempt.reservationRequestId = env.requestId;
|
|
165
|
+
}
|
|
158
166
|
Object.assign(s, reserved.state);
|
|
159
167
|
(0, requests_1.applyOutcome)(s, { ...base, outcome: 'applied', resultRef: reserved.attemptId });
|
|
160
168
|
return;
|
|
@@ -166,6 +174,15 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
166
174
|
const attempt = s.routingAttempts?.find(item => item.id === p.attemptId);
|
|
167
175
|
if (!attempt)
|
|
168
176
|
throw new Error('routing-observe attempt is unknown');
|
|
177
|
+
if (s.planBinding?.schema === 'compact-slices/v2' && attempt.envelope.role === 'implementer') {
|
|
178
|
+
if (attempt.nativeEvidenceRequired !== true)
|
|
179
|
+
throw new Error('routing-observe v2 requires a native event receipt');
|
|
180
|
+
const dispatch = s.dispatches.find(item => item.routingAttemptId === attempt.id);
|
|
181
|
+
const ack = dispatch?.dispatchRequestId ? s.appliedRequests[dispatch.dispatchRequestId] : undefined;
|
|
182
|
+
if (!dispatch || ack?.outcome !== 'applied' || ack.requestId !== dispatch.dispatchRequestId
|
|
183
|
+
|| ack.resultRef !== dispatch.id)
|
|
184
|
+
throw new Error('routing-observe requires applied dispatch ack');
|
|
185
|
+
}
|
|
169
186
|
let key = null;
|
|
170
187
|
if (attempt.nativeEvidenceRequired) {
|
|
171
188
|
try {
|
|
@@ -454,8 +471,40 @@ function applyRequestToState(s, env, digest, repoRoot, eventScope) {
|
|
|
454
471
|
const taskId = p.taskId;
|
|
455
472
|
if (!s.tasks.some((task) => task.id === taskId))
|
|
456
473
|
throw new Error('register --entity dispatch: taskId desconocido');
|
|
457
|
-
|
|
458
|
-
|
|
474
|
+
const v2 = s.planBinding?.schema === 'compact-slices/v2';
|
|
475
|
+
let routingAttemptId;
|
|
476
|
+
let reservedForNewDispatch = true;
|
|
477
|
+
if (v2) {
|
|
478
|
+
if (typeof p.routingAttemptId !== 'string' || p.routingAttemptId.length === 0)
|
|
479
|
+
throw new Error('register --entity dispatch v2 requiere routingAttemptId');
|
|
480
|
+
const attempt = s.routingAttempts?.find(candidate => candidate.id === p.routingAttemptId);
|
|
481
|
+
if (!attempt || attempt.envelope.role !== 'implementer'
|
|
482
|
+
|| attempt.envelope.sliceId !== taskId || attempt.envelope.planDigest !== s.planBinding.digest
|
|
483
|
+
|| attempt.envelope.executionDigest !== s.planBinding.executionDigest
|
|
484
|
+
|| !attempt.reservationRequestId
|
|
485
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.outcome !== 'applied'
|
|
486
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.requestId !== attempt.reservationRequestId
|
|
487
|
+
|| s.appliedRequests[attempt.reservationRequestId]?.resultRef !== attempt.id) {
|
|
488
|
+
throw new Error('register --entity dispatch: routing reservation for this slice and plan lacks applied ack');
|
|
489
|
+
}
|
|
490
|
+
routingAttemptId = attempt.id;
|
|
491
|
+
if (attempt.nativeEvidenceRequired !== true)
|
|
492
|
+
throw new Error('register --entity dispatch v2 requires native event receipt');
|
|
493
|
+
reservedForNewDispatch = attempt.state === 'reserved';
|
|
494
|
+
}
|
|
495
|
+
else if (p.routingAttemptId !== undefined)
|
|
496
|
+
throw new Error('register --entity dispatch: routingAttemptId solo para compact v2');
|
|
497
|
+
const existing = s.dispatches.find(d => d.id === dispatchId);
|
|
498
|
+
if (existing && (existing.taskId !== taskId || existing.routingAttemptId !== routingAttemptId)) {
|
|
499
|
+
throw new Error('register --entity dispatch: dispatchId conflictivo');
|
|
500
|
+
}
|
|
501
|
+
if (!existing) {
|
|
502
|
+
if (routingAttemptId && s.dispatches.some(d => d.routingAttemptId === routingAttemptId)) {
|
|
503
|
+
throw new Error('register --entity dispatch: routing reservation ya tiene un dispatch');
|
|
504
|
+
}
|
|
505
|
+
if (!reservedForNewDispatch)
|
|
506
|
+
throw new Error('register --entity dispatch: routing attempt ya no esta reservado');
|
|
507
|
+
s.dispatches.push({ id: dispatchId, taskId, at: now(), ...(routingAttemptId ? { routingAttemptId, dispatchRequestId: env.requestId } : {}) });
|
|
459
508
|
const task = s.tasks.find((t) => t.id === taskId);
|
|
460
509
|
if (task !== undefined)
|
|
461
510
|
task.attempts += 1;
|
|
@@ -95,7 +95,7 @@ function promptForGeneration(gen) {
|
|
|
95
95
|
'resume-next-action': 'Retoma el next_action vigente del journal.',
|
|
96
96
|
'reconcile-active-jobs': 'Reconcilia los jobs activos existentes del journal sin duplicarlos.',
|
|
97
97
|
};
|
|
98
|
-
return `${instructions[gen.resumeAction.kind]}\nGeneracion activa: ${gen.token}. Incluye --generation ${gen.token} en
|
|
98
|
+
return `${instructions[gen.resumeAction.kind]}\nGeneracion activa: ${gen.token}. Incluye --generation ${gen.token} solo en awm job request, register, verdict, controller-heartbeat, routing-reserve y routing-observe. Ejecuta awm job reconcile sin --generation; awm job ack <requestId> tambien es de solo lectura y no acepta ese flag. En compact v2: espera el ack applied de routing-reserve, toma su resultRef como routingAttemptId, registra dispatch con ese routingAttemptId y espera su ack applied antes de lanzar al implementador nativo. Ningun implementador escribe antes de ese ack. Luego emite routing-observe y espera su ack; un recibo de emision no es un ack aplicado.`;
|
|
99
99
|
}
|
|
100
100
|
function recoveryAction(action) {
|
|
101
101
|
if (!(0, types_1.isControllerRecoveryAction)(action))
|
|
@@ -143,8 +143,18 @@ function registerWatchCommand(program) {
|
|
|
143
143
|
...(opts.controllerAutonomy === undefined ? {} : { controllerAutonomy: opts.controllerAutonomy }),
|
|
144
144
|
};
|
|
145
145
|
process.stdout.write(`awm watch: supervisor activo (${cfg.provider}) — Ctrl-C para terminar\n`);
|
|
146
|
-
await (0, supervisor_1.runSupervisorLoop)(repo, branch, cfg);
|
|
147
|
-
|
|
146
|
+
const outcome = await (0, supervisor_1.runSupervisorLoop)(repo, branch, cfg);
|
|
147
|
+
const final = (0, archive_unused_1.watchJournalStatus)(repo, branch);
|
|
148
|
+
if (outcome === 'complete' && final.state === 'present' && final.cycleState === 'COMPLETE') {
|
|
149
|
+
process.stdout.write('gate verde: ciclo COMPLETE — drenado, lock liberado, apagando\n');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
process.stdout.write(`awm watch: ${outcome === 'stopped' ? 'detenido' : 'track congelado'}; ciclo ${final.cycleState ?? final.state} — sin certificacion de cierre\n`);
|
|
153
|
+
if (outcome === 'stopped')
|
|
154
|
+
process.exitCode = 130;
|
|
155
|
+
else if (outcome === 'complete' || final.state !== 'present')
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
}
|
|
148
158
|
});
|
|
149
159
|
watch.command('recover-request')
|
|
150
160
|
.description('reconcilia una task rechazada con una request correctiva aplicada bajo lock exclusivo')
|
|
@@ -341,12 +341,61 @@ class Supervisor {
|
|
|
341
341
|
if (before0.corrupt || !before0.state)
|
|
342
342
|
throw new Error('journal no verificable tras vincular identidad de admisión');
|
|
343
343
|
}
|
|
344
|
-
|
|
344
|
+
// Full sensor admission belongs to the launch boundary. Once that
|
|
345
|
+
// generation has a durable controller launch intent, RED edits are
|
|
346
|
+
// normal work, not a new pre-dispatch baseline. Preserve binding
|
|
347
|
+
// checks while it runs; the next generation gets a fresh admission.
|
|
348
|
+
const binding = before0.state.planBinding;
|
|
349
|
+
if (!binding)
|
|
350
|
+
throw new Error('binding compacto ausente tras lectura del journal');
|
|
351
|
+
const launched = (0, generations_1.activeGeneration)(before0.state);
|
|
352
|
+
const ownedLaunch = launched !== undefined && (launched.processRef !== undefined || launched.wrapperRef !== undefined
|
|
353
|
+
|| (0, generations_1.controllerGenerationHasUnresolvedClaim)(this.repoRoot, this.branch, launched));
|
|
354
|
+
const generationAdmitted = launched?.controllerJobId !== undefined && launched.launchArgvDigest !== undefined && ownedLaunch;
|
|
355
|
+
if (generationAdmitted) {
|
|
356
|
+
const plan = (0, validate_1.validatePlanFile)(binding.path, this.repoRoot);
|
|
357
|
+
if (plan.state !== 'valid' || plan.planDigest !== binding.digest || plan.schema !== binding.schema
|
|
358
|
+
|| (binding.executionDigest !== undefined && plan.executionDigest !== binding.executionDigest)) {
|
|
359
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'binding del plan alterado durante generacion admitida');
|
|
360
|
+
return 'custody';
|
|
361
|
+
}
|
|
362
|
+
if (binding.schema === 'compact-slices/v2') {
|
|
363
|
+
const asserted = assertedContext.runtime;
|
|
364
|
+
if (!asserted) {
|
|
365
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'runtime de routing ausente durante generacion v2');
|
|
366
|
+
return 'custody';
|
|
367
|
+
}
|
|
368
|
+
const expected = (0, capabilities_1.validateRuntimeKey)({ target: assertedContext.provider, ...asserted });
|
|
369
|
+
let local;
|
|
370
|
+
try {
|
|
371
|
+
local = await (0, local_event_scope_1.queryLocalEventScope)(this.repoRoot, expected.target, expected.kind);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `runtime local no verificable: ${error.message}`);
|
|
375
|
+
return 'custody';
|
|
376
|
+
}
|
|
377
|
+
if (local.state !== 'current' || JSON.stringify(local.scope.runtime) !== JSON.stringify(expected)) {
|
|
378
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'runtime local cambió durante generacion admitida');
|
|
379
|
+
return 'custody';
|
|
380
|
+
}
|
|
381
|
+
const stored = (0, event_store_1.readStoredEventReceipt)(expected);
|
|
382
|
+
if (stored.state === 'invalid' || (stored.state === 'present' &&
|
|
383
|
+
(stored.receipt.binaryDigest !== local.scope.binaryDigest || stored.receipt.configDigest !== local.scope.configDigest))) {
|
|
384
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'recibo nativo cambió durante generacion admitida');
|
|
385
|
+
return 'custody';
|
|
386
|
+
}
|
|
387
|
+
if (stored.state === 'absent' && (0, capabilities_1.readCapabilities)(expected, new Date()).state !== 'current') {
|
|
388
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'capacidades de routing no verificables durante generacion admitida');
|
|
389
|
+
return 'custody';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (!generationAdmitted && before0.state.admissionRetry && Date.now() < Date.parse(before0.state.admissionRetry.nextRetryAt))
|
|
345
394
|
return 'continue';
|
|
346
395
|
// A controller can create jobs and runnerTick can start them. Both are
|
|
347
396
|
// downstream of the same full compact unattended admission, so it must
|
|
348
397
|
// complete before any reconciliation path that could dispatch either.
|
|
349
|
-
if (
|
|
398
|
+
if (!generationAdmitted) {
|
|
350
399
|
let admission;
|
|
351
400
|
try {
|
|
352
401
|
admission = await this.dispatchAdmission();
|
|
@@ -359,7 +408,6 @@ class Supervisor {
|
|
|
359
408
|
|| admission.currentness !== 'current' || admission.sensors !== 'pass' || admission.journal !== 'current') {
|
|
360
409
|
const kind = transientAdmission(admission);
|
|
361
410
|
if (kind) {
|
|
362
|
-
const binding = before0.state.planBinding;
|
|
363
411
|
const plan = (0, validate_1.validatePlanFile)(binding.path, this.repoRoot);
|
|
364
412
|
if (plan.state !== 'valid' || plan.planDigest !== binding.digest || plan.schema !== binding.schema
|
|
365
413
|
|| (binding.executionDigest !== undefined && plan.executionDigest !== binding.executionDigest)
|
|
@@ -565,6 +613,10 @@ class Supervisor {
|
|
|
565
613
|
const finalizing = afterRequests.state?.cohortPhase === 'FINAL_INTEGRATION' || afterRequests.state?.cohortPhase === 'FINAL_INTERLOCK';
|
|
566
614
|
if (!finalizing && afterRequests.state !== null && (0, generations_1.activeGeneration)(afterRequests.state) === undefined
|
|
567
615
|
&& afterRequests.state.generations.length > 0 && afterRequests.state.cycle.status === 'IN_PROGRESS') {
|
|
616
|
+
// The current tick used its launched generation's admission. A
|
|
617
|
+
// replacement must wait until the next tick's full admission.
|
|
618
|
+
if (generationAdmitted)
|
|
619
|
+
return 'continue';
|
|
568
620
|
(0, generations_1.beginGeneration)(this.repoRoot, this.branch);
|
|
569
621
|
if (this.ensureController(resumeAction) === 'custody')
|
|
570
622
|
return 'custody';
|
|
@@ -577,12 +629,15 @@ class Supervisor {
|
|
|
577
629
|
const freezeCheck = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
578
630
|
const freezing = !freezeCheck.corrupt && freezeCheck.state !== null
|
|
579
631
|
&& freezeCheck.state.freezeRequested === true && freezeCheck.state.frozen === undefined;
|
|
632
|
+
// Reconcile controller ownership and, if needed, freshly admit its
|
|
633
|
+
// replacement before runnerTick can launch received jobs.
|
|
634
|
+
const supervision = await this.superviseController();
|
|
635
|
+
if (supervision === 'custody')
|
|
636
|
+
return 'custody';
|
|
580
637
|
(0, runner_1.runnerTick)(this.repoRoot, this.branch, this.spawner, {
|
|
581
|
-
reconcileGraceMs: this.cfg.reconcileGraceMs, stallObservationMs: this.cfg.jobStallObservationMs,
|
|
638
|
+
reconcileGraceMs: this.cfg.reconcileGraceMs, stallObservationMs: this.cfg.jobStallObservationMs,
|
|
639
|
+
dispatch: !freezing && supervision === 'ok',
|
|
582
640
|
});
|
|
583
|
-
const custody = await this.superviseController();
|
|
584
|
-
if (custody)
|
|
585
|
-
return 'custody';
|
|
586
641
|
if (freezing)
|
|
587
642
|
return this.attemptFreeze();
|
|
588
643
|
const r = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
@@ -695,15 +750,23 @@ class Supervisor {
|
|
|
695
750
|
(0, store_1.appendEvent)(this.repoRoot, this.branch, { kind: 'track-frozen', headSha: s.frozen.headSha });
|
|
696
751
|
return 'frozen'; // paso 6 ("libera el lock y sale"): runSupervisorLoop trata 'frozen' igual que 'complete'
|
|
697
752
|
}
|
|
698
|
-
/**
|
|
753
|
+
/** Deferred ownership drains existing jobs but never starts new work. */
|
|
699
754
|
async superviseController() {
|
|
700
755
|
const r = (0, store_1.readJournal)(this.repoRoot, this.branch);
|
|
701
756
|
if (r.corrupt || r.state === null)
|
|
702
757
|
throw new Error('journal corrupto (R1.6)');
|
|
703
758
|
const s = r.state;
|
|
704
759
|
const gen = (0, generations_1.activeGeneration)(s);
|
|
705
|
-
|
|
706
|
-
|
|
760
|
+
// Track worktrees may be driven by the plan controller without a
|
|
761
|
+
// local generation. The canonical final-integration job also runs
|
|
762
|
+
// after the plan controller is deliberately paused. An existing but
|
|
763
|
+
// unadopted launch is different: claim recovery/backoff must never
|
|
764
|
+
// dispatch received jobs under that generation.
|
|
765
|
+
if (gen === undefined)
|
|
766
|
+
return s.trackContext || s.cohortPhase === 'FINAL_INTEGRATION'
|
|
767
|
+
|| s.cohortPhase === 'FINAL_INTERLOCK' ? 'ok' : 'deferred';
|
|
768
|
+
if (gen.processRef === undefined)
|
|
769
|
+
return 'deferred';
|
|
707
770
|
if (this.lastGenerationToken !== gen.token) {
|
|
708
771
|
this.lastGenerationToken = gen.token;
|
|
709
772
|
this.lastActivity = null;
|
|
@@ -716,10 +779,13 @@ class Supervisor {
|
|
|
716
779
|
this.lastActivity = { key, changedAt: Date.now() };
|
|
717
780
|
}
|
|
718
781
|
const activityFrozenMs = Date.now() - this.lastActivity.changedAt;
|
|
719
|
-
const
|
|
782
|
+
const safeToReplace = adapter.safeToReplace(gen.processRef);
|
|
783
|
+
// A dead process with proven identity is not healthy merely because
|
|
784
|
+
// its last heartbeat was recent. Resolve it before any new job starts.
|
|
785
|
+
const decision = snap === null && safeToReplace === 'safe' ? 'resolve-generation' : (0, generations_1.decideStall)({ heartbeatAgeMs, activityFrozenMs, safeToReplace }, { heartbeatTimeoutMs: this.cfg.heartbeatTimeoutMs, activityWindowMs: this.cfg.activityWindowMs });
|
|
720
786
|
if (decision === 'healthy') {
|
|
721
787
|
this.backoff.reset();
|
|
722
|
-
return
|
|
788
|
+
return 'ok';
|
|
723
789
|
}
|
|
724
790
|
if (decision === 'suspected-stall-observe') {
|
|
725
791
|
if (gen.state !== 'controller-suspected-stall') {
|
|
@@ -727,39 +793,53 @@ class Supervisor {
|
|
|
727
793
|
(0, store_1.writeJournal)(this.repoRoot, this.branch, s);
|
|
728
794
|
(0, store_1.appendEvent)(this.repoRoot, this.branch, { kind: 'controller-suspected-stall', n: gen.n });
|
|
729
795
|
}
|
|
730
|
-
return
|
|
796
|
+
return 'ok';
|
|
731
797
|
}
|
|
732
798
|
if (decision === 'custody-blocked') {
|
|
733
799
|
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'doble senial de stall sin safeToReplace positivo del adapter (R4.2b)');
|
|
734
|
-
return
|
|
800
|
+
return 'custody';
|
|
735
801
|
}
|
|
736
802
|
// resolve-generation
|
|
737
803
|
const resolved = await (0, generations_1.resolveGeneration)(this.repoRoot, this.branch, adapter, { termGraceMs: this.cfg.termGraceMs, killGraceMs: this.cfg.killGraceMs });
|
|
738
804
|
if (resolved === 'custody-blocked')
|
|
739
|
-
return
|
|
805
|
+
return 'custody';
|
|
740
806
|
if (this.backoff.exhausted()) {
|
|
741
807
|
(0, generations_1.enterCustody)(this.repoRoot, this.branch, 'tope de relanzamientos por hora alcanzado (R4.3)');
|
|
742
|
-
return
|
|
808
|
+
return 'custody';
|
|
743
809
|
}
|
|
744
810
|
if (Date.now() < this.relaunchNotBefore)
|
|
745
|
-
return
|
|
811
|
+
return 'deferred'; // esperando backoff, auditando
|
|
812
|
+
// This is a different controller generation, even though the tick
|
|
813
|
+
// began under the old one's admission. Do not reuse that admission
|
|
814
|
+
// after RED edits, runtime drift, or a currentness change.
|
|
815
|
+
let replacementAdmission;
|
|
816
|
+
try {
|
|
817
|
+
replacementAdmission = await this.dispatchAdmission();
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión de reemplazo no verificable: ${error.message}`);
|
|
821
|
+
return 'custody';
|
|
822
|
+
}
|
|
823
|
+
if (replacementAdmission.state !== 'admitted' || replacementAdmission.executionMode !== 'desatendido'
|
|
824
|
+
|| replacementAdmission.currentness !== 'current' || replacementAdmission.sensors !== 'pass'
|
|
825
|
+
|| replacementAdmission.journal !== 'current') {
|
|
826
|
+
(0, generations_1.enterCustody)(this.repoRoot, this.branch, `admisión de reemplazo bloqueada antes de dispatch: ${replacementAdmission.diagnostics.map(d => `${d.code}: ${d.message}`).join('; ')}`);
|
|
827
|
+
return 'custody';
|
|
828
|
+
}
|
|
746
829
|
(0, generations_1.beginGeneration)(this.repoRoot, this.branch);
|
|
747
830
|
const nextAction = (0, store_1.readJournal)(this.repoRoot, this.branch).state.cycle.nextAction;
|
|
748
831
|
const action = { schema: 'controller-recovery/v1', kind: nextAction !== undefined ? 'resume-next-action' : 'resume-cycle' };
|
|
749
832
|
const launched = this.ensureController(action);
|
|
750
833
|
if (launched === 'custody')
|
|
751
|
-
return
|
|
834
|
+
return 'custody';
|
|
752
835
|
if (launched === 'ok') {
|
|
753
836
|
this.backoff.recordRelaunch();
|
|
754
837
|
this.relaunchNotBefore = Date.now() + this.backoff.nextMs();
|
|
755
838
|
}
|
|
756
|
-
return
|
|
839
|
+
return launched === 'deferred' ? 'deferred' : 'ok';
|
|
757
840
|
}
|
|
758
841
|
}
|
|
759
842
|
exports.Supervisor = Supervisor;
|
|
760
|
-
/** Foreground, visible, terminable (R2.4): sin daemons. SIGINT/SIGTERM libera
|
|
761
|
-
* el lock y sale; COMPLETE => auto-exit liberando lock y terminando la
|
|
762
|
-
* generacion propia (cero huerfanos). */
|
|
763
843
|
async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.defaultWrapperSpawner)(), trackRuntime, dispatchAdmission) {
|
|
764
844
|
const r = (0, store_1.readJournal)(repoRoot, branch);
|
|
765
845
|
if (r.corrupt || r.state === null) {
|
|
@@ -772,11 +852,12 @@ async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.d
|
|
|
772
852
|
}
|
|
773
853
|
(0, lock_1.verifyBranchInvariant)(repoRoot, r.state.branch);
|
|
774
854
|
if (r.state.cycle.status === 'COMPLETE')
|
|
775
|
-
return;
|
|
855
|
+
return 'complete';
|
|
776
856
|
const handle = (0, lock_1.acquireLock)(repoRoot);
|
|
777
857
|
let shutdownRequested = false;
|
|
778
858
|
let wakeSleep = null;
|
|
779
859
|
let safeToRelease = false;
|
|
860
|
+
let terminal = 'stopped';
|
|
780
861
|
const onSignal = () => { shutdownRequested = true; wakeSleep?.(); };
|
|
781
862
|
process.on('SIGINT', onSignal);
|
|
782
863
|
process.on('SIGTERM', onSignal);
|
|
@@ -790,6 +871,7 @@ async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.d
|
|
|
790
871
|
// drenar ownership y liberar el lock, el track ya cumplió su
|
|
791
872
|
// freeze y no debe seguir despachando ni corriendo su loop.
|
|
792
873
|
if (out === 'complete' || out === 'frozen') {
|
|
874
|
+
terminal = out;
|
|
793
875
|
const finalState = (0, store_1.readJournal)(repoRoot, branch);
|
|
794
876
|
if (!finalState.corrupt && finalState.state?.routingIncidents?.length)
|
|
795
877
|
process.stderr.write(`AWM final routing summary ${JSON.stringify((0, journal_1.routingReport)(finalState.state))}\n`);
|
|
@@ -831,6 +913,7 @@ async function runSupervisorLoop(repoRoot, branch, cfg, spawner = (0, runner_1.d
|
|
|
831
913
|
if (shutdownRequested)
|
|
832
914
|
(0, store_1.writeJournal)(repoRoot, branch, sEnd);
|
|
833
915
|
safeToRelease = true;
|
|
916
|
+
return terminal;
|
|
834
917
|
}
|
|
835
918
|
finally {
|
|
836
919
|
process.removeListener('SIGINT', onSignal);
|
|
@@ -85,9 +85,15 @@ function isWellFormedEnvelope(x) {
|
|
|
85
85
|
}
|
|
86
86
|
function listPendingRequests(repoRoot, branch) {
|
|
87
87
|
const dir = (0, paths_1.requestsDir)(repoRoot, branch);
|
|
88
|
+
const directory = fs_1.default.lstatSync(dir);
|
|
89
|
+
if (directory.isSymbolicLink() || !directory.isDirectory())
|
|
90
|
+
throw new Error('request directory is not a controlled directory');
|
|
88
91
|
return fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.json')).sort().map((f) => {
|
|
89
92
|
const file = path_1.default.join(dir, f);
|
|
90
93
|
try {
|
|
94
|
+
const stat = fs_1.default.lstatSync(file);
|
|
95
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
96
|
+
return { requestId: f, envelope: null, file, corrupt: true };
|
|
91
97
|
const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
92
98
|
// Shape completa antes de cualquier uso downstream. JSON valido no
|
|
93
99
|
// implica envelope valido: payload/generation/idempotency ausentes
|
|
@@ -99,6 +99,8 @@ function isWellFormedState(x) {
|
|
|
99
99
|
return false;
|
|
100
100
|
if (!Array.isArray(x.generations) || !Array.isArray(x.tasks))
|
|
101
101
|
return false;
|
|
102
|
+
if (!x.tasks.every(isWellFormedTask))
|
|
103
|
+
return false;
|
|
102
104
|
if (!Array.isArray(x.cycleVerificationPlan) || !Array.isArray(x.verdicts) || !Array.isArray(x.fixes))
|
|
103
105
|
return false;
|
|
104
106
|
if (!Array.isArray(x.requiredVerifiers) || !x.requiredVerifiers.every((kind) => VERIFICATION_KINDS.includes(kind))
|
|
@@ -108,8 +110,40 @@ function isWellFormedState(x) {
|
|
|
108
110
|
return false;
|
|
109
111
|
if (!isObj(x.appliedRequests) || !Object.values(x.appliedRequests).every(isWellFormedAppliedRequest))
|
|
110
112
|
return false;
|
|
113
|
+
if (!Object.entries(x.appliedRequests).every(([requestId, ack]) => requestId === ack.requestId))
|
|
114
|
+
return false;
|
|
111
115
|
if (x.routingAttempts !== undefined && (!Array.isArray(x.routingAttempts) || x.routingAttempts.length > 4096 || !x.routingAttempts.every(isWellFormedRoutingAttempt)))
|
|
112
116
|
return false;
|
|
117
|
+
// A linked v2 dispatch is evidence of a native handoff. Reject a broken
|
|
118
|
+
// link or a second dispatch for one reservation on journal read as well as
|
|
119
|
+
// at request-application time. Historical unlinked records remain readable
|
|
120
|
+
// but cannot masquerade as this new acknowledged handoff.
|
|
121
|
+
if (x.planBinding?.schema === 'compact-slices/v2') {
|
|
122
|
+
const binding = x.planBinding;
|
|
123
|
+
const attempts = (x.routingAttempts ?? []);
|
|
124
|
+
const dispatches = x.dispatches;
|
|
125
|
+
const linked = new Set();
|
|
126
|
+
for (const dispatch of dispatches) {
|
|
127
|
+
if (!dispatch.routingAttemptId)
|
|
128
|
+
continue;
|
|
129
|
+
if (linked.has(dispatch.routingAttemptId))
|
|
130
|
+
return false;
|
|
131
|
+
linked.add(dispatch.routingAttemptId);
|
|
132
|
+
const attempt = attempts.find(candidate => candidate.id === dispatch.routingAttemptId);
|
|
133
|
+
if (!attempt || !(x.tasks.some(task => task.id === dispatch.taskId))
|
|
134
|
+
|| attempt.envelope.role !== 'implementer' || attempt.envelope.sliceId !== dispatch.taskId
|
|
135
|
+
|| attempt.envelope.planDigest !== binding.digest || attempt.envelope.executionDigest !== binding.executionDigest
|
|
136
|
+
|| !attempt.reservationRequestId || !dispatch.dispatchRequestId)
|
|
137
|
+
return false;
|
|
138
|
+
const acknowledgements = x.appliedRequests;
|
|
139
|
+
const reservationAck = acknowledgements[attempt.reservationRequestId];
|
|
140
|
+
const dispatchAck = acknowledgements[dispatch.dispatchRequestId];
|
|
141
|
+
if (reservationAck?.outcome !== 'applied' || reservationAck.requestId !== attempt.reservationRequestId
|
|
142
|
+
|| reservationAck.resultRef !== attempt.id || dispatchAck?.outcome !== 'applied'
|
|
143
|
+
|| dispatchAck.requestId !== dispatch.dispatchRequestId || dispatchAck.resultRef !== dispatch.id)
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
113
147
|
if (x.routingIncidents !== undefined && (!Array.isArray(x.routingIncidents) || x.routingIncidents.length > 4096 || !x.routingIncidents.every(isWellFormedRoutingIncident)))
|
|
114
148
|
return false;
|
|
115
149
|
if (x.implementationLineages !== undefined && (!Array.isArray(x.implementationLineages) || x.implementationLineages.length > 4096 || !x.implementationLineages.every(isWellFormedImplementationLineage)))
|
|
@@ -183,7 +217,10 @@ function isWellFormedNextAction(x) {
|
|
|
183
217
|
&& (x.state === 'pending' || x.state === 'in-progress');
|
|
184
218
|
}
|
|
185
219
|
function isWellFormedDispatch(x) {
|
|
186
|
-
return isObj(x) &&
|
|
220
|
+
return isObj(x) && Object.keys(x).every(key => ['id', 'taskId', 'at', 'routingAttemptId', 'dispatchRequestId'].includes(key))
|
|
221
|
+
&& typeof x.id === 'string' && x.id.length > 0 && typeof x.taskId === 'string' && x.taskId.length > 0
|
|
222
|
+
&& typeof x.at === 'string' && (x.routingAttemptId === undefined || (typeof x.routingAttemptId === 'string' && x.routingAttemptId.length > 0))
|
|
223
|
+
&& (x.dispatchRequestId === undefined || (typeof x.dispatchRequestId === 'string' && x.dispatchRequestId.length > 0));
|
|
187
224
|
}
|
|
188
225
|
function strings(x) {
|
|
189
226
|
return Array.isArray(x) && x.every((item) => typeof item === 'string');
|
|
@@ -235,11 +272,11 @@ function isWellFormedAppliedRequest(x) {
|
|
|
235
272
|
&& (x.resultRef === undefined || typeof x.resultRef === 'string');
|
|
236
273
|
}
|
|
237
274
|
function isWellFormedRoutingAttempt(x) {
|
|
238
|
-
return isObj(x) && Object.keys(x).every(key => ['schema', 'id', 'obligationId', 'lineageId', 'attempt', 'envelope', 'envelopeDigest', 'fingerprint', 'state', 'reservedAt', 'nativeEvidenceRequired', 'nativeEventDigest', 'nativeAgentId', 'observed', 'observedBackendModel', 'verdict', 'reasonCode', 'administrativeRepair'].includes(key))
|
|
275
|
+
return isObj(x) && Object.keys(x).every(key => ['schema', 'id', 'obligationId', 'lineageId', 'attempt', 'envelope', 'envelopeDigest', 'fingerprint', 'state', 'reservedAt', 'reservationRequestId', 'nativeEvidenceRequired', 'nativeEventDigest', 'nativeAgentId', 'observed', 'observedBackendModel', 'verdict', 'reasonCode', 'administrativeRepair'].includes(key))
|
|
239
276
|
&& x.schema === 'routing-attempt/v1'
|
|
240
277
|
&& typeof x.id === 'string' && typeof x.obligationId === 'string' && typeof x.lineageId === 'string' && Number.isSafeInteger(x.attempt) && x.attempt > 0
|
|
241
278
|
&& typeof x.envelopeDigest === 'string' && /^[a-f0-9]{64}$/.test(x.envelopeDigest) && typeof x.fingerprint === 'string' && /^[a-f0-9]{64}$/.test(x.fingerprint)
|
|
242
|
-
&& ['reserved', 'active', 'complete', 'blocked', 'unknown'].includes(String(x.state)) && (x.reservedAt === undefined || (typeof x.reservedAt === 'string' && Number.isFinite(Date.parse(x.reservedAt)))) && (x.nativeEvidenceRequired === undefined || x.nativeEvidenceRequired === true) && (x.nativeEventDigest === undefined || isDigest(x.nativeEventDigest)) && (x.nativeAgentId === undefined || typeof x.nativeAgentId === 'string') && (x.observed === undefined || isRoutingSelection(x.observed)) && (x.observedBackendModel === undefined || (typeof x.observedBackendModel === 'string' && x.observedBackendModel.length > 0 && x.observedBackendModel.length <= 128)) && (x.verdict === undefined || ['pass', 'fail', 'inconclusive'].includes(String(x.verdict))) && (x.reasonCode === undefined || typeof x.reasonCode === 'string') && (x.administrativeRepair === undefined || x.administrativeRepair === true) && isRoutingEnvelope(x.envelope);
|
|
279
|
+
&& ['reserved', 'active', 'complete', 'blocked', 'unknown'].includes(String(x.state)) && (x.reservedAt === undefined || (typeof x.reservedAt === 'string' && Number.isFinite(Date.parse(x.reservedAt)))) && (x.reservationRequestId === undefined || (typeof x.reservationRequestId === 'string' && x.reservationRequestId.length > 0 && x.reservationRequestId.length <= 128)) && (x.nativeEvidenceRequired === undefined || x.nativeEvidenceRequired === true) && (x.nativeEventDigest === undefined || isDigest(x.nativeEventDigest)) && (x.nativeAgentId === undefined || typeof x.nativeAgentId === 'string') && (x.observed === undefined || isRoutingSelection(x.observed)) && (x.observedBackendModel === undefined || (typeof x.observedBackendModel === 'string' && x.observedBackendModel.length > 0 && x.observedBackendModel.length <= 128)) && (x.verdict === undefined || ['pass', 'fail', 'inconclusive'].includes(String(x.verdict))) && (x.reasonCode === undefined || typeof x.reasonCode === 'string') && (x.administrativeRepair === undefined || x.administrativeRepair === true) && isRoutingEnvelope(x.envelope);
|
|
243
280
|
}
|
|
244
281
|
function isWellFormedRoutingIncident(x) {
|
|
245
282
|
if (!isObj(x) || Object.keys(x).length !== 12 || !Object.keys(x).every(key => ['schema', 'id', 'runStartedAt', 'target', 'selection', 'reasonCode', 'firstAt', 'lastAt', 'affectedObligations', 'fallbackCount', 'blockedCount', 'alertState'].includes(key)))
|
|
@@ -1,4 +1,37 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -19,6 +52,7 @@ const supervisor_1 = require("../../../src/commands/watch/supervisor");
|
|
|
19
52
|
const exec_wrapper_1 = require("../../../src/commands/job/exec-wrapper");
|
|
20
53
|
const lock_1 = require("../../../src/commands/watch/lock");
|
|
21
54
|
const archive_unused_1 = require("../../../src/commands/watch/archive-unused");
|
|
55
|
+
const adapterModule = __importStar(require("../../../src/core/journal/adapter"));
|
|
22
56
|
jest.setTimeout(30000);
|
|
23
57
|
function valid(repo) {
|
|
24
58
|
const report = (0, validate_1.validatePlanFile)('docs/plan.md', repo);
|
|
@@ -43,7 +77,7 @@ describe('compact lifecycle proof and unused bootstrap archival', () => {
|
|
|
43
77
|
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), `${fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8')}\n- [ ] Reviewed task.\n`);
|
|
44
78
|
(0, init_1.initWatch)(repo, 'main', { path: 'docs/plan.md', report: valid(repo) });
|
|
45
79
|
});
|
|
46
|
-
afterEach(() => { fs_1.default.rmSync(repo, { recursive: true, force: true }); });
|
|
80
|
+
afterEach(() => { jest.restoreAllMocks(); fs_1.default.rmSync(repo, { recursive: true, force: true }); });
|
|
47
81
|
test('durable journal retains hash commitments only, never the validated plan source body', () => {
|
|
48
82
|
const contents = (directory) => fs_1.default.readdirSync(directory, { withFileTypes: true }).map(entry => entry.isDirectory() ? contents(path_1.default.join(directory, entry.name)) : fs_1.default.readFileSync(path_1.default.join(directory, entry.name), 'utf8')).join('\n');
|
|
49
83
|
expect(contents((0, paths_1.journalDir)(repo, 'main'))).not.toContain('Reviewed task.');
|
|
@@ -138,6 +172,18 @@ describe('compact lifecycle proof and unused bootstrap archival', () => {
|
|
|
138
172
|
fingerprint: (0, fingerprint_1.computeFingerprint)(repo, argv, ['source.md'], '.').fingerprint, argv, paths: ['source.md'], cwd: '.',
|
|
139
173
|
} });
|
|
140
174
|
}
|
|
175
|
+
// This fixture acts as the external controller. Give it an adopted
|
|
176
|
+
// identity before expecting the supervisor to dispatch verification
|
|
177
|
+
// jobs; launch intent alone is deliberately insufficient.
|
|
178
|
+
expect(await supervisor.tick()).toBe('continue');
|
|
179
|
+
const launched = (0, store_1.readJournal)(repo, 'main').state;
|
|
180
|
+
const gen = launched.generations.find(entry => entry.token === generation.token);
|
|
181
|
+
gen.processRef = { pid: process.pid, processGroup: process.pid, startTime: 'fixture',
|
|
182
|
+
spawnNonce: gen.spawnNonce, argvDigest: gen.launchArgvDigest, psArgsDigest: 'b'.repeat(64) };
|
|
183
|
+
(0, store_1.writeJournal)(repo, 'main', launched);
|
|
184
|
+
const adapter = adapterModule.adapterFor('codex');
|
|
185
|
+
jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter,
|
|
186
|
+
activity: () => ({ cpuTime: '0', groupSize: 1 }), safeToReplace: () => 'indeterminate' });
|
|
141
187
|
for (let i = 0; i < 100; i++) {
|
|
142
188
|
expect(await supervisor.tick()).toBe('continue');
|
|
143
189
|
if (Object.values((0, store_1.readJournal)(repo, 'main').state.jobs).some(job => job.verdict === 'pass'))
|