@linzumi/cli 1.0.110 → 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.
- package/README.md +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/scripts/build-core-commander-seams-parity-oracle.mjs +586 -0
- package/scripts/build-core-daemon-parity-oracle.mjs +573 -0
- package/scripts/build-core-dispatch-input-queue-parity-oracle.mjs +219 -0
- package/scripts/build-core-fate-ladder-parity-oracle.mjs +250 -0
- package/scripts/build-core-generation-overlap-parity-oracle.mjs +288 -0
- package/scripts/build-core-integration-parity-oracle.mjs +361 -0
- package/scripts/build-core-lifecycle-guards-parity-oracle.mjs +443 -0
- package/scripts/build-core-lock-parity-oracle.mjs +455 -0
- package/scripts/build-core-reconnect-resume-parity-oracle.mjs +256 -0
- package/scripts/build-core-startup-resume-scan-parity-oracle.mjs +296 -0
- package/scripts/build-core-supervisor-parity-oracle.mjs +734 -0
- package/scripts/build-pipeline-parity-oracle.mjs +610 -0
|
@@ -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');
|