@linzumi/cli 1.0.275 → 1.0.276

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.
Files changed (41) hide show
  1. package/README.md +10 -9
  2. package/dist/{impl-ts/cloud-supervisor.mjs → cloud-supervisor.mjs} +78 -78
  3. package/dist/hosted-coding-account-worker.js +25 -0
  4. package/dist/index.js +1609 -3
  5. package/dist/mcp-server.mjs +174 -168
  6. package/dist/{impl-res/native → native}/darwin/manifest.json +5 -2
  7. package/dist/remote-codex-harness-worker.js +757 -0
  8. package/dist/tcp-tunnel-cli.mjs +25 -69
  9. package/dist/topics-cli.mjs +137 -13
  10. package/package.json +9 -16
  11. package/scripts/build.mjs +33 -356
  12. package/scripts/bump-cli-version.mjs +35 -9
  13. package/scripts/check-package-size-budget.mjs +13 -61
  14. package/dist/impl-res/cloud-supervisor.mjs +0 -281
  15. package/dist/impl-res/index.js +0 -1546
  16. package/dist/impl-res/mcp-server.mjs +0 -461
  17. package/dist/impl-ts/assets/linzumi-logo.svg +0 -1
  18. package/dist/impl-ts/crossHarnessFailover.mjs +0 -14
  19. package/dist/impl-ts/index.js +0 -2617
  20. package/dist/impl-ts/mcp-server.mjs +0 -455
  21. package/dist/impl-ts/native/darwin/manifest.json +0 -21
  22. package/dist/impl-ts/native/darwin/preview-process-identity +0 -0
  23. package/dist/impl-ts/tcp-tunnel-cli.mjs +0 -93
  24. package/scripts/build-differential-entry-impl-res.mjs +0 -64
  25. package/scripts/build-differential-entry.mjs +0 -180
  26. package/scripts/build-onboarding-demo-evidence.mjs +0 -186
  27. package/scripts/build-tui-demo-evidence.mjs +0 -107
  28. package/scripts/diagnose-claude-probe.ts +0 -198
  29. package/scripts/differentialMutations.mjs +0 -114
  30. package/scripts/f5_live_cross_harness_failover_e2e.mjs +0 -239
  31. package/scripts/golden-chaos-harness.mjs +0 -1420
  32. package/scripts/normalize-agent-replay-fixture.ts +0 -281
  33. package/scripts/promote-gstack-claude-smoke-fixture.ts +0 -891
  34. package/scripts/record-agent-replay-fixtures.ts +0 -610
  35. package/scripts/replay-agent-backend-browser.ts +0 -198
  36. package/scripts/replay-agent-backend.ts +0 -1961
  37. package/scripts/replay-agent-fixture.ts +0 -326
  38. package/scripts/typecheck.mjs +0 -42
  39. /package/dist/{impl-res/assets → assets}/linzumi-logo.svg +0 -0
  40. /package/dist/{impl-res/crossHarnessFailover.mjs → crossHarnessFailover.mjs} +0 -0
  41. /package/dist/{impl-res/native → native}/darwin/preview-process-identity +0 -0
@@ -1,198 +0,0 @@
1
- /*
2
- - Date: 2026-07-11
3
- Spec: audit PR #3019, W17 (William seq 870: "SDK startup 15s timeout x2"
4
- while `claude` ran fine when invoked directly).
5
- Relationship: Side-by-side diagnostic for the Claude SDK startup probe.
6
- Runs (1) a DIRECT invocation of the exact `claude` executable the probe's
7
- fallback scan resolves and (2) the real availability probe
8
- (probeClaudeCodeAvailabilityStatus - the same code path the runner runs at
9
- startup), with IDENTICAL environment preparation
10
- (prepareClaudeCodeSpawnEnvironment) and binary resolution, capturing
11
- stderr, exit codes, and timing for both. When the probe fails while the
12
- direct invocation succeeds, the report shows the probe's captured child
13
- stderr tail - the evidence that used to be discarded (the SDK spawns with
14
- stdio 'ignore' for stderr unless a callback is passed).
15
-
16
- Usage:
17
- npx tsx scripts/diagnose-claude-probe.ts [--claude-bin /path/to/claude]
18
-
19
- Exit code 0 always (this is a reporter, not a gate); read the verdict.
20
- */
21
- import { spawn } from 'node:child_process';
22
- import { existsSync } from 'node:fs';
23
- import { homedir } from 'node:os';
24
- import {
25
- findClaudeCodeExecutableFallback,
26
- prepareClaudeCodeSpawnEnvironment,
27
- probeClaudeCodeAvailabilityStatus,
28
- } from '../src/claudeCodeSession';
29
-
30
- const DIRECT_TIMEOUT_MS = 15_000; // mirrors the probe's initializeTimeoutMs
31
-
32
- type DirectResult = {
33
- readonly command: string;
34
- readonly durationMs: number;
35
- readonly exitCode: number | null;
36
- readonly timedOut: boolean;
37
- readonly stdoutTail: string;
38
- readonly stderrTail: string;
39
- readonly spawnError?: string;
40
- };
41
-
42
- function tail(value: string, max = 600): string {
43
- const collapsed = value.replace(/\s+/g, ' ').trim();
44
- return collapsed.slice(-max);
45
- }
46
-
47
- async function runDirect(
48
- executable: string,
49
- args: readonly string[],
50
- env: NodeJS.ProcessEnv
51
- ): Promise<DirectResult> {
52
- const started = Date.now();
53
- return await new Promise((resolvePromise) => {
54
- let stdout = '';
55
- let stderr = '';
56
- let settled = false;
57
- const child = spawn(executable, [...args], { env, windowsHide: true });
58
- const settle = (partial: Partial<DirectResult>): void => {
59
- if (settled) {
60
- return;
61
- }
62
- settled = true;
63
- clearTimeout(deadline);
64
- resolvePromise({
65
- command: `${executable} ${args.join(' ')}`,
66
- durationMs: Date.now() - started,
67
- exitCode: child.exitCode,
68
- timedOut: false,
69
- stdoutTail: tail(stdout),
70
- stderrTail: tail(stderr),
71
- ...partial,
72
- });
73
- };
74
- const deadline = setTimeout(() => {
75
- child.kill('SIGKILL');
76
- settle({ timedOut: true, exitCode: null });
77
- }, DIRECT_TIMEOUT_MS);
78
- child.stdout?.on('data', (chunk: Buffer) => {
79
- stdout += chunk.toString();
80
- });
81
- child.stderr?.on('data', (chunk: Buffer) => {
82
- stderr += chunk.toString();
83
- });
84
- child.on('error', (error) => {
85
- settle({ spawnError: error.message });
86
- });
87
- child.on('close', (code) => {
88
- settle({ exitCode: code });
89
- });
90
- });
91
- }
92
-
93
- async function main(): Promise<void> {
94
- const claudeBinFlagIndex = process.argv.indexOf('--claude-bin');
95
- const claudeBin =
96
- claudeBinFlagIndex === -1
97
- ? undefined
98
- : process.argv[claudeBinFlagIndex + 1];
99
- // A bare `--claude-bin` with no path must fail loudly, not silently run
100
- // as if the flag was never given (Greptile P2, PR #3035).
101
- if (claudeBinFlagIndex !== -1 && claudeBin === undefined) {
102
- console.error('--claude-bin requires a path argument');
103
- process.exit(1);
104
- }
105
-
106
- // IDENTICAL env preparation to both the probe and real sessions: the one
107
- // shared function (merged login-shell PATH).
108
- const env = prepareClaudeCodeSpawnEnvironment();
109
-
110
- // IDENTICAL binary resolution to the probe's fallback scan.
111
- const resolvedFallback = findClaudeCodeExecutableFallback({
112
- env,
113
- platform: process.platform,
114
- fileExists: existsSync,
115
- homeDir: homedir(),
116
- });
117
- const directExecutable = claudeBin ?? resolvedFallback;
118
-
119
- console.log('=== W17 claude probe side-by-side diagnostic ===');
120
- console.log(`cwd: ${process.cwd()}`);
121
- console.log(
122
- `resolved user-installed claude: ${resolvedFallback ?? '(none found on PATH or in ~/.local/bin, ~/.claude/local)'}`
123
- );
124
- console.log(`explicit --claude-bin: ${claudeBin ?? '(not set)'}`);
125
- console.log('');
126
-
127
- // --- Side A: direct invocation of the same binary ---
128
- console.log('--- A: direct invocation ---');
129
- let direct: DirectResult | undefined;
130
- if (directExecutable === undefined) {
131
- console.log('skipped: no claude executable resolved for a direct run');
132
- } else {
133
- direct = await runDirect(directExecutable, ['--version'], env);
134
- console.log(
135
- `direct \`${direct.command}\`: exit=${direct.exitCode} timedOut=${direct.timedOut} in ${direct.durationMs}ms`
136
- );
137
- if (direct.stdoutTail !== '') {
138
- console.log(` stdout: ${direct.stdoutTail}`);
139
- }
140
- if (direct.stderrTail !== '') {
141
- console.log(` stderr: ${direct.stderrTail}`);
142
- }
143
- if (direct.spawnError !== undefined) {
144
- console.log(` spawn error: ${direct.spawnError}`);
145
- }
146
- }
147
- console.log('');
148
-
149
- // --- Side B: the real SDK startup probe (same code path as the runner) ---
150
- console.log(
151
- '--- B: SDK startup probe (probeClaudeCodeAvailabilityStatus) ---'
152
- );
153
- const started = Date.now();
154
- const outcome = await probeClaudeCodeAvailabilityStatus({
155
- cwd: process.cwd(),
156
- claudeBin,
157
- });
158
- const probeMs = Date.now() - started;
159
- console.log(
160
- `probe verdict: status=${outcome.status} transient=${outcome.transient} in ${probeMs}ms`
161
- );
162
- if (outcome.detail !== undefined) {
163
- console.log(`probe failure detail (SDK error + child stderr tail):`);
164
- console.log(` ${outcome.detail}`);
165
- }
166
- console.log('');
167
-
168
- console.log('--- verdict ---');
169
- // The verdict branches on side A's ACTUAL result: "both failed" is a
170
- // uniformly broken setup, not a probe-vs-direct divergence (Greptile P2,
171
- // PR #3035).
172
- const directSucceeded =
173
- direct !== undefined &&
174
- direct.exitCode === 0 &&
175
- !direct.timedOut &&
176
- direct.spawnError === undefined;
177
- if (outcome.status === 'logged_in') {
178
- console.log(
179
- 'probe and direct invocation agree: Claude Code is launchable.'
180
- );
181
- } else if (directSucceeded) {
182
- console.log(
183
- 'DIVERGENCE: the direct invocation succeeded but the probe failed. The failure detail above is the evidence to attach to the issue.'
184
- );
185
- } else if (direct !== undefined) {
186
- console.log(
187
- 'both the direct invocation AND the probe failed - a uniformly broken claude setup on this machine, not a probe divergence. Fix the direct invocation first (its stderr/exit code above).'
188
- );
189
- } else {
190
- console.log(
191
- 'no user-installed claude and the probe failed - see detail above.'
192
- );
193
- }
194
- // Reporter, not a gate.
195
- process.exit(0);
196
- }
197
-
198
- void main();
@@ -1,114 +0,0 @@
1
- // Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md (M2.6)
2
- // Relationship: The maintained seeded-mutation set for the differential
3
- // harness's anti-false-green mutation check. Each entry is a deliberate
4
- // behavior delta patched into the TS commander AT BUILD TIME
5
- // (scripts/build-differential-entry.mjs --mutation <name>); the harness's
6
- // MutationCheckConformance then asserts the differential suite goes RED on
7
- // the mutant (transcript diff for the shape/ordering classes, a named
8
- // ported-invariant violation for the invariant class). A seeded delta the
9
- // suite stays green on means the gate is lying - that conformance leg fails.
10
- //
11
- // This registry is the SINGLE source (derive, don't duplicate): the build
12
- // script applies `patches`, and the harness conformance imports this exact
13
- // module to drive and pin the set (closed classes, one mutation per class).
14
- //
15
- // MAINTENANCE RULES:
16
- // - The set stays small (one mutation per class below) - every entry is a
17
- // permanent CI cost, so each must earn its place by proving a DISTINCT
18
- // sensitivity class of the differential canonical transcript.
19
- // - `find` strings must match the current source EXACTLY ONCE; the build
20
- // fails loudly on zero or multiple matches, so a refactor can never
21
- // silently no-op a mutation (the anchor rot the exactly-once rule
22
- // exists for).
23
- // - Mutations must never remove an event a differential scenario AWAITS
24
- // (the choreography synchronizes on milestones; deleting one turns a
25
- // fast red into a slow timeout red).
26
- // - No current-version literals (the commander-harness package rule).
27
- //
28
- // Classes (the M2.6 task's closed vocabulary):
29
- // - transcript-shape: an event the commander silently stops emitting must
30
- // change the canonical transcript bytes.
31
- // - invariant: a behavior delta violating a ported ordering invariant
32
- // must surface as that invariant's named violation.
33
- // - ordering: a pure reorder of the commander's own send order (same
34
- // event multiset) must change the canonical transcript bytes.
35
-
36
- export const differentialMutations = [
37
- {
38
- name: 'message-state-processing-drop',
39
- class: 'transcript-shape',
40
- scenario: 'harness-session-rebuild',
41
- proves:
42
- 'a silently dropped message-state publish (the msg-state ladder silent-drop class: the user-visible "processing / starting turn" paint never reaches the server) changes the canonical transcript - the differential diff can see a suppressed notification type',
43
- patches: [
44
- {
45
- file: 'src/channelSession.ts',
46
- find: ` const startingConsensus = await publishQueuedMessageState(
47
- args,
48
- state,
49
- next,
50
- {
51
- status: 'processing',
52
- reason: 'starting turn',
53
- },
54
- 'acked'
55
- );`,
56
- replace: ` // [seeded differential mutation: message-state-processing-drop] the
57
- // starting-turn processing paint is silently dropped.
58
- const startingConsensus = { kind: 'applied' } as const;`,
59
- },
60
- ],
61
- },
62
- {
63
- name: 'join-roster-drop',
64
- class: 'invariant',
65
- expectInvariant: 'I319',
66
- scenario: 'full-join-surfaces',
67
- proves:
68
- 'a commander that silently drops the join-reply roster (the Commander-boots-into-empty-onboarding regression class the compute-nodes program exists to kill) under-delivers the display page and goes red as an I319 violation, not just a byte diff',
69
- patches: [
70
- {
71
- file: 'src/runner.ts',
72
- find: ` seedDashboardRosterFromReply(joinResponse, 'join');`,
73
- replace: ` // [seeded differential mutation: join-roster-drop] the join-reply
74
- // roster seed is silently dropped.`,
75
- },
76
- ],
77
- },
78
- {
79
- name: 'handshake-initialized-reorder',
80
- class: 'ordering',
81
- scenario: 'harness-clean-turn',
82
- proves:
83
- "a pure reorder of the commander's own send order (the codex app-server handshake: `initialized` notified BEFORE the `initialize` request instead of after its result) changes the canonical transcript even though the event multiset is identical - per-party send order is preserved evidence, not normalized away",
84
- patches: [
85
- {
86
- file: 'src/codexAppServer.ts',
87
- // The early frame below is byte-for-byte the wire shape of the
88
- // pristine `initialized` literal in codexAppServer.ts, so this
89
- // mutation stays a PURE reorder. Drift teeth (Greptile finding on
90
- // #3115): if the real notification ever gains fields, the early
91
- // frame's content diverges and MutationCheckConformance's
92
- // ordering-class assertion (equal sorted-line multiset) goes red
93
- // until this patch is re-synced - the payload cannot silently rot.
94
- find: ` const response = await sendRequest(websocket, pending, {
95
- jsonrpc: '2.0',
96
- id: 'initialize',
97
- method: 'initialize',`,
98
- replace: ` // [seeded differential mutation: handshake-initialized-reorder] the
99
- // initialized notification jumps AHEAD of the initialize request.
100
- websocket.send(JSON.stringify({ jsonrpc: '2.0', method: 'initialized' }));
101
- const response = await sendRequest(websocket, pending, {
102
- jsonrpc: '2.0',
103
- id: 'initialize',
104
- method: 'initialize',`,
105
- },
106
- {
107
- file: 'src/codexAppServer.ts',
108
- find: ` websocket.send(JSON.stringify(initialized));`,
109
- replace: ` // [seeded differential mutation: handshake-initialized-reorder] the
110
- // post-result send is suppressed (it already went out early above).`,
111
- },
112
- ],
113
- },
114
- ];
@@ -1,239 +0,0 @@
1
- #!/usr/bin/env node
2
- // F5 LIVE real-harness e2e (compute-nodes program, Workstream F): the
3
- // cross-harness failover RUNG PATH against the REAL claude-code harness -
4
- // no scripted double anywhere on the target side.
5
- //
6
- // 1. The failover ORACLE (the same bundle the conformance suite runs:
7
- // scripts/build-cross-harness-failover-oracle.mjs) drives the REAL
8
- // channelSession: a codex thread runs, its backend DIES mid-thread
9
- // (the worker/IPC client closes - the precise F5 trigger), the fate
10
- // ladder decides the cross_harness_failover rung, and the REAL
11
- // transpilation bridge (T.2 exportPage -> T.4 rehydrateSession)
12
- // seeds the REAL claude session store (~/.claude/projects/...).
13
- // 2. The REAL @anthropic-ai/claude-agent-sdk validates the seeded
14
- // session (getSessionInfo), reads the transpiled history back
15
- // (getSessionMessages), and RESUMES it (query with options.resume),
16
- // completing a REAL follow-up turn whose answer must reference
17
- // content that exists ONLY in the failed codex thread's transpiled
18
- // activity (the coherence probe).
19
- //
20
- // This is a LOCAL, manual, credentialed run (it spends one real model
21
- // turn); it is NOT in any CI test list. Evidence lands in
22
- // /tmp/linzumi-evidence-f5/ and is mirrored into the committed evidence
23
- // doc (kandan/server_v2/web/docs/qa-evidence/2026-07-14-f5-failover/).
24
- //
25
- // Scope honesty (the T.4 precedent, extended): there is deliberately NO
26
- // deployed-preview leg - the failover's kandan/codex sides ride the
27
- // oracle's scripted doubles (killing a real production backend mid-thread
28
- // on the preview is not an exercisable CI act), while the LADDER, the
29
- // TRANSPILERS, the SESSION STORE, and the CLAUDE HARNESS are all real.
30
-
31
- import { execFileSync } from 'node:child_process';
32
- import { randomUUID } from 'node:crypto';
33
- import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
34
- import { homedir } from 'node:os';
35
- import { dirname, join, resolve } from 'node:path';
36
- import { fileURLToPath } from 'node:url';
37
-
38
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
39
- const evidenceDir = '/tmp/linzumi-evidence-f5';
40
- mkdirSync(evidenceDir, { recursive: true });
41
-
42
- function fail(message) {
43
- console.error(`F5-LIVE-E2E FAIL: ${message}`);
44
- process.exit(1);
45
- }
46
-
47
- // --- 1. The REAL rung path: oracle -> ladder -> bridge -> REAL store ---------
48
-
49
- execFileSync(
50
- process.execPath,
51
- [join(packageRoot, 'scripts', 'build-cross-harness-failover-oracle.mjs')],
52
- { stdio: 'inherit' }
53
- );
54
-
55
- const cwdRaw = join('/tmp', `linzumi-f5-live-${Date.now()}`);
56
- mkdirSync(cwdRaw, { recursive: true });
57
- const cwd = realpathSync(cwdRaw);
58
-
59
- const reportRaw = execFileSync(
60
- process.execPath,
61
- [join(packageRoot, '.differential', 'cross-harness-failover-oracle.mjs')],
62
- {
63
- env: {
64
- ...process.env,
65
- // The REAL store: the bridge seeds ~/.claude/projects/<munged-cwd>/.
66
- F5_ORACLE_HOME: homedir(),
67
- F5_ORACLE_CWD: cwd,
68
- F5_ORACLE_EXECUTOR: 'bridge',
69
- },
70
- encoding: 'utf8',
71
- }
72
- );
73
- const report = JSON.parse(reportRaw.trim());
74
-
75
- const failoverStep = (report.ladderSteps ?? []).find(
76
- (step) => step.rung === 'cross_harness_failover'
77
- );
78
- if (!failoverStep) fail('the ladder never decided the failover rung');
79
- if (failoverStep.fate !== 'transient_local') {
80
- fail(`unexpected fate: ${failoverStep.fate}`);
81
- }
82
- const dispatchedLog = (report.failoverLogs ?? []).find(
83
- (log) => log.event === 'codex.fate_ladder_cross_harness_failover_dispatched'
84
- );
85
- if (!dispatchedLog) fail('no failover_dispatched receipt log');
86
- if (dispatchedLog.payload.model !== 'oracle-model-slug') {
87
- fail('the receipts did not carry the explicitly-passed model (D39)');
88
- }
89
- if (!report.seeded) fail('the bridge seeded no session store');
90
- const { targetSessionId, sessionFile, transpiledItemCount } = report.seeded;
91
- console.log('[f5-live] rung decided + REAL store seeded:', sessionFile);
92
- console.log(
93
- '[f5-live] transpiled items:',
94
- transpiledItemCount,
95
- '(redacted reasoning:',
96
- report.seeded.redactedReasoningCount + ')'
97
- );
98
-
99
- writeFileSync(
100
- join(evidenceDir, 'f5-live-oracle-report.json'),
101
- JSON.stringify(report, null, 2) + '\n'
102
- );
103
-
104
- // The seeded store must contain the recap marker + the journal content.
105
- const storeLines = readFileSync(sessionFile, 'utf8')
106
- .trim()
107
- .split('\n')
108
- .map((line) => JSON.parse(line));
109
- const storeText = JSON.stringify(storeLines);
110
- if (!storeText.includes('[linzumi cross-harness resume]')) {
111
- fail('the seeded store carries no cross-harness resume marker');
112
- }
113
- if (!storeText.includes('renamed the column and updated 3 call sites')) {
114
- fail('the seeded store lost the transpiled codex activity');
115
- }
116
-
117
- // --- 2. The REAL SDK: validate, read back, RESUME, follow-up turn ------------
118
-
119
- const sdk = await import('@anthropic-ai/claude-agent-sdk');
120
-
121
- // Never run the harness as a nested claude-code session.
122
- const env = { ...process.env };
123
- delete env.CLAUDECODE;
124
- delete env.CLAUDE_CODE_ENTRYPOINT;
125
- delete env.CLAUDE_CODE_SSE_PORT;
126
-
127
- const info = await sdk.getSessionInfo(targetSessionId, { dir: cwd });
128
- if (!info) fail('the real SDK store did not find the seeded session');
129
- console.log('[f5-live] getSessionInfo found the failover-seeded session');
130
-
131
- const storedMessages = await sdk.getSessionMessages(targetSessionId, {
132
- dir: cwd,
133
- });
134
- if (storedMessages.length !== storeLines.length) {
135
- fail(
136
- `store read-back count ${storedMessages.length} != seeded ${storeLines.length}`
137
- );
138
- }
139
- console.log(
140
- '[f5-live] getSessionMessages returned the seeded history:',
141
- storedMessages.length,
142
- 'messages'
143
- );
144
-
145
- // The coherence probe: answerable ONLY from the transpiled codex activity
146
- // that crossed with the failover.
147
- const probe =
148
- 'Answer from this conversation only, in one short sentence: before the ' +
149
- 'harness failover, how many call sites did the agent say it had already ' +
150
- 'updated after renaming the column?';
151
-
152
- const transcript = [];
153
- const query = sdk.query({
154
- prompt: probe,
155
- options: {
156
- cwd,
157
- resume: targetSessionId,
158
- model: 'haiku',
159
- permissionMode: 'default',
160
- allowedTools: [],
161
- maxTurns: 1,
162
- persistSession: true,
163
- env,
164
- },
165
- });
166
-
167
- let announcedSessionId = null;
168
- let resultMessage = null;
169
- let assistantText = '';
170
- for await (const message of query) {
171
- transcript.push(message);
172
- if (message.type === 'system' && message.subtype === 'init') {
173
- announcedSessionId = message.session_id;
174
- console.log('[f5-live] system:init announced session:', announcedSessionId);
175
- }
176
- if (message.type === 'assistant') {
177
- const blocks = message.message?.content ?? [];
178
- for (const block of blocks) {
179
- if (block.type === 'text') assistantText += block.text + '\n';
180
- }
181
- }
182
- if (message.type === 'result') {
183
- resultMessage = message;
184
- break;
185
- }
186
- }
187
-
188
- writeFileSync(
189
- join(evidenceDir, 'f5-live-resume-transcript.ndjson'),
190
- transcript.map((message) => JSON.stringify(message)).join('\n') + '\n'
191
- );
192
-
193
- if (!resultMessage) fail('the follow-up turn produced no result terminal');
194
- if (resultMessage.is_error) {
195
- fail(`the follow-up turn errored: ${JSON.stringify(resultMessage)}`);
196
- }
197
- console.log('[f5-live] follow-up turn completed:', resultMessage.subtype);
198
- console.log('[f5-live] follow-up answer:', assistantText.trim());
199
-
200
- const coherent = /\b(3|three)\b/i.test(assistantText);
201
- if (!coherent) {
202
- fail(
203
- 'the follow-up answer does not reference the transpiled codex activity ' +
204
- '(expected the 3 updated call sites) - the resume did not carry the ' +
205
- 'failed thread across the harness boundary'
206
- );
207
- }
208
- console.log(
209
- '[f5-live] COHERENCE PROVED: the answer references the 3 updated call ' +
210
- 'sites, which exist only in the transpiled codex activity'
211
- );
212
-
213
- const summary = {
214
- ran_at: new Date().toISOString(),
215
- trigger:
216
- 'transient_local infra kill on a DEAD codex worker (the oracle-scripted backend), redispatch/worker_rebuild unavailable',
217
- flag: 'LINZUMI_CROSS_HARNESS_FAILOVER=1 (default OFF)',
218
- ladder_decision: failoverStep,
219
- failover_receipt: dispatchedLog.payload,
220
- source_harness: 'codex',
221
- target_harness: 'claude_code',
222
- target_session_id: targetSessionId,
223
- announced_session_id: announcedSessionId,
224
- session_file: sessionFile,
225
- transpiled_item_count: transpiledItemCount,
226
- redacted_reasoning_count: report.seeded.redactedReasoningCount,
227
- follow_up_result_subtype: resultMessage.subtype,
228
- follow_up_answer: assistantText.trim(),
229
- coherence_probe: probe,
230
- coherence_proved: true,
231
- scope_honesty:
232
- 'kandan/codex sides scripted by the oracle doubles; the ladder, the T transpilers, the session store, and the claude harness (SDK resume + real model turn) are all real. No deployed-preview leg (killing a real backend mid-thread on the preview is not an exercisable CI act).',
233
- };
234
- writeFileSync(
235
- join(evidenceDir, 'f5-live-failover-summary.json'),
236
- JSON.stringify(summary, null, 2) + '\n'
237
- );
238
- console.log('[f5-live] PASS - evidence in', evidenceDir);
239
- process.exit(0);