@linzumi/cli 1.0.111 → 1.0.113

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,349 @@
1
+ // F5 (compute-nodes program, Workstream F; plan kandan/plans/
2
+ // 2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md).
3
+ // Relationship: Builds the CROSS-HARNESS FAILOVER ORACLE - the in-process
4
+ // driver of the REAL rung path for the F5 differential-harness acceptance
5
+ // (packages/commander-harness/src/conformance/CrossHarnessFailoverConformance.res).
6
+ //
7
+ // The driver exercises the SHIPPED machinery end to end in one process:
8
+ // the REAL channelSession (attachChannelSession: the fate-ladder consult,
9
+ // the backend-dead trigger, the commit-then-advance receipts, the visible
10
+ // I382 rung state) against a scripted dead-able codex client, with the
11
+ // runner-shaped failover executor wired to the REAL transpilation bridge
12
+ // (src/crossHarnessFailover.mjs: T.2 HarnessCodexTranspiler.exportPage ->
13
+ // T.4 HarnessClaudeRehydration -> the claude session-store seeding into an
14
+ // isolated F5_ORACLE_HOME). The conformance suite then completes the
15
+ // resume leg on the seeded store through the REAL HarnessClaudeAdapter
16
+ // against the scripted SDK double (the T.4 Direction-A mechanism) - the
17
+ // spawned-commander environment cannot host a live claude harness, so the
18
+ // in-process oracle is the honest home for the full "the coding job
19
+ // resumes on the other harness" walk (the cluster-8/T.4 posture).
20
+ //
21
+ // Env knobs (set by the conformance suite):
22
+ // F5_ORACLE_HOME - the isolated claude-store root the bridge seeds
23
+ // F5_ORACLE_CWD - the thread's working directory (store keying)
24
+ // F5_ORACLE_EXECUTOR - 'bridge' (the REAL transpile+seed) | 'fail'
25
+ // (a scripted executor failure: the honest-red leg)
26
+ //
27
+ // Output: ONE JSON report line on stdout:
28
+ // { ladderSteps, failoverLogs, visibleReasons, messageStates,
29
+ // seeded: { targetSessionId, sessionFile, transpiledItemCount,
30
+ // redactedReasoningCount } | null,
31
+ // executorRequest: { model, journalTurnIds, journalItemCount } | null }
32
+ //
33
+ // Determinism: scripted data + milestone polls only; no wall-clock rows.
34
+ // The output is a local test artifact (.differential/ is gitignored).
35
+ import { mkdir } from 'node:fs/promises';
36
+ import { dirname, join } from 'node:path';
37
+ import { fileURLToPath } from 'node:url';
38
+ import { build } from 'esbuild';
39
+
40
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
41
+
42
+ const driverSource = `
43
+ import { attachChannelSession } from './src/channelSession';
44
+ import type { HarnessActivityJournal } from './src/channelSession';
45
+ import type { CodexAppServerClient } from './src/codexAppServer';
46
+ import type { JsonObject, JsonRpcResponse, JsonValue } from './src/protocol';
47
+ import type { PhoenixClient } from './src/phoenix';
48
+ // The REAL transpilation bridge (T.2 export -> T.4 rehydration -> store
49
+ // seeding) - the exact module the runner's production executor loads.
50
+ import { transpileAndSeedClaudeSession } from './src/crossHarnessFailover.mjs';
51
+
52
+ const oracleHome = process.env.F5_ORACLE_HOME;
53
+ const oracleCwd = process.env.F5_ORACLE_CWD;
54
+ const executorKind = process.env.F5_ORACLE_EXECUTOR ?? 'bridge';
55
+ if (!oracleHome || !oracleCwd) {
56
+ throw new Error('F5_ORACLE_HOME and F5_ORACLE_CWD are required');
57
+ }
58
+
59
+ // The rung ships flag-gated; the oracle exercises the flag-ON path.
60
+ process.env.LINZUMI_CROSS_HARNESS_FAILOVER = '1';
61
+
62
+ const ladderSteps: Array<Record<string, unknown>> = [];
63
+ const failoverLogs: Array<{ event: string; payload: Record<string, unknown> }> = [];
64
+ const visibleReasons: string[] = [];
65
+ const messageStates: Array<Record<string, unknown>> = [];
66
+ let executorRequest: Record<string, unknown> | null = null;
67
+ let seeded: Record<string, unknown> | null = null;
68
+ let executorSettled = false;
69
+
70
+ function okResponse(result: JsonValue): JsonRpcResponse {
71
+ return { jsonrpc: '2.0', id: 1, result };
72
+ }
73
+
74
+ // --- The scripted kandan side (minimal PhoenixClient double) ---------------
75
+ const eventCallbacks = new Set<
76
+ (topic: string, event: string, payload: JsonValue) => void
77
+ >();
78
+ const kandan = {
79
+ join: async () => ({ cursor: 0 }),
80
+ unregisterJoin: () => undefined,
81
+ push: async (_topic: string, event: string, payload: JsonObject) => {
82
+ if (event === 'message_state') {
83
+ messageStates.push(payload as Record<string, unknown>);
84
+ if (typeof payload.reason === 'string') {
85
+ visibleReasons.push(payload.reason);
86
+ }
87
+ }
88
+ if (event === 'session:announce') {
89
+ return { status: 'ok', response: { seq: 10 } };
90
+ }
91
+ return { status: 'ok', response: {} };
92
+ },
93
+ pushIfConnected: async (_topic: string, event: string, payload: JsonObject) =>
94
+ kandan.push(_topic, event, payload),
95
+ onControl: () => undefined,
96
+ onEvent: (
97
+ callback: (topic: string, event: string, payload: JsonValue) => void
98
+ ) => {
99
+ eventCallbacks.add(callback);
100
+ },
101
+ onReconnect: () => undefined,
102
+ close: () => undefined,
103
+ } as unknown as PhoenixClient;
104
+ const emit = (topic: string, event: string, payload: JsonValue) => {
105
+ for (const callback of eventCallbacks) callback(topic, event, payload);
106
+ };
107
+
108
+ // --- The scripted dead-able codex backend ----------------------------------
109
+ let turnStartCount = 0;
110
+ const workerState = { closed: false };
111
+ const codex: CodexAppServerClient = {
112
+ request: async (method) => {
113
+ if (workerState.closed) {
114
+ throw new Error('thread Codex worker IPC client is closed');
115
+ }
116
+ switch (method) {
117
+ case 'thread/start':
118
+ case 'thread/resume':
119
+ return okResponse({ thread: { id: 'oracle-codex-thread-1' } });
120
+ case 'turn/start':
121
+ turnStartCount += 1;
122
+ return okResponse({ turn: { id: 'oracle-turn-1' } });
123
+ case 'thread/read':
124
+ return okResponse({ thread: { turns: [] } });
125
+ default:
126
+ throw new Error('unexpected oracle codex request ' + method);
127
+ }
128
+ },
129
+ notify: () => undefined,
130
+ onNotification: () => undefined,
131
+ onRequest: () => undefined,
132
+ isClosed: () => workerState.closed,
133
+ close: () => {
134
+ workerState.closed = true;
135
+ },
136
+ };
137
+
138
+ async function waitFor(predicate: () => boolean, label: string): Promise<void> {
139
+ const deadline = Date.now() + 30_000;
140
+ while (!predicate()) {
141
+ if (Date.now() > deadline) {
142
+ throw new Error('timed out waiting for ' + label);
143
+ }
144
+ await new Promise((resolve) => setTimeout(resolve, 10));
145
+ }
146
+ }
147
+
148
+ async function run(): Promise<void> {
149
+ const token =
150
+ 'fake.' +
151
+ Buffer.from(
152
+ JSON.stringify({ actor_id: 7, actor_username: 'oracle-owner' })
153
+ ).toString('base64url') +
154
+ '.sig';
155
+
156
+ const runtime = await attachChannelSession({
157
+ kandan,
158
+ codex,
159
+ topic: 'local_runner:oracle-f5-failover',
160
+ instanceId: 'instance-oracle-f5',
161
+ options: {
162
+ token,
163
+ runnerId: 'oracle-runner-1',
164
+ cwd: oracleCwd,
165
+ codexBin: 'node',
166
+ launchTui: false,
167
+ channelSession: {
168
+ workspaceSlug: 'oracle-workspace',
169
+ channelSlug: 'oracle-channel',
170
+ listenUser: 'oracle-owner',
171
+ kandanThreadId: 'oracle-kandan-thread-1',
172
+ rootSeq: 60,
173
+ codexThreadId: 'oracle-codex-thread-1',
174
+ // D39: the turn/session spec's model slug, asserted byte-equal on
175
+ // the receipts by the conformance suite.
176
+ model: 'oracle-model-slug',
177
+ streamFlushMs: 0,
178
+ },
179
+ crossHarnessFailover: {
180
+ targetHarness: 'claude_code',
181
+ execute: async (request) => {
182
+ executorRequest = {
183
+ kandanThreadId: request.kandanThreadId,
184
+ codexThreadId: request.codexThreadId ?? null,
185
+ queuedSeq: request.queuedSeq,
186
+ messageBody: request.message.body,
187
+ model: request.model ?? null,
188
+ journalTurnIds: request.activity.turns.map((turn) => turn.turnId),
189
+ journalItemCount: request.activity.itemCount,
190
+ };
191
+ try {
192
+ if (executorKind === 'fail') {
193
+ return { ok: false as const, failure: 'scripted executor failure' };
194
+ }
195
+ const result = await transpileAndSeedClaudeSession({
196
+ activity: request.activity as HarnessActivityJournal,
197
+ codexThreadId: request.codexThreadId ?? 'unknown-codex-session',
198
+ kandanThreadId: request.kandanThreadId,
199
+ cwd: oracleCwd,
200
+ home: oracleHome,
201
+ });
202
+ if (!result.ok) {
203
+ return { ok: false as const, failure: result.failure };
204
+ }
205
+ seeded = {
206
+ targetSessionId: result.targetSessionId,
207
+ sessionFile: result.sessionFile,
208
+ transpiledItemCount: result.transpiledItemCount,
209
+ redactedReasoningCount: result.redactedReasoningCount,
210
+ };
211
+ return {
212
+ ok: true as const,
213
+ transpiledItemCount: result.transpiledItemCount,
214
+ redactedReasoningCount: result.redactedReasoningCount,
215
+ targetSessionId: result.targetSessionId,
216
+ };
217
+ } finally {
218
+ executorSettled = true;
219
+ }
220
+ },
221
+ },
222
+ },
223
+ log: (event: string, payload: Record<string, unknown>) => {
224
+ if (event === 'codex.fate_ladder_step') {
225
+ ladderSteps.push(payload);
226
+ }
227
+ if (event.startsWith('codex.fate_ladder_cross_harness_failover')) {
228
+ failoverLogs.push({ event, payload });
229
+ }
230
+ },
231
+ });
232
+
233
+ // The user's coding-job message -> the turn dispatches on codex.
234
+ emit('chat:oracle-workspace:oracle-channel', 'event', {
235
+ seq: 61,
236
+ type: 'thread.message',
237
+ actor: { kind: 'human', slug: 'oracle-owner' },
238
+ actor_user_id: 7,
239
+ body: 'keep working on the migration',
240
+ payload: { thread_id: 'oracle-kandan-thread-1', reply_to_seq: 60 },
241
+ });
242
+ await waitFor(() => turnStartCount >= 1, 'the codex dispatch');
243
+
244
+ // The recorded activity the failover exports: a realistic little session
245
+ // walk INCLUDING a reasoning item (so redaction accounting is exercised).
246
+ runtime.handleCodexNotification('turn/started', {
247
+ threadId: 'oracle-codex-thread-1',
248
+ turn: { id: 'oracle-turn-1' },
249
+ });
250
+ const items: JsonObject[] = [
251
+ {
252
+ id: 'oracle-item-1',
253
+ type: 'userMessage',
254
+ content: [{ type: 'text', text: 'keep working on the migration' }],
255
+ },
256
+ { id: 'oracle-item-2', type: 'reasoning', summary: [] },
257
+ {
258
+ id: 'oracle-item-3',
259
+ type: 'agentMessage',
260
+ text: 'renamed the column and updated 3 call sites so far',
261
+ },
262
+ ];
263
+ for (const item of items) {
264
+ runtime.handleCodexNotification('item/completed', {
265
+ threadId: 'oracle-codex-thread-1',
266
+ turnId: 'oracle-turn-1',
267
+ item,
268
+ completedAtMs: 1_784_000_000_000,
269
+ });
270
+ }
271
+ await new Promise((resolve) => setTimeout(resolve, 50));
272
+
273
+ // THE BACKEND DIES (the precise F5 trigger: the worker/IPC client is
274
+ // gone), then the infra-kill terminal lands with fate transient_local.
275
+ workerState.closed = true;
276
+ runtime.handleCodexNotification('turn/failed', {
277
+ threadId: 'oracle-codex-thread-1',
278
+ turnId: 'oracle-turn-1',
279
+ reason: 'thread Codex worker exited',
280
+ retryable: true,
281
+ fate: 'transient_local',
282
+ });
283
+
284
+ await waitFor(() => executorSettled, 'the failover executor');
285
+ if (executorKind === 'fail') {
286
+ await waitFor(
287
+ () =>
288
+ messageStates.some(
289
+ (state) =>
290
+ state.status === 'failed' &&
291
+ typeof state.reason === 'string' &&
292
+ (state.reason as string).includes('cross-harness failover')
293
+ ),
294
+ 'the honest failover_failed red'
295
+ );
296
+ }
297
+ // Let the post-executor bookkeeping settle.
298
+ await new Promise((resolve) => setTimeout(resolve, 100));
299
+ await runtime.close();
300
+
301
+ process.stdout.write(
302
+ JSON.stringify({
303
+ ladderSteps,
304
+ failoverLogs,
305
+ visibleReasons,
306
+ messageStates,
307
+ seeded,
308
+ executorRequest,
309
+ turnStartCount,
310
+ }) + '\\n'
311
+ );
312
+ }
313
+
314
+ run().then(
315
+ () => process.exit(0),
316
+ (error) => {
317
+ process.stderr.write(
318
+ String(error && error.stack ? error.stack : error) + '\\n'
319
+ );
320
+ process.exit(1);
321
+ }
322
+ );
323
+ `;
324
+
325
+ const outDir = join(packageRoot, '.differential');
326
+ const outPath = join(outDir, 'cross-harness-failover-oracle.mjs');
327
+
328
+ await mkdir(outDir, { recursive: true });
329
+
330
+ await build({
331
+ stdin: {
332
+ contents: driverSource,
333
+ resolveDir: packageRoot,
334
+ sourcefile: 'cross-harness-failover-driver.ts',
335
+ loader: 'ts',
336
+ },
337
+ bundle: true,
338
+ platform: 'node',
339
+ target: 'node20',
340
+ format: 'esm',
341
+ outfile: outPath,
342
+ minify: false,
343
+ sourcemap: false,
344
+ legalComments: 'none',
345
+ logLevel: 'silent',
346
+ external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
347
+ });
348
+
349
+ process.stdout.write(outPath + '\n');
@@ -133,7 +133,8 @@ const outfile =
133
133
  ? join(packageRoot, '.differential/impl-ts.mjs')
134
134
  : join(packageRoot, `.differential/impl-ts.mutant-${mutation.name}.mjs`);
135
135
 
136
- const mutationHooks = mutation === undefined ? undefined : mutationPlugin(mutation);
136
+ const mutationHooks =
137
+ mutation === undefined ? undefined : mutationPlugin(mutation);
137
138
 
138
139
  await build({
139
140
  entryPoints: [join(packageRoot, 'src/differentialCommanderEntry.ts')],
@@ -158,3 +159,22 @@ await copyFile(
158
159
  join(packageRoot, 'src/assets/linzumi-logo.svg'),
159
160
  join(packageRoot, '.differential/assets/linzumi-logo.svg')
160
161
  );
162
+
163
+ // F5: the cross-harness failover bridge rides next to the bundle exactly as
164
+ // it does in dist/ (the runner dynamic-imports ./crossHarnessFailover.mjs
165
+ // relative to import.meta.url), bundled with the compiled ReScript
166
+ // transpiler artifacts resolved at build time. Unlike the dist build there
167
+ // is NO stub fallback here: the differential harness must exercise the REAL
168
+ // transpile leg, so missing artifacts fail this build loudly.
169
+ await build({
170
+ entryPoints: [join(packageRoot, 'src/crossHarnessFailover.mjs')],
171
+ bundle: true,
172
+ platform: 'node',
173
+ target: 'node20',
174
+ format: 'esm',
175
+ outfile: join(packageRoot, '.differential/crossHarnessFailover.mjs'),
176
+ minify: false,
177
+ sourcemap: false,
178
+ legalComments: 'none',
179
+ banner,
180
+ });
package/scripts/build.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // Spec: plans/2026-05-17-linzumi-cli-node-toolchain-note.md
2
2
  // Relationship: Builds the published CLI package with Node-hosted tooling.
3
- import { copyFile, mkdir, rm } from 'node:fs/promises';
3
+ import { copyFile, mkdir, rm, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { build } from 'esbuild';
@@ -84,6 +84,45 @@ await build({
84
84
  banner,
85
85
  });
86
86
 
87
+ // F5 cross-harness failover bridge (Workstream F task F5): the runner
88
+ // dynamic-imports ./crossHarnessFailover.mjs relative to its own bundle, so
89
+ // dist ships a self-contained sibling bundle with the compiled ReScript
90
+ // transpiler artifacts (packages/linzumi-cli-rescript, built by `rescript`)
91
+ // resolved at build time. When the artifacts are not built in this checkout
92
+ // the publish must not silently ship a broken rung: emit an HONEST stub
93
+ // whose import succeeds but whose executor refuses loudly - the ladder then
94
+ // terminates red-with-receipts exactly as F2 (the flag's rollback posture) -
95
+ // and warn in the build log.
96
+ try {
97
+ await build({
98
+ entryPoints: [join(packageRoot, 'src/crossHarnessFailover.mjs')],
99
+ bundle: true,
100
+ platform: 'node',
101
+ target: 'node20',
102
+ format: 'esm',
103
+ outfile: join(packageRoot, 'dist/crossHarnessFailover.mjs'),
104
+ minify: true,
105
+ sourcemap: false,
106
+ legalComments: 'none',
107
+ banner,
108
+ });
109
+ } catch (error) {
110
+ console.warn(
111
+ '[build] cross-harness failover bridge could not be bundled (are the ' +
112
+ 'linzumi-cli-rescript artifacts built?); shipping the honest refusal ' +
113
+ `stub instead: ${error instanceof Error ? error.message : String(error)}`
114
+ );
115
+ await writeFile(
116
+ join(packageRoot, 'dist/crossHarnessFailover.mjs'),
117
+ 'export async function transpileAndSeedClaudeSession() {\n' +
118
+ ' return {\n' +
119
+ ' ok: false,\n' +
120
+ " failure: 'the cross-harness failover transpiler bridge was not bundled into this build',\n" +
121
+ ' };\n' +
122
+ '}\n'
123
+ );
124
+ }
125
+
87
126
  await mkdir(join(packageRoot, 'dist/assets'), { recursive: true });
88
127
  await copyFile(
89
128
  join(packageRoot, 'src/assets/linzumi-logo.svg'),
@@ -0,0 +1,239 @@
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);