agentic-workflow-manager 9.12.1 → 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.
@@ -0,0 +1,749 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs_1 = __importDefault(require("fs"));
40
+ const os_1 = __importDefault(require("os"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const crypto_1 = require("crypto");
43
+ const child_process_1 = require("child_process");
44
+ const commander_1 = require("commander");
45
+ const apply_1 = require("../../../src/commands/watch/apply");
46
+ const generations_1 = require("../../../src/commands/watch/generations");
47
+ const types_1 = require("../../../src/core/journal/types");
48
+ const store_1 = require("../../../src/core/journal/store");
49
+ const requests_1 = require("../../../src/core/journal/requests");
50
+ const request_1 = require("../../../src/commands/job/request");
51
+ const init_1 = require("../../../src/commands/watch/init");
52
+ const validate_1 = require("../../../src/core/plan/validate");
53
+ const watch_1 = require("../../../src/commands/watch");
54
+ const supervisor_1 = require("../../../src/commands/watch/supervisor");
55
+ const supervisorModule = __importStar(require("../../../src/commands/watch/supervisor"));
56
+ const generationModule = __importStar(require("../../../src/commands/watch/generations"));
57
+ const adapterModule = __importStar(require("../../../src/core/journal/adapter"));
58
+ const routing_approval_1 = require("../../helpers/routing-approval");
59
+ const routing_approval_2 = require("../../helpers/routing-approval");
60
+ const canonical_1 = require("../../../src/core/model-policy/canonical");
61
+ const machine_key_1 = require("../../../src/core/model-policy/machine-key");
62
+ const native_routing_proof_1 = require("../../../src/core/model-policy/native-routing-proof");
63
+ const selection_policy_digest_1 = require("../../../src/core/model-policy/selection-policy-digest");
64
+ const admission_recovery_1 = require("../../../src/commands/watch/admission-recovery");
65
+ const eventStore = __importStar(require("../../../src/core/model-policy/event-store"));
66
+ const localScope = __importStar(require("../../../src/core/model-policy/local-event-scope"));
67
+ const exec_wrapper_1 = require("../../../src/commands/job/exec-wrapper");
68
+ const paths_1 = require("../../../src/core/journal/paths");
69
+ const paths_2 = require("../../../src/core/journal/paths");
70
+ function v2State() {
71
+ const state = (0, types_1.emptyState)('main');
72
+ state.schema = 2;
73
+ state.planBinding = {
74
+ path: 'plans/cycle.md', digest: routing_approval_1.routingEnvelope.planDigest,
75
+ executionDigest: routing_approval_1.routingEnvelope.executionDigest, executionIdentitySchema: 'awm-plan-execution/v1', schema: 'compact-slices/v2',
76
+ executionMode: 'desatendido', boundAt: new Date().toISOString(),
77
+ };
78
+ state.tasks.push({ id: 'S1', title: 'Implement S1', status: 'pending', attempts: 0,
79
+ verificationPlan: [], reviewObligations: [] });
80
+ state.routingAttempts = [{ schema: 'routing-attempt/v1', id: 'route-1', obligationId: 'S1-implementer',
81
+ lineageId: 'lineage-S1', attempt: 1, envelope: routing_approval_1.routingEnvelope,
82
+ envelopeDigest: 'e'.repeat(64), fingerprint: 'f'.repeat(64), state: 'reserved',
83
+ reservedAt: new Date().toISOString(), reservationRequestId: 'reserve-1', nativeEvidenceRequired: true }];
84
+ state.appliedRequests['reserve-1'] = { requestId: 'reserve-1', idempotencyKey: 'reserve-1',
85
+ payloadDigest: 'd'.repeat(64), outcome: 'applied', resultRef: 'route-1' };
86
+ return state;
87
+ }
88
+ function dispatch(state, payload) {
89
+ (0, apply_1.applyRequestToState)(state, { kind: 'register-entity', requestId: 'dispatch-request',
90
+ generationToken: 'g1', idempotencyKey: 'dispatch-key', payload: { entity: 'dispatch',
91
+ dispatchId: 'dispatch-S1', taskId: 'S1', ...payload } }, 'd'.repeat(64), process.cwd());
92
+ }
93
+ function boundRepo(version = 'v1') {
94
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-196-bound-'));
95
+ (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'main'], { cwd: repo });
96
+ (0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'commit', '--allow-empty', '-qm', 'seed'], { cwd: repo });
97
+ fs_1.default.mkdirSync(path_1.default.join(repo, 'plans'), { recursive: true });
98
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'plans', 'cycle.md'), fs_1.default.readFileSync(path_1.default.join(__dirname, `../../core/plan/fixtures/compact-slices-${version}/valid.md`), 'utf8'));
99
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'source.md'), '## Canonical source\nfixture source\n');
100
+ (0, init_1.initWatch)(repo, 'main');
101
+ const plan = (0, validate_1.validatePlanFile)('plans/cycle.md', repo);
102
+ if (plan.state !== 'valid')
103
+ throw new Error('invalid fixture');
104
+ const state = (0, store_1.readJournal)(repo, 'main').state;
105
+ state.schema = 2;
106
+ state.planBinding = { path: 'plans/cycle.md', digest: plan.planDigest, schema: plan.schema,
107
+ ...(plan.executionDigest ? { executionDigest: plan.executionDigest, executionIdentitySchema: 'awm-plan-execution/v1' } : {}),
108
+ executionMode: 'desatendido', boundAt: new Date().toISOString() };
109
+ (0, store_1.writeJournal)(repo, 'main', state);
110
+ return repo;
111
+ }
112
+ const admitted = { state: 'admitted', planState: 'valid', executionMode: 'desatendido',
113
+ journal: 'current', currentness: 'current', sensors: 'pass', diagnostics: [] };
114
+ const redSensor = { state: 'blocked', planState: 'valid', executionMode: 'desatendido',
115
+ journal: 'not-required', currentness: 'current', sensors: 'fail',
116
+ diagnostics: [{ code: 'ADMISSION_SENSORS_BLOCKED', message: 'format fails during RED' }] };
117
+ describe('issue #196 reopened: supervised S1 handoff', () => {
118
+ beforeAll(() => {
119
+ // These tests execute the published CLI entrypoint, not only ts-jest
120
+ // imports. Rebuild it so source regressions cannot hide behind dist.
121
+ (0, child_process_1.execFileSync)('npm', ['run', 'build'], {
122
+ cwd: path_1.default.resolve(__dirname, '../../../'), stdio: 'pipe',
123
+ // Windows exposes npm as npm.cmd, which requires a shell to spawn.
124
+ shell: process.platform === 'win32',
125
+ });
126
+ }, 30000);
127
+ test('controller prompt fences only generation-mutating job verbs, not reconcile', () => {
128
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-196-prompt-'));
129
+ try {
130
+ (0, store_1.initJournal)(repo, 'main');
131
+ (0, generations_1.beginGeneration)(repo, 'main');
132
+ let argv = [];
133
+ (0, generations_1.launchControllerGeneration)(repo, 'main', 'codex', { schema: 'controller-recovery/v1', kind: 'resume-next-action' }, job => { argv = job.argv; });
134
+ const prompt = argv.join(' ');
135
+ expect(prompt).toContain('awm job reconcile');
136
+ expect(prompt).toMatch(/reconcile[^\n]*sin --generation/);
137
+ expect(prompt).toMatch(/awm job ack/);
138
+ expect(prompt).toContain('--generation');
139
+ }
140
+ finally {
141
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
142
+ }
143
+ });
144
+ test('v2 dispatch requires an applied matching routing reservation before native launch', () => {
145
+ const state = v2State();
146
+ expect(() => dispatch(state, {})).toThrow(/routingAttemptId|routing/i);
147
+ expect(state.dispatches).toHaveLength(0);
148
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).not.toThrow();
149
+ expect(state.dispatches).toEqual([expect.objectContaining({ id: 'dispatch-S1', taskId: 'S1', routingAttemptId: 'route-1' })]);
150
+ expect(state.tasks[0].attempts).toBe(1);
151
+ });
152
+ test('v2 dispatch rejects a reservation for another slice without recording work', () => {
153
+ const state = v2State();
154
+ state.routingAttempts[0].envelope = { ...routing_approval_1.routingEnvelope, sliceId: 'S2' };
155
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).toThrow(/slice|task|routing/i);
156
+ expect(state.dispatches).toHaveLength(0);
157
+ expect(state.tasks[0].attempts).toBe(0);
158
+ });
159
+ test('v2 dispatch rejects unknown, unapplied and wrong-plan reservations', () => {
160
+ for (const scenario of ['unknown', 'unapplied', 'wrong-plan']) {
161
+ const state = v2State();
162
+ if (scenario === 'unapplied')
163
+ delete state.appliedRequests['reserve-1'];
164
+ if (scenario === 'wrong-plan')
165
+ state.routingAttempts[0].envelope = {
166
+ ...routing_approval_1.routingEnvelope, planDigest: 'a'.repeat(64),
167
+ };
168
+ expect(() => dispatch(state, { routingAttemptId: scenario === 'unknown' ? 'missing' : 'route-1' }))
169
+ .toThrow(/reservation|routing|plan|ack/i);
170
+ expect(state.dispatches).toHaveLength(0);
171
+ expect(state.tasks[0].attempts).toBe(0);
172
+ }
173
+ });
174
+ test('replay with a different routing attempt cannot reuse a dispatch ID', () => {
175
+ const state = v2State();
176
+ dispatch(state, { routingAttemptId: 'route-1' });
177
+ state.routingAttempts.push({ ...state.routingAttempts[0], id: 'route-2', reservationRequestId: 'reserve-2', state: 'reserved' });
178
+ state.appliedRequests['reserve-2'] = { requestId: 'reserve-2', idempotencyKey: 'reserve-2',
179
+ payloadDigest: 'd'.repeat(64), outcome: 'applied', resultRef: 'route-2' };
180
+ expect(() => dispatch(state, { routingAttemptId: 'route-2' })).toThrow(/conflict|dispatch/i);
181
+ expect(state.dispatches).toHaveLength(1);
182
+ expect(state.tasks[0].attempts).toBe(1);
183
+ });
184
+ test('an exact dispatch replay after native observation does not duplicate S1', () => {
185
+ const state = v2State();
186
+ dispatch(state, { routingAttemptId: 'route-1' });
187
+ state.routingAttempts[0].state = 'active';
188
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).not.toThrow();
189
+ expect(state.dispatches).toHaveLength(1);
190
+ expect(state.tasks[0].attempts).toBe(1);
191
+ });
192
+ test('a reservation cannot create a second dispatch with a different ID', () => {
193
+ const state = v2State();
194
+ dispatch(state, { routingAttemptId: 'route-1' });
195
+ expect(() => dispatch(state, { routingAttemptId: 'route-1', dispatchId: 'dispatch-S1-duplicate' }))
196
+ .toThrow(/reservation|dispatch|routing/i);
197
+ expect(state.dispatches).toHaveLength(1);
198
+ expect(state.tasks[0].attempts).toBe(1);
199
+ });
200
+ test('a persisted v2 dispatch cannot point to a missing routing attempt', () => {
201
+ const state = v2State();
202
+ dispatch(state, { routingAttemptId: 'route-1' });
203
+ expect((0, types_1.isWellFormedState)(state)).toBe(true);
204
+ state.dispatches[0].routingAttemptId = 'route-missing';
205
+ expect((0, types_1.isWellFormedState)(state)).toBe(false);
206
+ });
207
+ test('an unrelated applied ack cannot stand in for the routing reservation ack', () => {
208
+ const state = v2State();
209
+ delete state.appliedRequests['reserve-1'];
210
+ state.appliedRequests['task-ack'] = { requestId: 'task-ack', idempotencyKey: 'task-ack',
211
+ payloadDigest: 'd'.repeat(64), outcome: 'applied', resultRef: 'route-1' };
212
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).toThrow(/ack|reservation|routing/i);
213
+ expect(state.dispatches).toHaveLength(0);
214
+ });
215
+ test('v2 implementer dispatch refuses a legacy receipt without native provenance', () => {
216
+ const state = v2State();
217
+ state.routingAttempts[0].nativeEvidenceRequired = undefined;
218
+ expect(() => dispatch(state, { routingAttemptId: 'route-1' })).toThrow(/native|receipt|evidence/i);
219
+ expect(state.dispatches).toHaveLength(0);
220
+ });
221
+ test('v2 native observation cannot precede an acknowledged dispatch', () => {
222
+ const state = v2State();
223
+ expect(() => (0, apply_1.applyRequestToState)(state, { kind: 'routing-observe', requestId: 'observe-1', generationToken: 'g1',
224
+ idempotencyKey: 'observe-1', payload: { attemptId: 'route-1', nativeAgentId: 'child-1',
225
+ observed: routing_approval_1.routingEnvelope.resolved } }, 'd'.repeat(64), process.cwd())).toThrow(/dispatch|ack/i);
226
+ expect(state.routingAttempts[0].state).toBe('reserved');
227
+ });
228
+ test('a linked dispatch with no task is not a valid journal', () => {
229
+ const state = v2State();
230
+ dispatch(state, { routingAttemptId: 'route-1' });
231
+ expect((0, types_1.isWellFormedState)(state)).toBe(true);
232
+ state.tasks = [];
233
+ expect((0, types_1.isWellFormedState)(state)).toBe(false);
234
+ });
235
+ test('journal ACK map keys must name the same request as their values', () => {
236
+ const state = v2State();
237
+ state.appliedRequests['req-wrong'] = { requestId: 'req-other', idempotencyKey: 'alias',
238
+ payloadDigest: 'd'.repeat(64), outcome: 'applied', resultRef: 'route-1' };
239
+ expect((0, types_1.isWellFormedState)(state)).toBe(false);
240
+ });
241
+ test('real CLI exposes exact pending and applied request ack without changing the journal', () => {
242
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-196-ack-'));
243
+ try {
244
+ (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'main'], { cwd: repo });
245
+ (0, store_1.initJournal)(repo, 'main');
246
+ const emitted = (0, requests_1.emitRequest)(repo, 'main', { kind: 'controller-heartbeat', generationToken: 'g1',
247
+ idempotencyKey: 'heartbeat-1', payload: {} });
248
+ const compiled = path_1.default.resolve(__dirname, '../../../dist/src/index.js');
249
+ const run = (id) => (0, child_process_1.spawnSync)(process.execPath, [compiled, 'job', 'ack', id], {
250
+ cwd: repo, encoding: 'utf8', env: { ...process.env, AWM_NO_UPDATE_CHECK: '1' },
251
+ });
252
+ const pending = run(emitted.requestId);
253
+ expect(pending.status).toBe(0);
254
+ expect(JSON.parse(pending.stdout)).toMatchObject({ state: 'pending', requestId: emitted.requestId });
255
+ (0, apply_1.consumePendingRequests)(repo, 'main', 'g1');
256
+ const applied = run(emitted.requestId);
257
+ expect(applied.status).toBe(0);
258
+ expect(JSON.parse(applied.stdout)).toMatchObject({ state: 'applied', requestId: emitted.requestId });
259
+ const unknown = run('req-unknown');
260
+ expect(unknown.status).not.toBe(0);
261
+ expect(JSON.parse(unknown.stdout)).toMatchObject({ state: 'unknown', requestId: 'req-unknown' });
262
+ const rejectedRequest = (0, requests_1.emitRequest)(repo, 'main', { kind: 'register-entity', generationToken: 'g1',
263
+ idempotencyKey: 'bad-task', payload: { entity: 'task' } });
264
+ (0, apply_1.consumePendingRequests)(repo, 'main', 'g1');
265
+ const rejected = run(rejectedRequest.requestId);
266
+ expect(rejected.status).not.toBe(0);
267
+ expect(JSON.parse(rejected.stdout)).toMatchObject({ state: 'rejected', requestId: rejectedRequest.requestId });
268
+ const corruptRequest = (0, requests_1.emitRequest)(repo, 'main', { kind: 'controller-heartbeat', generationToken: 'g1',
269
+ idempotencyKey: 'bad-json', payload: {} });
270
+ fs_1.default.writeFileSync(corruptRequest.file, '{malformed');
271
+ expect(JSON.parse(run(corruptRequest.requestId).stdout)).toMatchObject({ state: 'unverifiable', requestId: corruptRequest.requestId });
272
+ (0, apply_1.consumePendingRequests)(repo, 'main', 'g1');
273
+ expect(JSON.parse(run(corruptRequest.requestId).stdout)).toMatchObject({ state: 'unverifiable', requestId: corruptRequest.requestId });
274
+ }
275
+ finally {
276
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
277
+ }
278
+ });
279
+ test('ack reports an unsafe or absent request directory as unverifiable', () => {
280
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-196-ack-dir-'));
281
+ try {
282
+ (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'main'], { cwd: repo });
283
+ (0, store_1.initJournal)(repo, 'main');
284
+ const dir = (0, paths_1.requestsDir)(repo, 'main');
285
+ const saved = `${dir}-saved`;
286
+ fs_1.default.renameSync(dir, saved);
287
+ const compiled = path_1.default.resolve(__dirname, '../../../dist/src/index.js');
288
+ const run = () => (0, child_process_1.spawnSync)(process.execPath, [compiled, 'job', 'ack', 'req-absent'], {
289
+ cwd: repo, encoding: 'utf8', env: { ...process.env, AWM_NO_UPDATE_CHECK: '1' },
290
+ });
291
+ const absent = run();
292
+ expect(absent.status).not.toBe(0);
293
+ expect(JSON.parse(absent.stdout)).toMatchObject({ state: 'unverifiable', reasonCode: 'request-directory' });
294
+ fs_1.default.symlinkSync(saved, dir, 'dir');
295
+ const linked = run();
296
+ expect(linked.status).not.toBe(0);
297
+ expect(JSON.parse(linked.stdout)).toMatchObject({ state: 'unverifiable', reasonCode: 'request-directory' });
298
+ fs_1.default.unlinkSync(dir);
299
+ fs_1.default.renameSync(saved, dir);
300
+ }
301
+ finally {
302
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
303
+ }
304
+ });
305
+ test('an admitted launched generation does not reclassify RED sensor failure as pre-dispatch custody', async () => {
306
+ const repo = boundRepo();
307
+ try {
308
+ let report = admitted;
309
+ let admissions = 0;
310
+ let spawns = 0;
311
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { spawns += 1; }, undefined, async () => { admissions += 1; return report; }, true);
312
+ expect(await sup.tick()).toBe('continue');
313
+ const launched = (0, store_1.readJournal)(repo, 'main').state;
314
+ expect(launched.generations).toHaveLength(1);
315
+ expect(launched.generations[0].launchArgvDigest).toBeDefined();
316
+ const gen = launched.generations[0];
317
+ gen.processRef = { pid: process.pid, processGroup: process.pid, startTime: 'fixture',
318
+ spawnNonce: gen.spawnNonce, argvDigest: gen.launchArgvDigest, psArgsDigest: 'b'.repeat(64) };
319
+ (0, store_1.writeJournal)(repo, 'main', launched);
320
+ const adapter = adapterModule.adapterFor('codex');
321
+ jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter,
322
+ activity: () => ({ cpuTime: '0', groupSize: 1 }), safeToReplace: () => 'indeterminate' });
323
+ fs_1.default.writeFileSync(path_1.default.join(repo, 's1-red.test.ts'), 'test("RED", () => { throw new Error("RED"); });\n');
324
+ (0, request_1.requestJob)(repo, 'main', gen.token, [process.execPath, '-e', 'process.exit(0)'], [], '.');
325
+ report = redSensor;
326
+ expect(await sup.tick()).toBe('continue');
327
+ const duringRed = (0, store_1.readJournal)(repo, 'main').state;
328
+ expect(duringRed.cycle.status).toBe('IN_PROGRESS');
329
+ expect(duringRed.generations).toHaveLength(1);
330
+ expect(admissions).toBe(1);
331
+ expect(spawns).toBe(2); // one controller launch, then the pending job during RED
332
+ expect(Object.keys(duringRed.jobs)).toHaveLength(1);
333
+ }
334
+ finally {
335
+ jest.restoreAllMocks();
336
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
337
+ }
338
+ });
339
+ test('the same RED continuation applies to compact v2 with unchanged native scope', async () => {
340
+ const repo = boundRepo('v2');
341
+ try {
342
+ const runtime = routing_approval_1.routingEnvelope.runtime;
343
+ const scope = { runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
344
+ jest.spyOn(localScope, 'queryLocalEventScope').mockResolvedValue({ state: 'current', scope });
345
+ jest.spyOn(eventStore, 'readStoredEventReceipt').mockReturnValue({ state: 'present', receipt: {
346
+ schema: 'routing-capabilities/v2', ...scope, recordedAt: new Date().toISOString(), claims: [],
347
+ } });
348
+ let report = admitted;
349
+ let admissions = 0;
350
+ const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, routingIdentity: {
351
+ kind: runtime.kind, version: runtime.version, accountScopeDigest: runtime.accountScopeDigest,
352
+ }, controllerAutonomy: 'approval-free' };
353
+ const sup = new supervisor_1.Supervisor(repo, 'main', cfg, () => { }, undefined, async () => { admissions += 1; return report; }, true);
354
+ expect(await sup.tick()).toBe('continue');
355
+ const gen = (0, store_1.readJournal)(repo, 'main').state.generations[0];
356
+ fs_1.default.writeFileSync((0, exec_wrapper_1.claimPath)((0, paths_1.logsDir)(repo, 'main'), gen.controllerJobId, gen.spawnNonce), '{}');
357
+ report = redSensor;
358
+ expect(await sup.tick()).toBe('continue');
359
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
360
+ expect(admissions).toBe(1);
361
+ }
362
+ finally {
363
+ jest.restoreAllMocks();
364
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
365
+ }
366
+ });
367
+ test('a new generation still fails closed when sensors are red', async () => {
368
+ const repo = boundRepo();
369
+ try {
370
+ let report = admitted;
371
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { }, undefined, async () => report, true);
372
+ expect(await sup.tick()).toBe('continue');
373
+ const state = (0, store_1.readJournal)(repo, 'main').state;
374
+ state.generations[0].state = 'terminated';
375
+ (0, store_1.writeJournal)(repo, 'main', state);
376
+ report = redSensor;
377
+ expect(await sup.tick()).toBe('custody');
378
+ expect((0, store_1.readJournal)(repo, 'main').state.generations).toHaveLength(1);
379
+ }
380
+ finally {
381
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
382
+ }
383
+ });
384
+ test('an active v2 generation refuses dispatch when local runtime identity drifts', async () => {
385
+ const repo = boundRepo('v2');
386
+ try {
387
+ (0, generations_1.beginGeneration)(repo, 'main');
388
+ (0, generations_1.launchControllerGeneration)(repo, 'main', 'codex', { schema: 'controller-recovery/v1', kind: 'resume-cycle' }, () => { });
389
+ const gen = (0, store_1.readJournal)(repo, 'main').state.generations[0];
390
+ fs_1.default.writeFileSync((0, exec_wrapper_1.claimPath)((0, paths_1.logsDir)(repo, 'main'), gen.controllerJobId, gen.spawnNonce), '{}');
391
+ const expected = routing_approval_1.routingEnvelope.runtime;
392
+ jest.spyOn(localScope, 'queryLocalEventScope').mockResolvedValue({ state: 'current', scope: {
393
+ runtime: { ...expected, version: '99.0.0' }, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64),
394
+ } });
395
+ const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, routingIdentity: {
396
+ kind: expected.kind, version: expected.version, accountScopeDigest: expected.accountScopeDigest,
397
+ }, controllerAutonomy: 'approval-free' };
398
+ expect(await new supervisor_1.Supervisor(repo, 'main', cfg, () => { }, undefined, async () => admitted, true).tick()).toBe('custody');
399
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('BLOCKED');
400
+ }
401
+ finally {
402
+ jest.restoreAllMocks();
403
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
404
+ }
405
+ });
406
+ test('crash before controller claim rechecks admission before retrying the launch', async () => {
407
+ const repo = boundRepo();
408
+ try {
409
+ let attempts = 0;
410
+ const spawn = () => { attempts += 1; throw new Error('launch offline'); };
411
+ expect(await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, spawn, undefined, async () => admitted, true).tick()).toBe('continue');
412
+ const intent = (0, store_1.readJournal)(repo, 'main').state.generations[0];
413
+ expect(intent.launchArgvDigest).toBeDefined();
414
+ expect(intent.wrapperRef).toBeUndefined();
415
+ expect(attempts).toBe(1);
416
+ expect(await new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, spawn, undefined, async () => redSensor, true).tick()).toBe('custody');
417
+ expect(attempts).toBe(1);
418
+ }
419
+ finally {
420
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
421
+ }
422
+ });
423
+ test('replacement after a stalled generation waits for fresh admission', async () => {
424
+ const repo = boundRepo();
425
+ try {
426
+ (0, generations_1.beginGeneration)(repo, 'main');
427
+ (0, generations_1.launchControllerGeneration)(repo, 'main', 'codex', { schema: 'controller-recovery/v1', kind: 'resume-cycle' }, () => { });
428
+ const state = (0, store_1.readJournal)(repo, 'main').state;
429
+ state.generations[0].processRef = { pid: 99999999, processGroup: 99999999, startTime: 'Thu Sep 24 20:00:00 2026',
430
+ spawnNonce: 'n', argvDigest: 'a'.repeat(64), psArgsDigest: 'b'.repeat(64) };
431
+ state.controllerHeartbeatAt = new Date(Date.now() - 3600_000).toISOString();
432
+ (0, store_1.writeJournal)(repo, 'main', state);
433
+ (0, request_1.requestJob)(repo, 'main', state.generations[0].token, [process.execPath, '-e', 'process.exit(0)'], [], '.');
434
+ const adapter = adapterModule.adapterFor('codex');
435
+ jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter, activity: () => null, safeToReplace: () => 'safe' });
436
+ jest.spyOn(generationModule, 'resolveGeneration').mockImplementation(async () => {
437
+ const current = (0, store_1.readJournal)(repo, 'main').state;
438
+ current.generations[0].state = 'terminated';
439
+ (0, store_1.writeJournal)(repo, 'main', current);
440
+ return 'proven-dead';
441
+ });
442
+ let admissions = 0;
443
+ let jobSpawns = 0;
444
+ const sup = new supervisor_1.Supervisor(repo, 'main', { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, heartbeatTimeoutMs: 0, activityWindowMs: 0 }, () => { jobSpawns += 1; }, undefined, async () => { admissions += 1; return redSensor; }, true);
445
+ expect(await sup.tick()).toBe('custody');
446
+ expect((0, store_1.readJournal)(repo, 'main').state.generations).toHaveLength(1);
447
+ expect(admissions).toBe(1);
448
+ expect(jobSpawns).toBe(0);
449
+ }
450
+ finally {
451
+ jest.restoreAllMocks();
452
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
453
+ }
454
+ });
455
+ test('relaunch backoff does not start received jobs under the dead generation', async () => {
456
+ const repo = boundRepo();
457
+ try {
458
+ (0, generations_1.beginGeneration)(repo, 'main');
459
+ (0, generations_1.launchControllerGeneration)(repo, 'main', 'codex', { schema: 'controller-recovery/v1', kind: 'resume-cycle' }, () => { });
460
+ const state = (0, store_1.readJournal)(repo, 'main').state;
461
+ state.generations[0].processRef = { pid: 99999998, processGroup: 99999998, startTime: 'Thu Sep 24 20:00:00 2026',
462
+ spawnNonce: 'n', argvDigest: 'a'.repeat(64), psArgsDigest: 'b'.repeat(64) };
463
+ state.controllerHeartbeatAt = new Date().toISOString();
464
+ (0, store_1.writeJournal)(repo, 'main', state);
465
+ (0, request_1.requestJob)(repo, 'main', state.generations[0].token, [process.execPath, '-e', 'process.exit(0)'], [], '.');
466
+ const adapter = adapterModule.adapterFor('codex');
467
+ jest.spyOn(adapterModule, 'adapterFor').mockReturnValue({ ...adapter, activity: () => null, safeToReplace: () => 'safe' });
468
+ jest.spyOn(generationModule, 'resolveGeneration').mockResolvedValue('proven-dead');
469
+ let spawns = 0;
470
+ let admissions = 0;
471
+ const sup = new supervisor_1.Supervisor(repo, 'main', { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, heartbeatTimeoutMs: 60_000, activityWindowMs: 0 }, () => { spawns += 1; }, undefined, async () => { admissions += 1; return admitted; }, true);
472
+ sup.relaunchNotBefore = Date.now() + 60_000;
473
+ expect(await sup.tick()).toBe('continue');
474
+ expect(spawns).toBe(0);
475
+ expect(admissions).toBe(0);
476
+ }
477
+ finally {
478
+ jest.restoreAllMocks();
479
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
480
+ }
481
+ });
482
+ test('failed initial controller launch cannot start received jobs during backoff', async () => {
483
+ const repo = boundRepo();
484
+ try {
485
+ (0, generations_1.beginGeneration)(repo, 'main');
486
+ const token = (0, store_1.readJournal)(repo, 'main').state.generations[0].token;
487
+ (0, request_1.requestJob)(repo, 'main', token, [process.execPath, '-e', 'process.exit(0)'], [], '.');
488
+ let jobSpawns = 0;
489
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { jobSpawns += 1; throw new Error('controller launch offline'); }, undefined, async () => admitted, true);
490
+ expect(await sup.tick()).toBe('continue');
491
+ expect(jobSpawns).toBe(1); // failed controller launch only
492
+ expect((0, store_1.readJournal)(repo, 'main').state.generations[0].processRef).toBeUndefined();
493
+ expect(await sup.tick()).toBe('continue');
494
+ expect(jobSpawns).toBe(1); // no controller retry and no job dispatch in backoff
495
+ }
496
+ finally {
497
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
498
+ }
499
+ });
500
+ test('unadopted controller wrapper cannot start received jobs', async () => {
501
+ const repo = boundRepo();
502
+ try {
503
+ (0, generations_1.beginGeneration)(repo, 'main');
504
+ const token = (0, store_1.readJournal)(repo, 'main').state.generations[0].token;
505
+ (0, request_1.requestJob)(repo, 'main', token, [process.execPath, '-e', 'process.exit(0)'], [], '.');
506
+ const wrapper = { pid: process.pid, processGroup: process.pid, startTime: 'Thu Sep 24 20:00:00 2026',
507
+ spawnNonce: 'n', argvDigest: 'a'.repeat(64), psArgsDigest: 'b'.repeat(64) };
508
+ (0, generations_1.launchControllerGeneration)(repo, 'main', 'codex', { schema: 'controller-recovery/v1', kind: 'resume-cycle' }, () => wrapper);
509
+ let jobSpawns = 0;
510
+ const sup = new supervisor_1.Supervisor(repo, 'main', supervisor_1.DEFAULT_SUPERVISOR_CONFIG, () => { jobSpawns += 1; }, undefined, async () => admitted, true);
511
+ expect(await sup.tick()).toBe('continue');
512
+ expect(jobSpawns).toBe(0);
513
+ }
514
+ finally {
515
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
516
+ }
517
+ });
518
+ test('watch CLI never announces COMPLETE when the loop stops with an in-progress journal', async () => {
519
+ const repo = boundRepo();
520
+ const originalCwd = process.cwd();
521
+ const output = [];
522
+ const write = jest.spyOn(process.stdout, 'write').mockImplementation(chunk => { output.push(String(chunk)); return true; });
523
+ const loop = jest.spyOn(supervisorModule, 'runSupervisorLoop').mockResolvedValue('stopped');
524
+ try {
525
+ process.chdir(repo);
526
+ const program = new commander_1.Command();
527
+ (0, watch_1.registerWatchCommand)(program);
528
+ await program.parseAsync(['node', 'awm', 'watch']);
529
+ expect(loop).toHaveBeenCalled();
530
+ expect(output.join('')).not.toContain('COMPLETE');
531
+ expect(output.join('')).toMatch(/IN_PROGRESS|detenido/i);
532
+ }
533
+ finally {
534
+ process.chdir(originalCwd);
535
+ write.mockRestore();
536
+ loop.mockRestore();
537
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
538
+ }
539
+ });
540
+ test('a frozen loop with a corrupt final journal exits nonzero', async () => {
541
+ const repo = boundRepo();
542
+ const previousCwd = process.cwd();
543
+ const previousExitCode = process.exitCode;
544
+ const output = [];
545
+ const write = jest.spyOn(process.stdout, 'write').mockImplementation(chunk => { output.push(String(chunk)); return true; });
546
+ const loop = jest.spyOn(supervisorModule, 'runSupervisorLoop').mockImplementation(async () => {
547
+ fs_1.default.writeFileSync((0, paths_1.statePath)(repo, 'main'), '{corrupt');
548
+ return 'frozen';
549
+ });
550
+ try {
551
+ process.chdir(repo);
552
+ const program = new commander_1.Command();
553
+ (0, watch_1.registerWatchCommand)(program);
554
+ await program.parseAsync(['node', 'awm', 'watch']);
555
+ expect(process.exitCode).toBe(1);
556
+ expect(output.join('')).not.toContain('gate verde');
557
+ expect(output.join('')).not.toContain('COMPLETE');
558
+ }
559
+ finally {
560
+ process.chdir(previousCwd);
561
+ process.exitCode = previousExitCode;
562
+ write.mockRestore();
563
+ loop.mockRestore();
564
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
565
+ }
566
+ });
567
+ (process.platform === 'win32' ? test.skip : test)('real SIGINT stops the loop without claiming COMPLETE', async () => {
568
+ const repo = boundRepo();
569
+ const compiled = path_1.default.resolve(__dirname, '../../../dist/src/commands/watch/supervisor.js');
570
+ const script = `const s=require(${JSON.stringify(compiled)}); const report={state:'admitted',planState:'valid',executionMode:'desatendido',journal:'current',currentness:'current',sensors:'pass',diagnostics:[]}; process.on('newListener',function ready(event){if(event==='SIGINT'){process.removeListener('newListener',ready);setImmediate(()=>console.log('SIGINT_READY'))}}); s.runSupervisorLoop(process.cwd(),'main',{...s.DEFAULT_SUPERVISOR_CONFIG,tickMs:30},()=>{},undefined,async()=>report).then(out=>console.log(JSON.stringify({out}))).catch(e=>{console.error(e);process.exitCode=2})`;
571
+ const child = (0, child_process_1.spawn)(process.execPath, ['-e', script], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
572
+ let stdout = '';
573
+ let stderr = '';
574
+ child.stdout.on('data', chunk => { stdout += String(chunk); });
575
+ child.stderr.on('data', chunk => { stderr += String(chunk); });
576
+ try {
577
+ const deadline = Date.now() + 4000;
578
+ // The lock is acquired just before the signal handler is installed.
579
+ // Wait for the child's readiness marker to avoid killing it in that gap.
580
+ while ((!fs_1.default.existsSync((0, paths_2.supervisorLockPath)(repo)) || !stdout.includes('SIGINT_READY')) && child.exitCode === null && Date.now() < deadline) {
581
+ await new Promise(resolve => setTimeout(resolve, 25));
582
+ }
583
+ expect(fs_1.default.existsSync((0, paths_2.supervisorLockPath)(repo))).toBe(true);
584
+ expect(stdout).toContain('SIGINT_READY');
585
+ child.kill('SIGINT');
586
+ const exit = await new Promise(resolve => child.once('exit', code => resolve(code)));
587
+ expect(exit).toBe(0);
588
+ expect(stdout).toContain('"out":"stopped"');
589
+ expect(stdout).not.toContain('COMPLETE');
590
+ expect(stderr).not.toContain('Error');
591
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
592
+ }
593
+ finally {
594
+ if (child.exitCode === null)
595
+ child.kill('SIGKILL');
596
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
597
+ }
598
+ }, 10000);
599
+ (process.platform === 'win32' ? test.skip : test)('compiled watch CLI reports interrupted journal truthfully', async () => {
600
+ const repo = boundRepo();
601
+ const supervisor = path_1.default.resolve(__dirname, '../../../dist/src/commands/watch/supervisor.js');
602
+ const index = path_1.default.resolve(__dirname, '../../../dist/src/index.js');
603
+ const harness = `const s=require(${JSON.stringify(supervisor)}); const keep=setInterval(()=>{},1000); s.runSupervisorLoop=()=>new Promise(resolve=>process.on('SIGINT',()=>{clearInterval(keep);resolve('stopped')})); process.argv=[process.execPath,${JSON.stringify(index)},'watch']; require(${JSON.stringify(index)}).program.parseAsync(process.argv).catch(e=>{console.error(e);process.exitCode=2});`;
604
+ const child = (0, child_process_1.spawn)(process.execPath, ['-e', harness], { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'] });
605
+ let stdout = '';
606
+ let stderr = '';
607
+ child.stdout.on('data', chunk => { stdout += String(chunk); });
608
+ child.stderr.on('data', chunk => { stderr += String(chunk); });
609
+ try {
610
+ const deadline = Date.now() + 4000;
611
+ while (!stdout.includes('supervisor activo') && child.exitCode === null && Date.now() < deadline) {
612
+ await new Promise(resolve => setTimeout(resolve, 25));
613
+ }
614
+ expect(stdout).toContain('supervisor activo');
615
+ child.kill('SIGINT');
616
+ const exit = await new Promise(resolve => child.once('close', code => resolve(code)));
617
+ expect(exit).toBe(130);
618
+ expect(stdout).toMatch(/detenido; ciclo IN_PROGRESS/);
619
+ expect(stdout).not.toContain('COMPLETE');
620
+ expect(stderr).not.toContain('Error');
621
+ }
622
+ finally {
623
+ if (child.exitCode === null)
624
+ child.kill('SIGKILL');
625
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
626
+ }
627
+ }, 10000);
628
+ test('recovered v2 S1 uses real CLI reservation, dispatch ack, observation and one continuation', async () => {
629
+ const repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-196-integral-'));
630
+ const previousHome = process.env.AWM_HOME;
631
+ const operator = path_1.default.join(repo, 'operator');
632
+ try {
633
+ (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'main'], { cwd: repo });
634
+ (0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', 'commit', '--allow-empty', '-qm', 'seed'], { cwd: repo });
635
+ fs_1.default.mkdirSync(path_1.default.join(repo, 'plans'), { recursive: true });
636
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'plans', 'cycle.md'), fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v2/valid.md'), 'utf8'));
637
+ fs_1.default.writeFileSync(path_1.default.join(repo, 'source.md'), '## Canonical source\nfixture source\n');
638
+ const plan = (0, validate_1.validatePlanFile)('plans/cycle.md', repo);
639
+ if (plan.state !== 'valid' || !plan.executionDigest)
640
+ throw new Error('invalid v2 fixture');
641
+ (0, init_1.initWatch)(repo, 'main', { path: 'plans/cycle.md', report: plan });
642
+ const blocked = (0, store_1.readJournal)(repo, 'main').state;
643
+ blocked.cycle.status = 'BLOCKED';
644
+ blocked.cycle.blockedReason = 'admisión compacta desatendida bloqueada antes de dispatch: ADMISSION_CURRENTNESS_BLOCKED: registry:baseline unverifiable';
645
+ (0, store_1.writeJournal)(repo, 'main', blocked);
646
+ const runtime = routing_approval_1.routingEnvelope.runtime;
647
+ const scope = { runtime, binaryDigest: 'b'.repeat(64), configDigest: 'c'.repeat(64) };
648
+ jest.spyOn(eventStore, 'readStoredEventReceipt').mockReturnValue({ state: 'present', receipt: {
649
+ schema: 'routing-capabilities/v2', ...scope, recordedAt: new Date().toISOString(), claims: [],
650
+ } });
651
+ jest.spyOn(localScope, 'queryLocalEventScope').mockResolvedValue({ state: 'current', scope });
652
+ expect(await (0, admission_recovery_1.recoverAdmissionCustody)(repo, 'main', { provider: 'codex',
653
+ routingIdentity: { kind: runtime.kind, version: runtime.version, accountScopeDigest: runtime.accountScopeDigest },
654
+ controllerAutonomy: 'approval-free', reason: 'Reverified local gates' }, async () => admitted)).toBe('recovered');
655
+ expect((0, store_1.readJournal)(repo, 'main').state.cycle.status).toBe('IN_PROGRESS');
656
+ jest.restoreAllMocks();
657
+ const generation = (0, generations_1.beginGeneration)(repo, 'main');
658
+ fs_1.default.mkdirSync(operator);
659
+ process.env.AWM_HOME = operator;
660
+ const machineKey = (0, machine_key_1.ensureMachineKey)(operator);
661
+ const approval = (0, routing_approval_2.routingApproval)();
662
+ approval.policy.content.mappings[0].degradation.allowMissingObservedIdentity = true;
663
+ approval.policy.contentDigest = (0, canonical_1.canonicalPolicyDigest)(approval.policy.content);
664
+ fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'model-policy.json'), JSON.stringify(approval.policy));
665
+ const enrolledAt = new Date(Date.now() - 1000).toISOString();
666
+ const eventDigest = 'e'.repeat(64);
667
+ const eventReceipt = { schema: 'routing-capabilities/v2', ...scope, recordedAt: enrolledAt,
668
+ claims: [{ selection: routing_approval_1.routingEnvelope.resolved,
669
+ mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(approval.policy.content.mappings[0], routing_approval_1.routingEnvelope.resolved),
670
+ eventDigest, source: 'codex-turn-context', observedAt: enrolledAt,
671
+ actualModel: 'unverified', tokenUsage: 'unknown' }] };
672
+ const receipt = path_1.default.join(operator, 'routing-capabilities-v2', 'codex', 'native.json');
673
+ fs_1.default.mkdirSync(path_1.default.dirname(receipt), { recursive: true });
674
+ const mac = (0, crypto_1.createHmac)('sha256', machineKey).update('AWM routing-capabilities/v2 local receipt\0')
675
+ .update(JSON.stringify(eventReceipt)).digest('hex');
676
+ fs_1.default.writeFileSync(receipt, JSON.stringify({ receipt: eventReceipt, mac }));
677
+ const envelope = { ...routing_approval_1.routingEnvelope, planDigest: plan.planDigest, executionDigest: plan.executionDigest,
678
+ policyDigest: approval.policy.contentDigest,
679
+ capabilityDigest: (0, crypto_1.createHash)('sha256').update(JSON.stringify(eventReceipt)).digest('hex'),
680
+ outcome: 'degraded', unavailableEvidence: ['observedModelEvidence'] };
681
+ const envelopeFile = path_1.default.join(repo, 'envelope.json');
682
+ fs_1.default.writeFileSync(envelopeFile, JSON.stringify(envelope));
683
+ const compiled = path_1.default.resolve(__dirname, '../../../dist/src/index.js');
684
+ const cli = (...args) => {
685
+ const result = (0, child_process_1.spawnSync)(process.execPath, [compiled, 'job', ...args], { cwd: repo, encoding: 'utf8',
686
+ env: { ...process.env, AWM_NO_UPDATE_CHECK: '1' } });
687
+ if (result.status !== 0)
688
+ throw new Error(`CLI ${args.join(' ')}: ${result.stderr} ${result.stdout}`);
689
+ return JSON.parse(result.stdout);
690
+ };
691
+ const task = cli('register', '--generation', generation.token, '--entity', 'task', '--json', JSON.stringify({
692
+ taskId: 'S1', title: 'Implement S1', verificationPlan: [], reviewObligations: [],
693
+ }));
694
+ expect(cli('ack', task.requestId).state).toBe('pending');
695
+ (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
696
+ expect((0, store_1.readJournal)(repo, 'main').state.requestProblems).toEqual([]);
697
+ expect(cli('ack', task.requestId).state).toBe('applied');
698
+ const reserve = cli('routing-reserve', '--generation', generation.token, '--obligation', 'S1-implementer', '--lineage', 'lineage-S1', '--envelope-file', envelopeFile, '--fingerprint', 'f'.repeat(64));
699
+ expect(cli('ack', reserve.requestId).state).toBe('pending');
700
+ (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
701
+ expect((0, store_1.readJournal)(repo, 'main').state.requestProblems).toEqual([]);
702
+ const reservation = cli('ack', reserve.requestId);
703
+ expect(reservation.state).toBe('applied');
704
+ const attemptId = reservation.resultRef;
705
+ const dispatchPayload = JSON.stringify({ dispatchId: 'dispatch-S1', taskId: 'S1', routingAttemptId: attemptId });
706
+ const queuedDispatch = cli('register', '--generation', generation.token, '--entity', 'dispatch', '--json', dispatchPayload);
707
+ expect(cli('ack', queuedDispatch.requestId).state).toBe('pending');
708
+ expect(fs_1.default.existsSync(path_1.default.join(repo, 's1-red.test.ts'))).toBe(false);
709
+ (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
710
+ expect(cli('ack', queuedDispatch.requestId)).toMatchObject({ state: 'applied', resultRef: 'dispatch-S1' });
711
+ const worker = (0, child_process_1.spawnSync)(process.execPath, ['-e', `require('fs').writeFileSync(${JSON.stringify(path_1.default.join(repo, 's1-red.test.ts'))}, 'test("RED", () => { throw new Error("RED"); });\\n')`], {
712
+ cwd: repo, encoding: 'utf8', env: { ...process.env, AWM_NO_UPDATE_CHECK: '1' },
713
+ });
714
+ expect(worker.status).toBe(0);
715
+ expect(fs_1.default.existsSync(path_1.default.join(repo, 's1-red.test.ts'))).toBe(true);
716
+ const proof = (0, native_routing_proof_1.signNativeRoutingProof)({ attemptId, nativeAgentId: 'native-child-1', source: 'codex-turn-context',
717
+ eventDigest, observedAt: new Date().toISOString(), selection: envelope.resolved }, machineKey);
718
+ const observation = (0, requests_1.emitRequest)(repo, 'main', { kind: 'routing-observe', generationToken: generation.token,
719
+ idempotencyKey: 'native-observation-S1', payload: { attemptId, nativeAgentId: 'native-child-1',
720
+ observed: envelope.resolved, nativeProof: proof } });
721
+ (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
722
+ expect(cli('ack', observation.requestId).state).toBe('applied');
723
+ const replay = cli('register', '--generation', generation.token, '--entity', 'dispatch', '--json', dispatchPayload);
724
+ (0, apply_1.consumePendingRequests)(repo, 'main', generation.token, scope);
725
+ expect(cli('ack', replay.requestId).state).toBe('applied');
726
+ expect(cli('reconcile').nextAction).toBeTruthy();
727
+ const unsupported = (0, child_process_1.spawnSync)(process.execPath, [compiled, 'job', 'reconcile', '--generation', generation.token], {
728
+ cwd: repo, encoding: 'utf8', env: { ...process.env, AWM_NO_UPDATE_CHECK: '1' },
729
+ });
730
+ expect(unsupported.status).not.toBe(0);
731
+ expect(unsupported.stderr).toMatch(/generation|unknown option/i);
732
+ const final = (0, store_1.readJournal)(repo, 'main').state;
733
+ expect(final.tasks).toHaveLength(1);
734
+ expect(final.dispatches).toHaveLength(1);
735
+ expect(final.routingAttempts).toHaveLength(1);
736
+ expect(final.routingAttempts[0].state).toBe('active');
737
+ expect(final.tasks[0].attempts).toBe(1);
738
+ expect(Object.keys(final.jobs)).toHaveLength(0);
739
+ }
740
+ finally {
741
+ jest.restoreAllMocks();
742
+ if (previousHome === undefined)
743
+ delete process.env.AWM_HOME;
744
+ else
745
+ process.env.AWM_HOME = previousHome;
746
+ fs_1.default.rmSync(repo, { recursive: true, force: true });
747
+ }
748
+ });
749
+ });