@linzumi/cli 1.0.111 → 1.0.112

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,443 @@
1
+ // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
2
+ // (M3 cluster 10, commander core).
3
+ // Relationship: Builds the TS-side PARITY ORACLE for the lifecycle
4
+ // drain-guards slice of the ReScript commander port, a sibling of
5
+ // build-pipeline-parity-oracle.mjs (cluster 9). The oracle bundles the REAL
6
+ // TS lifecycle guards module (src/runnerLifecycleGuards.ts - the
7
+ // SIGTERM/SIGINT fast-exit drain and the SIGUSR2 graceful update-restart
8
+ // drain: drain constants, env resolvers, refusal/held/forced-kill reason
9
+ // strings, the hold classification, the SIGTERM routing decision, the
10
+ // force-exit verdict, and the two drain machines themselves) behind the
11
+ // cluster 1/2 one-shot BATCH driver (a JSON array of cases on stdin, a JSON
12
+ // array of results on stdout).
13
+ //
14
+ // Byte parity: log events are traced VERBATIM (event name + the TS field
15
+ // objects' own key order rides JSON.stringify), so the ReScript side's log
16
+ // field dicts must reproduce field presence AND insertion order exactly.
17
+ // Determinism: the graceful-drain runs ride a scripted clock + a scripted
18
+ // poll scheduler fired explicitly by ops, signal deliveries ride
19
+ // process.emit (synchronous JS listener dispatch, no real signal), and
20
+ // every op is followed by a macrotask settle so async hook completions land
21
+ // in a stable order. The fast-exit drain runs use real (small) deadline
22
+ // timers - the traced outcome is deterministic either way because scripted
23
+ // steps either settle immediately or never.
24
+ //
25
+ // SECRETS: the oracle only ever sees the conformance suite's synthetic
26
+ // payloads. The output is a local test artifact (.differential/ is
27
+ // gitignored, never published) and stays unminified for debuggability.
28
+ import { mkdir } from 'node:fs/promises';
29
+ import { dirname, join } from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+ import { build } from 'esbuild';
32
+
33
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
34
+
35
+ const driverSource = `
36
+ import {
37
+ runDrain,
38
+ installDrainOnSigterm,
39
+ gracefulUpdateDrainSignal,
40
+ defaultGracefulUpdateDrainMaxWaitMs,
41
+ defaultGracefulUpdateDrainHardCapMs,
42
+ defaultGracefulUpdateDrainOverlapHardCapMs,
43
+ gracefulUpdateDrainActivityIdleMs,
44
+ gracefulUpdateDrainPollMs,
45
+ gracefulUpdateDrainWaitingLogIntervalMs,
46
+ resolveGracefulUpdateDrainMaxWaitMs,
47
+ resolveGracefulUpdateDrainHardCapMs,
48
+ resolveGracefulUpdateDrainActivityIdleMs,
49
+ updateRestartDrainRefusalPrefix,
50
+ updateRestartDrainRefusalReason,
51
+ isUpdateRestartDrainRefusalError,
52
+ updateRestartDrainHeldReason,
53
+ defaultUpdateDrainHeldTurnMaxWaitMs,
54
+ updateDrainHeldTurnSweepIntervalMs,
55
+ resolveUpdateDrainHeldTurnMaxWaitMs,
56
+ classifyUpdateDrainStartHold,
57
+ updateRestartDrainForcedKillReasonPrefix,
58
+ updateRestartDrainForcedKillReason,
59
+ updateRestartDrainForcedKillWaitingOnApprovalReason,
60
+ shouldRouteSigtermThroughGracefulUpdateDrain,
61
+ updateDrainForceExitVerdict,
62
+ updateDrainShouldForceExit,
63
+ createGracefulUpdateDrain,
64
+ installGracefulUpdateDrainOnSignal,
65
+ } from './src/runnerLifecycleGuards';
66
+
67
+ const settle = () => new Promise((resolve) => setImmediate(resolve));
68
+
69
+ // Env cases carry null for "unset"; a real env never holds null.
70
+ function envOf(input) {
71
+ const env = {};
72
+ for (const [key, value] of Object.entries(input ?? {})) {
73
+ if (value !== null) env[key] = value;
74
+ }
75
+ return env;
76
+ }
77
+
78
+ function scriptedStep(name, behavior, trace) {
79
+ if (behavior === undefined || behavior === null || behavior === 'omit') {
80
+ return undefined;
81
+ }
82
+ return () => {
83
+ trace.push({ t: 'step', name });
84
+ if (behavior === 'fail') throw new Error(name + ' failed');
85
+ if (behavior === 'never') return new Promise(() => {});
86
+ return undefined;
87
+ };
88
+ }
89
+
90
+ function scriptedSteps(input, trace) {
91
+ return {
92
+ stopAccepting: scriptedStep('stopAccepting', input.stopAccepting ?? 'ok', trace),
93
+ interruptInFlightTurns: scriptedStep(
94
+ 'interruptInFlightTurns',
95
+ input.interruptInFlightTurns,
96
+ trace
97
+ ),
98
+ flushInboundQueues: scriptedStep('flushInboundQueues', input.flushInboundQueues ?? 'ok', trace),
99
+ flushOutbox: scriptedStep('flushOutbox', input.flushOutbox ?? 'ok', trace),
100
+ checkpointCurrentTurn: scriptedStep(
101
+ 'checkpointCurrentTurn',
102
+ input.checkpointCurrentTurn ?? 'ok',
103
+ trace
104
+ ),
105
+ };
106
+ }
107
+
108
+ async function runDrainStepsCase(input) {
109
+ const trace = [];
110
+ const steps = scriptedSteps(input.steps ?? {}, trace);
111
+ const log = (event, fields) => trace.push({ t: 'log', event, fields });
112
+ // The drain's deadline timer is unref'd by design; with a scripted
113
+ // never-resolving step it can be the ONLY pending work in this one-shot
114
+ // driver, and node would exit before it fires. Keep the loop alive for
115
+ // the duration of the case (production runners always have live handles).
116
+ const keepAlive = setInterval(() => {}, 25);
117
+ let result;
118
+ try {
119
+ const outcome = await runDrain(steps, input.deadlineMs, log);
120
+ result = { ok: { drained: outcome.drained, timedOut: outcome.timedOut } };
121
+ } catch (error) {
122
+ result = { error: error instanceof Error ? error.message : String(error) };
123
+ } finally {
124
+ clearInterval(keepAlive);
125
+ }
126
+ return { trace, result };
127
+ }
128
+
129
+ async function runSigtermDrainCase(input) {
130
+ const trace = [];
131
+ const steps = scriptedSteps(input.steps ?? {}, trace);
132
+ let exitThrowsRemaining = input.exitThrows ?? 0;
133
+ let exitCount = 0;
134
+ const installed = installDrainOnSigterm({
135
+ steps,
136
+ deadlineMs: input.deadlineMs ?? 5_000,
137
+ log: (event, fields) => trace.push({ t: 'log', event, fields }),
138
+ exit: (code, signal) => {
139
+ trace.push({ t: 'exit', code, signal: signal ?? null });
140
+ exitCount += 1;
141
+ if (exitThrowsRemaining > 0) {
142
+ exitThrowsRemaining -= 1;
143
+ throw new Error('scripted exit failure');
144
+ }
145
+ },
146
+ forceExit: (code) => {
147
+ trace.push({ t: 'forceExit', code });
148
+ if (input.forceExitThrows === true) {
149
+ throw new Error('scripted force-exit failure');
150
+ }
151
+ },
152
+ signals: input.signals ?? undefined,
153
+ });
154
+ let uninstalled = false;
155
+ for (const op of input.ops ?? []) {
156
+ if (op.op === 'trigger') installed.trigger(op.signal);
157
+ else if (op.op === 'emit') process.emit(op.signal);
158
+ else if (op.op === 'uninstall') {
159
+ installed.uninstall();
160
+ uninstalled = true;
161
+ }
162
+ await settle();
163
+ }
164
+ const expectedExits = input.expectedExits ?? 0;
165
+ const deadlineAt = Date.now() + 5_000;
166
+ while (exitCount < expectedExits && Date.now() < deadlineAt) {
167
+ await new Promise((resolve) => setTimeout(resolve, 5));
168
+ }
169
+ await settle();
170
+ if (!uninstalled) installed.uninstall();
171
+ return { trace };
172
+ }
173
+
174
+ function turnOf(spec) {
175
+ return {
176
+ threadId: spec.threadId,
177
+ turnId: spec.turnId ?? undefined,
178
+ engine: spec.engine,
179
+ };
180
+ }
181
+
182
+ async function runUpdateDrainCase(input) {
183
+ const trace = [];
184
+ const clock = { nowMs: input.startMs ?? 0 };
185
+ const state = {
186
+ turns: (input.turns ?? []).map(turnOf),
187
+ quiescent: input.quiescent === true,
188
+ activity: input.activity ?? undefined,
189
+ approvalPending: input.approvalPending === true,
190
+ waitEligible: input.waitEligible === true,
191
+ };
192
+ const scheduler = { active: null, nextId: 1 };
193
+ const scheduleRepeating = (callback, ms) => {
194
+ const id = scheduler.nextId;
195
+ scheduler.nextId += 1;
196
+ trace.push({ t: 'interval.set', id, ms });
197
+ scheduler.active = { id, callback };
198
+ return id;
199
+ };
200
+ const cancelRepeating = (handle) => {
201
+ if (scheduler.active !== null && scheduler.active.id === handle) {
202
+ trace.push({ t: 'interval.clear', id: scheduler.active.id });
203
+ scheduler.active = null;
204
+ }
205
+ };
206
+ const hardCapMs =
207
+ input.capEnv !== undefined && input.capEnv !== null
208
+ ? resolveGracefulUpdateDrainHardCapMs(
209
+ envOf(input.capEnv),
210
+ input.maxWaitMs,
211
+ input.capOverlap === true
212
+ )
213
+ : input.hardCapMs ?? undefined;
214
+ const hooks = {
215
+ beginDraining: () => trace.push({ t: 'beginDraining' }),
216
+ isQuiescent: () => state.quiescent,
217
+ isWaitEligible:
218
+ input.waitEligible === undefined || input.waitEligible === null
219
+ ? undefined
220
+ : () => state.waitEligible,
221
+ inFlightTurns: input.withTurnsHook === false ? undefined : () => state.turns.slice(),
222
+ lastActivityAtMs: input.withActivityHook === false ? undefined : () => state.activity,
223
+ publishForcedKillTerminalStates:
224
+ input.withPublishHook === false
225
+ ? undefined
226
+ : (turns) => {
227
+ trace.push({
228
+ t: 'publishForcedKill',
229
+ threadIds: turns.map((turn) => turn.threadId),
230
+ reasons: turns.map(() =>
231
+ state.approvalPending
232
+ ? updateRestartDrainForcedKillWaitingOnApprovalReason
233
+ : updateRestartDrainForcedKillReason
234
+ ),
235
+ });
236
+ if (input.publishFails === true) {
237
+ throw new Error('scripted publish failure');
238
+ }
239
+ return undefined;
240
+ },
241
+ onTurnCompleted:
242
+ input.withOnTurnCompleted === true
243
+ ? (turn) =>
244
+ trace.push({ t: 'turnCompleted', threadId: turn.threadId, turnId: turn.turnId ?? null })
245
+ : undefined,
246
+ exitViaDrain: (outcome) =>
247
+ trace.push({ t: 'exitViaDrain', forced: outcome.forced, waitedMs: outcome.waitedMs }),
248
+ };
249
+ const params = {
250
+ hooks,
251
+ maxWaitMs: input.maxWaitMs,
252
+ hardCapMs,
253
+ activityIdleMs: input.activityIdleMs ?? undefined,
254
+ pollMs: input.pollMs ?? undefined,
255
+ waitingLogIntervalMs: input.waitingLogIntervalMs ?? undefined,
256
+ log: (event, fields) => trace.push({ t: 'log', event, fields }),
257
+ scheduler: { setInterval: scheduleRepeating, clearInterval: cancelRepeating },
258
+ now: () => clock.nowMs,
259
+ };
260
+ let requestDrain;
261
+ let cancelDrain;
262
+ let cleanup;
263
+ if (input.installSignals !== undefined && input.installSignals !== null) {
264
+ const installed = installGracefulUpdateDrainOnSignal({
265
+ ...params,
266
+ signals: input.installSignals,
267
+ });
268
+ requestDrain = installed.requestDrain;
269
+ cancelDrain = installed.cancelDrain;
270
+ cleanup = installed.uninstall;
271
+ } else {
272
+ const drain = createGracefulUpdateDrain(params);
273
+ requestDrain = drain.requestDrain;
274
+ cancelDrain = drain.cancelDrain;
275
+ cleanup = drain.stop;
276
+ }
277
+ for (const op of input.ops ?? []) {
278
+ if (op.op === 'requestDrain') requestDrain(op.trigger);
279
+ else if (op.op === 'cancelDrain') trace.push({ t: 'cancelResult', value: cancelDrain(op.reason) });
280
+ else if (op.op === 'advance') clock.nowMs = op.toMs;
281
+ else if (op.op === 'firePoll') {
282
+ if (scheduler.active !== null) scheduler.active.callback();
283
+ } else if (op.op === 'setTurns') state.turns = op.turns.map(turnOf);
284
+ else if (op.op === 'setActivity') state.activity = op.atMs;
285
+ else if (op.op === 'clearActivity') state.activity = undefined;
286
+ else if (op.op === 'setQuiescent') state.quiescent = op.value === true;
287
+ else if (op.op === 'setApprovalPending') state.approvalPending = op.value === true;
288
+ else if (op.op === 'setWaitEligible') state.waitEligible = op.value === true;
289
+ else if (op.op === 'stop') cleanup();
290
+ else if (op.op === 'emit') process.emit(op.signal);
291
+ await settle();
292
+ }
293
+ await settle();
294
+ cleanup();
295
+ return { trace };
296
+ }
297
+
298
+ async function runCase(input) {
299
+ switch (input.kind) {
300
+ case 'constants':
301
+ return {
302
+ gracefulUpdateDrainSignal,
303
+ defaultGracefulUpdateDrainMaxWaitMs,
304
+ defaultGracefulUpdateDrainHardCapMs,
305
+ defaultGracefulUpdateDrainOverlapHardCapMs,
306
+ gracefulUpdateDrainActivityIdleMs,
307
+ gracefulUpdateDrainPollMs,
308
+ gracefulUpdateDrainWaitingLogIntervalMs,
309
+ defaultUpdateDrainHeldTurnMaxWaitMs,
310
+ updateDrainHeldTurnSweepIntervalMs,
311
+ updateRestartDrainRefusalPrefix,
312
+ updateRestartDrainRefusalReason,
313
+ updateRestartDrainHeldReason,
314
+ updateRestartDrainForcedKillReasonPrefix,
315
+ updateRestartDrainForcedKillReason,
316
+ updateRestartDrainForcedKillWaitingOnApprovalReason,
317
+ };
318
+ case 'resolveMaxWait':
319
+ return resolveGracefulUpdateDrainMaxWaitMs(envOf(input.env));
320
+ case 'resolveHardCap':
321
+ return resolveGracefulUpdateDrainHardCapMs(
322
+ envOf(input.env),
323
+ input.maxWaitMs ?? undefined,
324
+ input.generationOverlap === true
325
+ );
326
+ case 'resolveActivityIdle':
327
+ return resolveGracefulUpdateDrainActivityIdleMs(envOf(input.env));
328
+ case 'resolveHeldTurnMaxWait':
329
+ return resolveUpdateDrainHeldTurnMaxWaitMs(envOf(input.env));
330
+ case 'isRefusalError': {
331
+ const value =
332
+ input.errorKind === 'error'
333
+ ? new Error(input.message)
334
+ : input.errorKind === 'string'
335
+ ? input.message
336
+ : input.errorKind === 'number'
337
+ ? input.number
338
+ : input.errorKind === 'null'
339
+ ? null
340
+ : undefined;
341
+ return isUpdateRestartDrainRefusalError(value);
342
+ }
343
+ case 'classifyHold': {
344
+ const decision = classifyUpdateDrainStartHold({
345
+ controlType: input.controlType,
346
+ channel: input.channel ?? undefined,
347
+ threadId: input.threadId ?? undefined,
348
+ sourceSeq: input.sourceSeq ?? undefined,
349
+ workDescription: input.workDescription ?? undefined,
350
+ workspace: input.workspace ?? undefined,
351
+ codexThreadId: input.codexThreadId ?? undefined,
352
+ cwd: input.cwd ?? undefined,
353
+ agentProvider: input.agentProvider ?? undefined,
354
+ hasRegistryRecord: input.hasRegistryRecord === true,
355
+ });
356
+ return { hold: decision.hold, refuse: decision.hold ? null : decision.refuse };
357
+ }
358
+ case 'routeSigterm':
359
+ return shouldRouteSigtermThroughGracefulUpdateDrain({
360
+ supervised: input.supervised === true,
361
+ platform: input.platform,
362
+ });
363
+ case 'forceExitVerdict': {
364
+ const verdict = updateDrainForceExitVerdict({
365
+ waitedMs: input.waitedMs,
366
+ maxWaitMs: input.maxWaitMs,
367
+ hardCapMs: input.hardCapMs,
368
+ nowMs: input.nowMs,
369
+ lastActivityAtMs: input.lastActivityAtMs ?? undefined,
370
+ activityIdleMs: input.activityIdleMs ?? undefined,
371
+ });
372
+ return {
373
+ force: verdict.force,
374
+ cause: verdict.force ? verdict.cause : null,
375
+ idleMs: verdict.force ? (verdict.idleMs ?? null) : null,
376
+ shouldForce: updateDrainShouldForceExit({
377
+ waitedMs: input.waitedMs,
378
+ maxWaitMs: input.maxWaitMs,
379
+ hardCapMs: input.hardCapMs,
380
+ nowMs: input.nowMs,
381
+ lastActivityAtMs: input.lastActivityAtMs ?? undefined,
382
+ activityIdleMs: input.activityIdleMs ?? undefined,
383
+ }),
384
+ };
385
+ }
386
+ case 'drainStepsRun':
387
+ return runDrainStepsCase(input);
388
+ case 'sigtermDrainRun':
389
+ return runSigtermDrainCase(input);
390
+ case 'updateDrainRun':
391
+ return runUpdateDrainCase(input);
392
+ default:
393
+ return { error: 'unknown case kind ' + String(input.kind) };
394
+ }
395
+ }
396
+
397
+ async function runBatch() {
398
+ const chunks = [];
399
+ for await (const chunk of process.stdin) chunks.push(chunk);
400
+ const cases = JSON.parse(Buffer.concat(chunks).toString('utf8'));
401
+ const results = [];
402
+ for (const testCase of cases) {
403
+ results.push(await runCase(testCase));
404
+ }
405
+ process.stdout.write(JSON.stringify(results), () => {
406
+ process.exit(0);
407
+ });
408
+ }
409
+
410
+ runBatch().then(
411
+ () => {},
412
+ (error) => {
413
+ process.stderr.write(String(error && error.stack ? error.stack : error) + '\\n');
414
+ process.exit(1);
415
+ }
416
+ );
417
+ `;
418
+
419
+ const outDir = join(packageRoot, '.differential');
420
+ const outPath = join(outDir, 'core-lifecycle-guards-parity-oracle.mjs');
421
+
422
+ await mkdir(outDir, { recursive: true });
423
+
424
+ await build({
425
+ stdin: {
426
+ contents: driverSource,
427
+ resolveDir: packageRoot,
428
+ sourcefile: 'core-lifecycle-guards-parity-driver.ts',
429
+ loader: 'ts',
430
+ },
431
+ bundle: true,
432
+ platform: 'node',
433
+ target: 'node20',
434
+ format: 'esm',
435
+ outfile: outPath,
436
+ minify: false,
437
+ sourcemap: false,
438
+ legalComments: 'none',
439
+ logLevel: 'silent',
440
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
441
+ });
442
+
443
+ process.stdout.write(outPath + '\n');