@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,573 @@
1
+ // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
2
+ // (M3 cluster 10, runner core / supervisor / daemon).
3
+ // Relationship: Builds the TS-side PARITY ORACLE for the Commander daemon
4
+ // start/status/wait/stop primitives of the ReScript commander port, the
5
+ // sibling of build-core-commander-seams-parity-oracle.mjs. ONE bundle
6
+ // imports the REAL './src/commanderDaemon' exports behind the one-shot
7
+ // batch driver: a JSON array of cases on stdin, a JSON array of results on
8
+ // stdout. The ReScript package's CoreDaemonParityConformance drives the
9
+ // SAME inputs through src/core/CommanderDaemon.res and asserts
10
+ // accept/reject/normalized-output equivalence AND byte-identical status
11
+ // record files - the cross-impl contract that lets both commanders share
12
+ // ~/.linzumi/commanders through M4.
13
+ //
14
+ // Determinism / seams per op:
15
+ // - paths: HOME is redirected to the case scratch dir for the whole
16
+ // derivation (os.homedir() reads $HOME per call on posix).
17
+ // - start: timestamps are pinned by a scoped global-Date override
18
+ // (startedAt is not injectable in the TS surface), process.cwd() is
19
+ // pinned by chdir into $DIR/cwdpin, the spawn rides the TS spawnImpl
20
+ // injection point as a scripted seam with a fixed pid, and the
21
+ // hard-wired readProcessIdentity probe runs the REAL `ps` against a
22
+ // pid that cannot exist (999999999 exceeds every platform pid space),
23
+ // so the probe deterministically reports no identity on both impls.
24
+ // The TS audit-log writer (called directly by the module) is redirected
25
+ // via LINZUMI_CLI_AUDIT_LOG to a scratch OUTSIDE the snapshot dir and
26
+ // its lines come back projected to the stable fields (ts dropped).
27
+ // - start with real:true: where the seam is bypassed, the REAL spawn runs
28
+ // `node -e` against the module's own command shape ('commander' is the
29
+ // evaluated script - a bare ReferenceError, the child exits instantly).
30
+ // MASKING (structural, mirrored by the conformance side): pid is
31
+ // replaced by '<PID>' after asserting it is a positive number,
32
+ // processStartedAt is dropped from records/key lists (the `ps` probe
33
+ // racing a dying child is the one genuinely unpinnable bit), and the
34
+ // log file's node error output is replaced by '<VOLATILE_LOG>'.
35
+ // - status/stop: process liveness is the REAL process.kill(pid, 0) - the
36
+ // conformance feeds both impls the SAME genuinely-live pid (its own)
37
+ // and genuinely-dead pid (a reaped spawnSync child), so classification
38
+ // compares real kernel answers. The identity reader rides the TS
39
+ // injection point (scripted, with a call counter) except in stop,
40
+ // where the TS surface hard-wires the default: the liveChild leg runs
41
+ // a REAL detached `node -e 'setInterval(...)'` child so the real `ps`
42
+ // probe classifies it and the real SIGINT terminates it (childExited
43
+ // reports the observed exit; the volatile pid is masked to '<PID>').
44
+ // - wait: `now`, readTextFile, and statusImpl all ride the TS injection
45
+ // points as scripted sequences with call counters (the counters are
46
+ // part of the parity contract - both impls must consume the seams in
47
+ // the same order); the hard-wired fs.watch leg is exercised for real
48
+ // via the realLog case (a scheduled append to a real file), and the
49
+ // scripted-clock cases drive its timeout path with remaining=0 so no
50
+ // leg depends on wall time.
51
+ //
52
+ // SECRETS: the oracle only ever sees the conformance suite's synthetic
53
+ // ids/paths; nothing here logs case contents.
54
+ //
55
+ // The output is a local test artifact (.differential/ is gitignored, never
56
+ // published) and stays unminified for debuggability.
57
+ import { mkdir } from 'node:fs/promises';
58
+ import { dirname, join } from 'node:path';
59
+ import { fileURLToPath } from 'node:url';
60
+ import { build } from 'esbuild';
61
+
62
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
63
+
64
+ const driverSource = `
65
+ import {
66
+ appendFileSync,
67
+ mkdirSync,
68
+ mkdtempSync,
69
+ readdirSync,
70
+ readFileSync,
71
+ realpathSync,
72
+ statSync,
73
+ writeFileSync,
74
+ } from 'node:fs';
75
+ import { spawn } from 'node:child_process';
76
+ import { tmpdir } from 'node:os';
77
+ import { join, relative } from 'node:path';
78
+ import {
79
+ commanderDaemonStatus,
80
+ commanderStatusDir,
81
+ commanderStatusFile,
82
+ defaultCommanderLogFile,
83
+ startCommanderDaemon,
84
+ stopCommanderDaemon,
85
+ waitForCommanderDaemon,
86
+ } from './src/commanderDaemon';
87
+
88
+ // --- Placeholder + snapshot plumbing --------------------------------------------
89
+
90
+ const dirPlaceholder = '$DIR';
91
+
92
+ function scratch() {
93
+ // realpath so macOS /var -> /private/var never splits the two impls'
94
+ // placeholder substitution.
95
+ return realpathSync(mkdtempSync(join(tmpdir(), 'linzumi-core-daemon-oracle-')));
96
+ }
97
+
98
+ function substitute(value, from, to) {
99
+ if (typeof value === 'string') {
100
+ return value.split(from).join(to);
101
+ }
102
+ if (Array.isArray(value)) {
103
+ return value.map((entry) => substitute(entry, from, to));
104
+ }
105
+ if (typeof value === 'object' && value !== null) {
106
+ return Object.fromEntries(
107
+ Object.entries(value).map(([key, entry]) => [
108
+ substitute(key, from, to),
109
+ substitute(entry, from, to),
110
+ ])
111
+ );
112
+ }
113
+ return value;
114
+ }
115
+
116
+ function snapshotFiles(dir) {
117
+ const files = {};
118
+ const walk = (current) => {
119
+ for (const name of readdirSync(current).sort()) {
120
+ const full = join(current, name);
121
+ if (statSync(full).isDirectory()) {
122
+ walk(full);
123
+ } else {
124
+ files[relative(dir, full)] = readFileSync(full, 'utf8');
125
+ }
126
+ }
127
+ };
128
+ walk(dir);
129
+ return files;
130
+ }
131
+
132
+ function outcome(run) {
133
+ try {
134
+ const value = run();
135
+ return value === undefined ? { defined: false } : { defined: true, value };
136
+ } catch (error) {
137
+ return {
138
+ threw: true,
139
+ message: error instanceof Error ? error.message : String(error),
140
+ };
141
+ }
142
+ }
143
+
144
+ const RealDate = Date;
145
+
146
+ function withFixedNow(nowMs, run) {
147
+ if (nowMs === undefined) {
148
+ return run();
149
+ }
150
+ class FixedDate extends RealDate {
151
+ constructor(...args) {
152
+ if (args.length === 0) {
153
+ super(nowMs);
154
+ } else {
155
+ super(...args);
156
+ }
157
+ }
158
+ static now() {
159
+ return nowMs;
160
+ }
161
+ }
162
+ globalThis.Date = FixedDate;
163
+ try {
164
+ return run();
165
+ } finally {
166
+ globalThis.Date = RealDate;
167
+ }
168
+ }
169
+
170
+ function withEnv(key, value, run) {
171
+ const previous = process.env[key];
172
+ process.env[key] = value;
173
+ try {
174
+ return run();
175
+ } finally {
176
+ if (previous === undefined) {
177
+ delete process.env[key];
178
+ } else {
179
+ process.env[key] = previous;
180
+ }
181
+ }
182
+ }
183
+
184
+ // The daemon status normalizer shared by the status/stop ops.
185
+ function statusJson(status) {
186
+ if (status.status === 'missing') {
187
+ return { status: 'missing', runnerId: status.runnerId, statusFile: status.statusFile };
188
+ }
189
+ return { status: status.status, record: status.record };
190
+ }
191
+
192
+ // --- paths -----------------------------------------------------------------------
193
+
194
+ function handlePaths(testCase) {
195
+ const dir = scratch();
196
+ return withEnv('HOME', dir, () => {
197
+ const derived = {
198
+ statusDir: outcome(() => commanderStatusDir()),
199
+ statusFileDefault: outcome(() => commanderStatusFile(testCase.runnerId)),
200
+ statusFileScoped: outcome(() =>
201
+ commanderStatusFile(testCase.runnerId, join(dir, 'custom'))
202
+ ),
203
+ logFileDefault: outcome(() => defaultCommanderLogFile(testCase.runnerId)),
204
+ };
205
+ return substitute(derived, dir, dirPlaceholder);
206
+ });
207
+ }
208
+
209
+ // --- start -----------------------------------------------------------------------
210
+
211
+ // Audit-line projection: ts is wall-clock noise (pinned only under the
212
+ // scoped Date override, so dropped for uniformity); the compared surface is
213
+ // the event vocabulary and the payload the module hands the writer.
214
+ function projectAuditLine(line) {
215
+ const parsed = JSON.parse(line);
216
+ const projected = {};
217
+ for (const key of ['event', 'sessionId', 'command', 'args', 'cwd', 'pid', 'purpose']) {
218
+ if (parsed[key] !== undefined) {
219
+ projected[key] = parsed[key];
220
+ }
221
+ }
222
+ return projected;
223
+ }
224
+
225
+ function readAuditEvents(auditFile) {
226
+ let raw;
227
+ try {
228
+ raw = readFileSync(auditFile, 'utf8');
229
+ } catch {
230
+ return [];
231
+ }
232
+ return raw
233
+ .split('\\n')
234
+ .filter((line) => line.trim() !== '')
235
+ .map(projectAuditLine);
236
+ }
237
+
238
+ function maskRealSpawn(result, files, auditEvents) {
239
+ const masked = { result, statusKeys: null, logFilePresent: false };
240
+ if (result.defined === true) {
241
+ const record = { ...result.value };
242
+ record.pid = typeof record.pid === 'number' && record.pid > 0 ? '<PID>' : '<INVALID_PID>';
243
+ delete record.processStartedAt;
244
+ masked.result = { defined: true, value: record };
245
+ }
246
+ const fileEntries = {};
247
+ for (const [name, contents] of Object.entries(files)) {
248
+ if (name.endsWith('.log')) {
249
+ masked.logFilePresent = true;
250
+ continue;
251
+ }
252
+ const parsed = JSON.parse(contents);
253
+ masked.statusKeys = Object.keys(parsed).filter((key) => key !== 'processStartedAt');
254
+ parsed.pid = typeof parsed.pid === 'number' && parsed.pid > 0 ? '<PID>' : '<INVALID_PID>';
255
+ delete parsed.processStartedAt;
256
+ fileEntries[name] = parsed;
257
+ }
258
+ masked.files = fileEntries;
259
+ // The ps identity probe races the instantly-dying real child (present or
260
+ // absent exec_file_completed, live-vs-zombie command line), so only the
261
+ // spawn/spawned pair is compared for the real leg.
262
+ masked.auditEvents = auditEvents
263
+ .filter((event) => event.event === 'process.spawn' || event.event === 'process.spawned')
264
+ .map((event) =>
265
+ event.pid === undefined
266
+ ? event
267
+ : {
268
+ ...event,
269
+ pid: typeof event.pid === 'number' && event.pid > 0 ? '<PID>' : '<INVALID_PID>',
270
+ }
271
+ );
272
+ return masked;
273
+ }
274
+
275
+ function handleStart(testCase) {
276
+ const dir = scratch();
277
+ const auditDir = mkdtempSync(join(tmpdir(), 'linzumi-core-daemon-audit-'));
278
+ const auditFile = join(auditDir, 'audit.jsonl');
279
+ const spec = substitute(testCase, dirPlaceholder, dir);
280
+ const cwdPin = join(dir, 'cwdpin');
281
+ mkdirSync(cwdPin, { recursive: true });
282
+ const previousCwd = process.cwd();
283
+ const spawnCalls = [];
284
+ let realChildPid;
285
+ try {
286
+ process.chdir(cwdPin);
287
+ return withEnv('HOME', dir, () =>
288
+ withEnv('LINZUMI_CLI_AUDIT_LOG', auditFile, () => {
289
+ const spawnImpl =
290
+ spec.real === true
291
+ ? undefined
292
+ : (command, args, options) => {
293
+ spawnCalls.push({
294
+ command,
295
+ args,
296
+ detached: options.detached === true,
297
+ stdio: options.stdio.map((entry) =>
298
+ typeof entry === 'number' ? '<fd>' : String(entry)
299
+ ),
300
+ envIsProcessEnv: options.env === process.env,
301
+ });
302
+ return {
303
+ pid: spec.scriptedPid === null ? undefined : spec.scriptedPid,
304
+ unref() {},
305
+ };
306
+ };
307
+ const result = withFixedNow(spec.nowMs, () =>
308
+ outcome(() => {
309
+ const record = startCommanderDaemon({
310
+ runnerId: spec.runnerId,
311
+ args: spec.args ?? [],
312
+ cwd: spec.cwd,
313
+ logFile: spec.logFile,
314
+ statusDir: spec.statusDir,
315
+ nodeBin: spec.real === true ? process.execPath : spec.nodeBin,
316
+ entrypoint: spec.real === true ? '-e' : spec.entrypoint,
317
+ spawnImpl,
318
+ });
319
+ realChildPid = spec.real === true ? record.pid : undefined;
320
+ return record;
321
+ })
322
+ );
323
+ const files = snapshotFiles(dir);
324
+ const auditEvents = readAuditEvents(auditFile);
325
+ const payload =
326
+ spec.real === true
327
+ ? maskRealSpawn(result, files, auditEvents)
328
+ : { result, files, spawnCalls, auditEvents };
329
+ return substitute(payload, dir, dirPlaceholder);
330
+ })
331
+ );
332
+ } finally {
333
+ process.chdir(previousCwd);
334
+ if (realChildPid !== undefined) {
335
+ try {
336
+ process.kill(realChildPid, 'SIGKILL');
337
+ } catch {
338
+ // already exited - the expected disposition.
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ // --- status ----------------------------------------------------------------------
345
+
346
+ function handleStatus(testCase) {
347
+ const dir = scratch();
348
+ const spec = substitute(testCase, dirPlaceholder, dir);
349
+ if (spec.contents !== undefined) {
350
+ writeFileSync(commanderStatusFile(spec.recordId ?? spec.runnerId, dir), spec.contents);
351
+ }
352
+ let identityCalls = 0;
353
+ const reader =
354
+ spec.identity === undefined
355
+ ? undefined
356
+ : (pid) => {
357
+ identityCalls += 1;
358
+ if (spec.identity.mode === 'none') {
359
+ return undefined;
360
+ }
361
+ return {
362
+ command: spec.identity.command,
363
+ ...(spec.identity.startedAt === undefined
364
+ ? {}
365
+ : { startedAt: spec.identity.startedAt }),
366
+ };
367
+ };
368
+ const result = outcome(() => statusJson(commanderDaemonStatus(spec.runnerId, dir, reader)));
369
+ return substitute({ result, identityCalls }, dir, dirPlaceholder);
370
+ }
371
+
372
+ // --- wait ------------------------------------------------------------------------
373
+
374
+ async function handleWait(testCase) {
375
+ const dir = scratch();
376
+ const spec = substitute(testCase, dirPlaceholder, dir);
377
+ let nowCalls = 0;
378
+ let statusCalls = 0;
379
+ let readCalls = 0;
380
+ const now =
381
+ spec.nows === undefined
382
+ ? undefined
383
+ : () => {
384
+ const value = spec.nows[Math.min(nowCalls, spec.nows.length - 1)];
385
+ nowCalls += 1;
386
+ return value;
387
+ };
388
+ const statusImpl =
389
+ spec.statuses === undefined
390
+ ? undefined
391
+ : () => {
392
+ const value = spec.statuses[Math.min(statusCalls, spec.statuses.length - 1)];
393
+ statusCalls += 1;
394
+ if (value.status === 'missing') {
395
+ return {
396
+ status: 'missing',
397
+ runnerId: spec.runnerId,
398
+ statusFile: value.statusFile,
399
+ };
400
+ }
401
+ return { status: value.status, record: value.record };
402
+ };
403
+ const readTextFile =
404
+ spec.logs === undefined
405
+ ? undefined
406
+ : () => {
407
+ const value = spec.logs[Math.min(readCalls, spec.logs.length - 1)];
408
+ readCalls += 1;
409
+ return value === null ? undefined : value;
410
+ };
411
+ if (spec.realLog !== undefined) {
412
+ writeFileSync(join(dir, 'daemon.log'), spec.realLog.initial);
413
+ if (spec.realLog.append !== undefined) {
414
+ setTimeout(() => {
415
+ try {
416
+ appendFileSync(join(dir, 'daemon.log'), spec.realLog.append);
417
+ } catch {
418
+ // the wait already settled and the scratch is being torn down.
419
+ }
420
+ }, spec.realLog.appendAfterMs);
421
+ }
422
+ }
423
+ let result;
424
+ try {
425
+ const value = await waitForCommanderDaemon({
426
+ runnerId: spec.runnerId,
427
+ timeoutMs: spec.timeoutMs,
428
+ statusDir: dir,
429
+ now,
430
+ readTextFile,
431
+ statusImpl,
432
+ });
433
+ result = { defined: true, value };
434
+ } catch (error) {
435
+ result = {
436
+ threw: true,
437
+ message: error instanceof Error ? error.message : String(error),
438
+ };
439
+ }
440
+ return substitute(
441
+ {
442
+ result,
443
+ nowCalls: spec.nows === undefined ? null : nowCalls,
444
+ statusCalls: spec.statuses === undefined ? null : statusCalls,
445
+ readCalls: spec.logs === undefined ? null : readCalls,
446
+ },
447
+ dir,
448
+ dirPlaceholder
449
+ );
450
+ }
451
+
452
+ // --- stop ------------------------------------------------------------------------
453
+
454
+ function maskStatusPid(result, pid) {
455
+ if (result.defined !== true || pid === undefined) {
456
+ return result;
457
+ }
458
+ const value = result.value;
459
+ if (value.record === undefined || value.record.pid !== pid) {
460
+ return result;
461
+ }
462
+ return {
463
+ defined: true,
464
+ value: { ...value, record: { ...value.record, pid: '<PID>' } },
465
+ };
466
+ }
467
+
468
+ async function handleStop(testCase) {
469
+ const dir = scratch();
470
+ const spec = substitute(testCase, dirPlaceholder, dir);
471
+ let child;
472
+ let childExited = null;
473
+ if (spec.mode === 'liveChild') {
474
+ child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], {
475
+ stdio: 'ignore',
476
+ });
477
+ // Let the exec settle so the real ps probe sees the command line.
478
+ await new Promise((resolve) => setTimeout(resolve, 100));
479
+ const record = {
480
+ runnerId: spec.runnerId,
481
+ pid: child.pid,
482
+ cwd: '/',
483
+ logFile: '/nonexistent-daemon.log',
484
+ startedAt: '2026-07-01T00:00:00.000Z',
485
+ command: [],
486
+ };
487
+ writeFileSync(
488
+ commanderStatusFile(spec.runnerId, dir),
489
+ JSON.stringify(record, null, 2) + '\\n'
490
+ );
491
+ } else if (spec.contents !== undefined) {
492
+ writeFileSync(commanderStatusFile(spec.runnerId, dir), spec.contents);
493
+ }
494
+ const result = outcome(() => statusJson(stopCommanderDaemon(spec.runnerId, dir)));
495
+ if (child !== undefined) {
496
+ childExited = await new Promise((resolve) => {
497
+ if (child.exitCode !== null || child.signalCode !== null) {
498
+ resolve(true);
499
+ return;
500
+ }
501
+ const timer = setTimeout(() => resolve(false), 5000);
502
+ child.on('exit', () => {
503
+ clearTimeout(timer);
504
+ resolve(true);
505
+ });
506
+ });
507
+ try {
508
+ child.kill('SIGKILL');
509
+ } catch {
510
+ // already exited - the expected disposition.
511
+ }
512
+ }
513
+ return substitute(
514
+ { result: maskStatusPid(result, child?.pid), childExited },
515
+ dir,
516
+ dirPlaceholder
517
+ );
518
+ }
519
+
520
+ // --- Dispatch --------------------------------------------------------------------
521
+
522
+ async function handle(testCase) {
523
+ switch (testCase.op) {
524
+ case 'paths':
525
+ return handlePaths(testCase);
526
+ case 'start':
527
+ return handleStart(testCase);
528
+ case 'status':
529
+ return handleStatus(testCase);
530
+ case 'wait':
531
+ return handleWait(testCase);
532
+ case 'stop':
533
+ return handleStop(testCase);
534
+ default:
535
+ return { threw: true, message: 'unknown op ' + String(testCase.op) };
536
+ }
537
+ }
538
+
539
+ let stdin = '';
540
+ process.stdin.setEncoding('utf8');
541
+ for await (const chunk of process.stdin) {
542
+ stdin += chunk;
543
+ }
544
+ const cases = JSON.parse(stdin);
545
+ const results = [];
546
+ for (const testCase of cases) {
547
+ results.push(await handle(testCase));
548
+ }
549
+ process.stdout.write(JSON.stringify(results));
550
+ `;
551
+
552
+ await mkdir(join(packageRoot, '.differential'), { recursive: true });
553
+
554
+ await build({
555
+ stdin: {
556
+ contents: driverSource,
557
+ resolveDir: packageRoot,
558
+ sourcefile: 'core-daemon-parity-driver.ts',
559
+ loader: 'ts',
560
+ },
561
+ bundle: true,
562
+ platform: 'node',
563
+ target: 'node20',
564
+ format: 'esm',
565
+ outfile: join(packageRoot, '.differential/core-daemon-parity-oracle.mjs'),
566
+ minify: false,
567
+ sourcemap: false,
568
+ legalComments: 'none',
569
+ // commanderDaemon.ts pulls only the runnerLogger/namingDualEmit slice of
570
+ // the commander graph; the runtime externals stay aligned with the other
571
+ // cluster oracles so a future graph widening cannot silently inline them.
572
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
573
+ });