@linzumi/cli 1.0.95 → 1.0.97

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,708 @@
1
+ // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
2
+ // (M3 cluster 3, auth).
3
+ // Relationship: Builds the TS-side PARITY ORACLE for the auth cluster of
4
+ // the ReScript commander port, the sibling of
5
+ // build-protocol-parity-oracle.mjs (cluster 1) and
6
+ // build-config-identity-parity-oracle.mjs (cluster 2). The oracle bundles
7
+ // the REAL TS auth modules (oauth.ts, deviceAuthWait.ts, authResolution.ts,
8
+ // runnerTokenExpiry.ts, runnerTokenRenewer.ts, channelSessionSupport.ts's
9
+ // access-token claim slice, authCache.ts, mcpServer.ts's
10
+ // resolveMcpLocalRunnerToken, runner.ts's writeEphemeralMcpAuthFile, and
11
+ // signupServerClient.ts + signupFlow.ts's signup-auth transforms) behind
12
+ // the same one-shot stdin/stdout batch driver: a JSON array of cases in, a
13
+ // JSON array of results out.
14
+ //
15
+ // FLOW cases carry a `baseUrl`/`kandanUrl` pointing at the ReScript parity
16
+ // suite's scripted auth HTTP server (ScriptedAuthServer.res), so the REAL
17
+ // TS flow functions run their real fetch legs against the same scripted
18
+ // wire the ReScript port runs against - outcome equivalence is asserted on
19
+ // both sides by AuthParityConformance.res.
20
+ //
21
+ // Determinism: every clock is injected per case (pollDeviceCodeForToken's
22
+ // `now` dep, the device wait's deps.now, the renewer's deps.now/setTimer)
23
+ // or scoped through the fixed-Date override for the modules whose
24
+ // timestamps are not injectable (authCache's issuedAt, signupFlow's
25
+ // createdAt). Scratch paths normalize through the "$DIR" placeholder.
26
+ //
27
+ // SECRETS: the oracle only ever sees the conformance suite's synthetic
28
+ // tokens; nothing here logs case payloads.
29
+ import { copyFile, mkdir } from 'node:fs/promises';
30
+ import { dirname, join } from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+ import { build } from 'esbuild';
33
+
34
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
35
+
36
+ const driverSource = `
37
+ import { mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
38
+ import { tmpdir } from 'node:os';
39
+ import { join } from 'node:path';
40
+ import {
41
+ deviceAuthFailureMessage,
42
+ kandanHttpBaseUrl,
43
+ pollDeviceCodeForToken,
44
+ renewLocalRunnerToken,
45
+ startDeviceAuthorization,
46
+ validateLocalRunnerTokenOutcome,
47
+ } from './src/oauth';
48
+ import {
49
+ deviceWaitPromptDecision,
50
+ renderDeviceWaitEvent,
51
+ waitForDeviceAuthorization,
52
+ } from './src/deviceAuthWait';
53
+ import {
54
+ resolveLocalRunnerToken,
55
+ unusableCachedTokenNotice,
56
+ } from './src/authResolution';
57
+ import { resolveRunnerTokenExpiresAt } from './src/runnerTokenExpiry';
58
+ import { createRunnerTokenRenewer } from './src/runnerTokenRenewer';
59
+ import {
60
+ accessTokenExpiresAtIso,
61
+ identityFromAccessToken,
62
+ singleWorkspaceScopeFromAccessToken,
63
+ } from './src/channelSessionSupport';
64
+ import {
65
+ readCachedLocalRunnerToken,
66
+ writeCachedLocalRunnerToken,
67
+ } from './src/authCache';
68
+ import { resolveMcpLocalRunnerToken } from './src/mcpServer';
69
+ import { writeEphemeralMcpAuthFile } from './src/runner';
70
+ import { createSignupServerClient } from './src/signupServerClient';
71
+ import {
72
+ comparableServiceUrl,
73
+ signupAuthFromVerification,
74
+ signupAuthMatchesServiceUrl,
75
+ } from './src/signupFlow';
76
+
77
+ // --- Shared plumbing (the cluster 2 conventions) -----------------------------------
78
+
79
+ const dirPlaceholder = '$DIR';
80
+
81
+ function scratch() {
82
+ return realpathSync(mkdtempSync(join(tmpdir(), 'linzumi-auth-oracle-')));
83
+ }
84
+
85
+ function substitute(value, from, to) {
86
+ if (typeof value === 'string') {
87
+ return value.split(from).join(to);
88
+ }
89
+ if (Array.isArray(value)) {
90
+ return value.map((entry) => substitute(entry, from, to));
91
+ }
92
+ if (typeof value === 'object' && value !== null) {
93
+ return Object.fromEntries(
94
+ Object.entries(value).map(([key, entry]) => [
95
+ substitute(key, from, to),
96
+ substitute(entry, from, to),
97
+ ])
98
+ );
99
+ }
100
+ return value;
101
+ }
102
+
103
+ function outcome(run) {
104
+ try {
105
+ const value = run();
106
+ return value === undefined ? { defined: false } : { defined: true, value };
107
+ } catch (error) {
108
+ return errorOutcome(error);
109
+ }
110
+ }
111
+
112
+ async function outcomeAsync(run) {
113
+ try {
114
+ const value = await run();
115
+ return value === undefined ? { defined: false } : { defined: true, value };
116
+ } catch (error) {
117
+ return errorOutcome(error);
118
+ }
119
+ }
120
+
121
+ function errorOutcome(error) {
122
+ const shaped = {
123
+ threw: true,
124
+ message: error instanceof Error ? error.message : String(error),
125
+ };
126
+ if (error instanceof Error && error.name === 'SignupServerError') {
127
+ shaped.name = error.name;
128
+ shaped.status = error.status;
129
+ shaped.code = error.code;
130
+ }
131
+ return shaped;
132
+ }
133
+
134
+ const RealDate = Date;
135
+
136
+ function withFixedNow(nowMs, run) {
137
+ if (nowMs === undefined) {
138
+ return run();
139
+ }
140
+ class FixedDate extends RealDate {
141
+ constructor(...args) {
142
+ if (args.length === 0) {
143
+ super(nowMs);
144
+ } else {
145
+ super(...args);
146
+ }
147
+ }
148
+ static now() {
149
+ return nowMs;
150
+ }
151
+ }
152
+ globalThis.Date = FixedDate;
153
+ try {
154
+ return run();
155
+ } finally {
156
+ globalThis.Date = RealDate;
157
+ }
158
+ }
159
+
160
+ // A deterministic clock: starts at startMs and advances by stepMs per
161
+ // read, matching AuthParityConformance's steppedNow.
162
+ function steppedNow(startMs, stepMs) {
163
+ let reads = 0;
164
+ return () => {
165
+ const value = startMs + reads * stepMs;
166
+ reads += 1;
167
+ return value;
168
+ };
169
+ }
170
+
171
+ function fileMode(path) {
172
+ return statSync(path).mode & 0o777;
173
+ }
174
+
175
+ // --- Pure transforms -----------------------------------------------------------------
176
+
177
+ function handleAccessTokenClaims(testCase) {
178
+ return {
179
+ identity: outcome(() => identityFromAccessToken(testCase.token)),
180
+ workspaceScope: outcome(() => singleWorkspaceScopeFromAccessToken(testCase.token)),
181
+ expiresAtIso: outcome(() => accessTokenExpiresAtIso(testCase.token)),
182
+ };
183
+ }
184
+
185
+ function handleDeviceAuthFailureMessage(testCase) {
186
+ return outcome(() =>
187
+ deviceAuthFailureMessage(testCase.error ?? undefined, testCase.httpStatus ?? undefined)
188
+ );
189
+ }
190
+
191
+ function handleDeviceWaitPromptDecision(testCase) {
192
+ return outcome(() =>
193
+ deviceWaitPromptDecision({
194
+ cycle: testCase.cycle,
195
+ nowMs: testCase.nowMs,
196
+ lastLoudPromptAtMs: testCase.lastLoudPromptAtMs ?? undefined,
197
+ isInteractive: testCase.isInteractive,
198
+ })
199
+ );
200
+ }
201
+
202
+ function handleRenderDeviceWaitEvent(testCase) {
203
+ return outcome(() => renderDeviceWaitEvent(testCase.event));
204
+ }
205
+
206
+ function handleUnusableCachedTokenNotice(testCase) {
207
+ return outcome(() => unusableCachedTokenNotice(testCase.outcome));
208
+ }
209
+
210
+ function handleKandanHttpBaseUrl(testCase) {
211
+ return outcome(() => kandanHttpBaseUrl(testCase.kandanUrl));
212
+ }
213
+
214
+ function handleComparableServiceUrl(testCase) {
215
+ return outcome(() => comparableServiceUrl(testCase.value));
216
+ }
217
+
218
+ function handleSignupAuthMatch(testCase) {
219
+ return outcome(() => signupAuthMatchesServiceUrl(testCase.auth, testCase.serviceUrl));
220
+ }
221
+
222
+ function handleSignupAuthFromVerification(testCase) {
223
+ return withFixedNow(testCase.nowMs, () =>
224
+ outcome(() => signupAuthFromVerification(testCase.verifiedAuth, testCase.serviceUrl))
225
+ );
226
+ }
227
+
228
+ // --- Token expiry resolution -----------------------------------------------------------
229
+
230
+ function handleRunnerTokenExpiry(testCase) {
231
+ const dir = scratch();
232
+ const authFilePath = join(dir, 'auth.json');
233
+ if (testCase.authFileContents !== undefined) {
234
+ writeFileSync(authFilePath, testCase.authFileContents, 'utf8');
235
+ }
236
+ const result = withFixedNow(testCase.nowMs, () =>
237
+ outcome(() =>
238
+ resolveRunnerTokenExpiresAt({
239
+ kandanUrl: testCase.kandanUrl,
240
+ token: testCase.token,
241
+ explicitToken: testCase.explicitToken ?? undefined,
242
+ authFilePath,
243
+ })
244
+ )
245
+ );
246
+ return substitute(result, dir, dirPlaceholder);
247
+ }
248
+
249
+ // --- Token renewer (scripted renewal sequence, recorded timers) -------------------------
250
+
251
+ async function handleTokenRenewer(testCase) {
252
+ const script = [...(testCase.script ?? [])];
253
+ const timers = [];
254
+ const logs = [];
255
+ const persisted = [];
256
+ const now = steppedNow(testCase.nowStartMs, testCase.nowStepMs);
257
+ const renewer = createRunnerTokenRenewer({
258
+ initialToken: testCase.initialToken,
259
+ tokenExpiresAt: testCase.tokenExpiresAt ?? undefined,
260
+ deps: {
261
+ renew: async () => {
262
+ const step = script.shift();
263
+ if (step === undefined) {
264
+ throw new Error('renewal script exhausted');
265
+ }
266
+ if (step.result === 'reject') {
267
+ throw new Error(step.message);
268
+ }
269
+ if (step.result === 'renewed') {
270
+ return {
271
+ kind: 'renewed',
272
+ accessToken: step.accessToken,
273
+ expiresInSeconds: step.expiresInSeconds ?? undefined,
274
+ };
275
+ }
276
+ return { kind: step.result };
277
+ },
278
+ persist:
279
+ testCase.persistFails === true
280
+ ? () => {
281
+ throw new Error(testCase.persistFailureMessage);
282
+ }
283
+ : (args) => {
284
+ persisted.push({
285
+ accessToken: args.accessToken,
286
+ expiresInSeconds: args.expiresInSeconds ?? null,
287
+ });
288
+ },
289
+ log: (event, payload) => {
290
+ logs.push({ event, payload });
291
+ },
292
+ now,
293
+ // Timers are RECORDED, never fired: the parity fixture drives
294
+ // renewNow() explicitly so both impls observe the same schedule.
295
+ setTimer: (_callback, delayMs) => {
296
+ timers.push(delayMs);
297
+ return timers.length;
298
+ },
299
+ clearTimer: () => {},
300
+ },
301
+ });
302
+ renewer.start();
303
+ const results = [];
304
+ const renewCalls = testCase.renewCalls ?? (testCase.script ?? []).length;
305
+ for (let call = 0; call < renewCalls; call += 1) {
306
+ results.push(await outcomeAsync(() => renewer.renewNow()));
307
+ }
308
+ const overlap = [];
309
+ if (testCase.probeOverlap === true) {
310
+ // Fire two renewNow calls into the same in-flight renewal: the second
311
+ // must observe in_progress.
312
+ const first = renewer.renewNow();
313
+ const second = await renewer.renewNow();
314
+ overlap.push(await outcomeAsync(() => first), { defined: true, value: second });
315
+ }
316
+ renewer.stop();
317
+ return {
318
+ results,
319
+ overlap,
320
+ currentToken: renewer.currentToken(),
321
+ timers,
322
+ logs,
323
+ persisted,
324
+ };
325
+ }
326
+
327
+ // --- HTTP flow legs (against the scripted auth server) ----------------------------------
328
+
329
+ async function handleValidate(testCase) {
330
+ return outcomeAsync(() =>
331
+ validateLocalRunnerTokenOutcome({
332
+ kandanUrl: testCase.kandanUrl,
333
+ accessToken: testCase.accessToken,
334
+ workspaceSlug: testCase.workspaceSlug ?? undefined,
335
+ channelSlug: testCase.channelSlug ?? undefined,
336
+ requiredScopes: testCase.requiredScopes ?? undefined,
337
+ })
338
+ );
339
+ }
340
+
341
+ async function handleRenew(testCase) {
342
+ return outcomeAsync(() =>
343
+ renewLocalRunnerToken({
344
+ kandanUrl: testCase.kandanUrl,
345
+ accessToken: testCase.accessToken,
346
+ })
347
+ );
348
+ }
349
+
350
+ async function handleDeviceStart(testCase) {
351
+ return outcomeAsync(() =>
352
+ startDeviceAuthorization({
353
+ httpBaseUrl: testCase.httpBaseUrl,
354
+ workspaceSlug: testCase.workspaceSlug ?? undefined,
355
+ channelSlug: testCase.channelSlug ?? undefined,
356
+ onboarding: testCase.onboarding ?? undefined,
357
+ identity: testCase.identity ?? undefined,
358
+ })
359
+ );
360
+ }
361
+
362
+ async function handleDevicePoll(testCase) {
363
+ return outcomeAsync(() =>
364
+ pollDeviceCodeForToken({
365
+ httpBaseUrl: testCase.httpBaseUrl,
366
+ deviceCode: testCase.deviceCode,
367
+ expiresInSeconds: testCase.expiresInSeconds ?? undefined,
368
+ now:
369
+ testCase.nowStartMs === undefined
370
+ ? undefined
371
+ : steppedNow(testCase.nowStartMs, testCase.nowStepMs),
372
+ })
373
+ );
374
+ }
375
+
376
+ async function handleDeviceWaitFlow(testCase) {
377
+ const events = [];
378
+ const logs = [];
379
+ const sleeps = [];
380
+ const result = await outcomeAsync(() =>
381
+ waitForDeviceAuthorization({
382
+ kandanUrl: testCase.kandanUrl,
383
+ workspaceSlug: testCase.workspaceSlug ?? undefined,
384
+ channelSlug: testCase.channelSlug ?? undefined,
385
+ onboarding: testCase.onboarding ?? undefined,
386
+ identity: testCase.identity ?? undefined,
387
+ isInteractive: testCase.isInteractive,
388
+ deps: {
389
+ now: steppedNow(testCase.nowStartMs, testCase.nowStepMs),
390
+ sleep: async (ms) => {
391
+ sleeps.push(ms);
392
+ },
393
+ report: (event) => {
394
+ events.push(event);
395
+ },
396
+ log: (event, payload) => {
397
+ logs.push({ event, payload });
398
+ },
399
+ },
400
+ })
401
+ );
402
+ return { result, events, logs, sleeps };
403
+ }
404
+
405
+ // The device acquisition composition resolveTokenFlow injects: the REAL
406
+ // persistent wait + the REAL cache write, minus the machine-identity read
407
+ // (which would touch the real ~/.linzumi; identity parity is pinned by the
408
+ // cluster 2 fingerprint cases).
409
+ function deviceAcquire(testCase, authFilePath, waitEvents) {
410
+ return async (args) => {
411
+ const token = await waitForDeviceAuthorization({
412
+ kandanUrl: args.kandanUrl,
413
+ workspaceSlug: args.workspaceSlug,
414
+ channelSlug: args.channelSlug,
415
+ onboarding: args.onboarding,
416
+ isInteractive: false,
417
+ deps: {
418
+ now: steppedNow(testCase.nowStartMs, testCase.nowStepMs),
419
+ sleep: async () => {},
420
+ report: (event) => {
421
+ waitEvents.push(event);
422
+ },
423
+ log: () => {},
424
+ },
425
+ });
426
+ withFixedNow(testCase.nowMs, () =>
427
+ writeCachedLocalRunnerToken({
428
+ kandanUrl: args.kandanUrl,
429
+ accessToken: token.accessToken,
430
+ expiresInSeconds: token.expiresInSeconds,
431
+ authFilePath,
432
+ })
433
+ );
434
+ return token.accessToken;
435
+ };
436
+ }
437
+
438
+ async function handleResolveTokenFlow(testCase) {
439
+ const dir = scratch();
440
+ const authFilePath = join(dir, 'auth.json');
441
+ if (testCase.authFileContents !== undefined) {
442
+ writeFileSync(authFilePath, testCase.authFileContents, 'utf8');
443
+ }
444
+ const reported = [];
445
+ const waitEvents = [];
446
+ const result = await outcomeAsync(() =>
447
+ resolveLocalRunnerToken(
448
+ {
449
+ kandanUrl: testCase.kandanUrl,
450
+ explicitToken: testCase.explicitToken ?? undefined,
451
+ workspaceSlug: testCase.workspaceSlug ?? undefined,
452
+ channelSlug: testCase.channelSlug ?? undefined,
453
+ requiredScopes: testCase.requiredScopes ?? undefined,
454
+ authFilePath,
455
+ reportRejectedCachedToken: (outcomeName) => {
456
+ reported.push(outcomeName);
457
+ },
458
+ },
459
+ {
460
+ readCachedToken: (kandanUrl, path) =>
461
+ withFixedNow(testCase.nowMs, () => readCachedLocalRunnerToken(kandanUrl, path)),
462
+ validateToken: validateLocalRunnerTokenOutcome,
463
+ acquireAndCacheToken: deviceAcquire(testCase, authFilePath, waitEvents),
464
+ }
465
+ )
466
+ );
467
+ let authFileBytes = null;
468
+ try {
469
+ authFileBytes = readFileSync(authFilePath, 'utf8');
470
+ } catch {
471
+ authFileBytes = null;
472
+ }
473
+ return substitute({ result, reported, waitEvents, authFileBytes }, dir, dirPlaceholder);
474
+ }
475
+
476
+ async function handleResolveMcpTokenFlow(testCase) {
477
+ const dir = scratch();
478
+ const authFilePath = join(dir, 'auth.json');
479
+ if (testCase.authFileContents !== undefined) {
480
+ writeFileSync(authFilePath, testCase.authFileContents, 'utf8');
481
+ }
482
+ const waitEvents = [];
483
+ const result = await outcomeAsync(() =>
484
+ resolveMcpLocalRunnerToken(
485
+ {
486
+ kandanUrl: testCase.kandanUrl,
487
+ explicitToken: testCase.explicitToken ?? undefined,
488
+ authFilePath,
489
+ workspaceSlug: testCase.workspaceSlug ?? undefined,
490
+ channelSlug: testCase.channelSlug ?? undefined,
491
+ },
492
+ {
493
+ readCachedToken: (kandanUrl, path) =>
494
+ withFixedNow(testCase.nowMs, () => readCachedLocalRunnerToken(kandanUrl, path)),
495
+ validateTokenOutcome: validateLocalRunnerTokenOutcome,
496
+ resolveToken: (args) =>
497
+ resolveLocalRunnerToken(args, {
498
+ readCachedToken: (kandanUrl, path) =>
499
+ withFixedNow(testCase.nowMs, () => readCachedLocalRunnerToken(kandanUrl, path)),
500
+ validateToken: validateLocalRunnerTokenOutcome,
501
+ acquireAndCacheToken: deviceAcquire(testCase, authFilePath, waitEvents),
502
+ }),
503
+ }
504
+ )
505
+ );
506
+ let authFileBytes = null;
507
+ try {
508
+ authFileBytes = readFileSync(authFilePath, 'utf8');
509
+ } catch {
510
+ authFileBytes = null;
511
+ }
512
+ return substitute({ result, waitEvents, authFileBytes }, dir, dirPlaceholder);
513
+ }
514
+
515
+ // --- MCP ephemeral auth file (cross-impl legs) -------------------------------------------
516
+
517
+ function handleMintMcpAuthFile(testCase) {
518
+ const minted = withFixedNow(testCase.nowMs, () =>
519
+ outcome(() =>
520
+ writeEphemeralMcpAuthFile({
521
+ kandanUrl: testCase.kandanUrl,
522
+ token: testCase.token,
523
+ linzumiAgentMcpToken: testCase.linzumiAgentMcpToken ?? undefined,
524
+ })
525
+ )
526
+ );
527
+ if (minted.threw === true) {
528
+ return { minted };
529
+ }
530
+ const { path, cleanup } = minted.value;
531
+ const directory = join(path, '..');
532
+ const shaped = {
533
+ fileBytes: readFileSync(path, 'utf8'),
534
+ dirMode: fileMode(directory),
535
+ fileMode: fileMode(path),
536
+ readBack: withFixedNow(testCase.nowMs, () =>
537
+ outcome(() => readCachedLocalRunnerToken(testCase.kandanUrl, path))
538
+ ),
539
+ };
540
+ if (testCase.keep === true) {
541
+ // The ReScript suite consumes this real path cross-impl, then removes it.
542
+ shaped.keptPath = path;
543
+ } else {
544
+ cleanup();
545
+ }
546
+ return { minted: { defined: true, value: 'minted' }, ...shaped };
547
+ }
548
+
549
+ function handleReadCachedTokenAtPath(testCase) {
550
+ return withFixedNow(testCase.nowMs, () =>
551
+ outcome(() => readCachedLocalRunnerToken(testCase.kandanUrl, testCase.authFilePath))
552
+ );
553
+ }
554
+
555
+ function handleWriteCachedTokenAtPath(testCase) {
556
+ return withFixedNow(testCase.nowMs, () =>
557
+ outcome(() =>
558
+ writeCachedLocalRunnerToken({
559
+ kandanUrl: testCase.kandanUrl,
560
+ accessToken: testCase.accessToken,
561
+ expiresInSeconds: testCase.expiresInSeconds ?? undefined,
562
+ authFilePath: testCase.authFilePath,
563
+ })
564
+ )
565
+ );
566
+ }
567
+
568
+ function handleRemovePath(testCase) {
569
+ return outcome(() => {
570
+ rmSync(testCase.path, { recursive: true, force: true });
571
+ });
572
+ }
573
+
574
+ // --- Signup client (live against the scripted server) --------------------------------------
575
+
576
+ async function handleSignupClientLive(testCase) {
577
+ const client = createSignupServerClient({ serviceUrl: testCase.serviceUrl });
578
+ switch (testCase.leg) {
579
+ case 'startEmailCode':
580
+ return outcomeAsync(() =>
581
+ client.startEmailCode({
582
+ email: testCase.email,
583
+ mode: testCase.mode,
584
+ selectedTasks: testCase.selectedTasks ?? undefined,
585
+ })
586
+ );
587
+ case 'verifyEmailCode':
588
+ return outcomeAsync(() =>
589
+ client.verifyEmailCode({ pendingId: testCase.pendingId, code: testCase.code })
590
+ );
591
+ case 'validateAuth':
592
+ return outcomeAsync(() => client.validateAuth({ accessToken: testCase.accessToken }));
593
+ default:
594
+ return { threw: true, message: 'unknown signup leg ' + String(testCase.leg) };
595
+ }
596
+ }
597
+
598
+ // --- Dispatch --------------------------------------------------------------------------------
599
+
600
+ async function handle(testCase) {
601
+ switch (testCase.op) {
602
+ case 'accessTokenClaims':
603
+ return handleAccessTokenClaims(testCase);
604
+ case 'deviceAuthFailureMessage':
605
+ return handleDeviceAuthFailureMessage(testCase);
606
+ case 'deviceWaitPromptDecision':
607
+ return handleDeviceWaitPromptDecision(testCase);
608
+ case 'renderDeviceWaitEvent':
609
+ return handleRenderDeviceWaitEvent(testCase);
610
+ case 'unusableCachedTokenNotice':
611
+ return handleUnusableCachedTokenNotice(testCase);
612
+ case 'kandanHttpBaseUrl':
613
+ return handleKandanHttpBaseUrl(testCase);
614
+ case 'comparableServiceUrl':
615
+ return handleComparableServiceUrl(testCase);
616
+ case 'signupAuthMatch':
617
+ return handleSignupAuthMatch(testCase);
618
+ case 'signupAuthFromVerification':
619
+ return handleSignupAuthFromVerification(testCase);
620
+ case 'runnerTokenExpiry':
621
+ return handleRunnerTokenExpiry(testCase);
622
+ case 'tokenRenewer':
623
+ return handleTokenRenewer(testCase);
624
+ case 'validate':
625
+ return handleValidate(testCase);
626
+ case 'renew':
627
+ return handleRenew(testCase);
628
+ case 'deviceStart':
629
+ return handleDeviceStart(testCase);
630
+ case 'devicePoll':
631
+ return handleDevicePoll(testCase);
632
+ case 'deviceWaitFlow':
633
+ return handleDeviceWaitFlow(testCase);
634
+ case 'resolveTokenFlow':
635
+ return handleResolveTokenFlow(testCase);
636
+ case 'resolveMcpTokenFlow':
637
+ return handleResolveMcpTokenFlow(testCase);
638
+ case 'mintMcpAuthFile':
639
+ return handleMintMcpAuthFile(testCase);
640
+ case 'readCachedTokenAtPath':
641
+ return handleReadCachedTokenAtPath(testCase);
642
+ case 'writeCachedTokenAtPath':
643
+ return handleWriteCachedTokenAtPath(testCase);
644
+ case 'removePath':
645
+ return handleRemovePath(testCase);
646
+ case 'signupClientLive':
647
+ return handleSignupClientLive(testCase);
648
+ default:
649
+ return { threw: true, message: 'unknown op ' + String(testCase.op) };
650
+ }
651
+ }
652
+
653
+ let stdin = '';
654
+ process.stdin.setEncoding('utf8');
655
+ for await (const chunk of process.stdin) {
656
+ stdin += chunk;
657
+ }
658
+ const cases = JSON.parse(stdin);
659
+ const results = [];
660
+ // Sequential on purpose: the fixed-Date override and the scripted server's
661
+ // per-route response queues both assume one case at a time.
662
+ for (const testCase of cases) {
663
+ results.push(await handle(testCase));
664
+ }
665
+ process.stdout.write(JSON.stringify(results));
666
+ `;
667
+
668
+ await mkdir(join(packageRoot, '.differential'), { recursive: true });
669
+
670
+ // runner.ts's graph resolves assets/ relative to the bundle at import time
671
+ // (same requirement as build-differential-entry.mjs).
672
+ await mkdir(join(packageRoot, '.differential/assets'), { recursive: true });
673
+ await copyFile(
674
+ join(packageRoot, 'src/assets/linzumi-logo.svg'),
675
+ join(packageRoot, '.differential/assets/linzumi-logo.svg')
676
+ );
677
+
678
+ // Renamed banner import (the build-differential-entry.mjs convention):
679
+ // this bundle keeps source-level identifiers and the commander sources
680
+ // import createRequire themselves - a bare banner name would collide. The
681
+ // banner is needed at all because CJS transitive deps (@inquirer's
682
+ // yoctocolors) require() node builtins at module scope.
683
+ const banner = {
684
+ js: "import { createRequire as __authOracleCreateRequire } from 'node:module';\nconst require = __authOracleCreateRequire(import.meta.url);",
685
+ };
686
+
687
+ await build({
688
+ stdin: {
689
+ contents: driverSource,
690
+ resolveDir: packageRoot,
691
+ sourcefile: 'auth-parity-driver.ts',
692
+ loader: 'ts',
693
+ },
694
+ banner,
695
+ bundle: true,
696
+ platform: 'node',
697
+ target: 'node20',
698
+ format: 'esm',
699
+ outfile: join(packageRoot, '.differential/auth-parity-oracle.mjs'),
700
+ minify: false,
701
+ sourcemap: false,
702
+ legalComments: 'none',
703
+ // runner.ts/mcpServer.ts/signupFlow.ts pull the wider commander graph;
704
+ // the same runtime externals as the differential entry build stay
705
+ // external (installed in this package's node_modules; the oracle only
706
+ // exercises the auth exports).
707
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
708
+ });