@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.
- package/README.md +1 -1
- package/dist/crossHarnessFailover.mjs +6 -0
- package/dist/index.js +431 -431
- package/package.json +1 -1
- package/scripts/build-core-commander-seams-parity-oracle.mjs +586 -0
- package/scripts/build-core-daemon-parity-oracle.mjs +573 -0
- package/scripts/build-core-dispatch-input-queue-parity-oracle.mjs +219 -0
- package/scripts/build-core-fate-ladder-parity-oracle.mjs +316 -0
- package/scripts/build-core-generation-overlap-parity-oracle.mjs +288 -0
- package/scripts/build-core-integration-parity-oracle.mjs +361 -0
- package/scripts/build-core-lifecycle-guards-parity-oracle.mjs +443 -0
- package/scripts/build-core-lock-parity-oracle.mjs +455 -0
- package/scripts/build-core-reconnect-resume-parity-oracle.mjs +256 -0
- package/scripts/build-core-startup-resume-scan-parity-oracle.mjs +296 -0
- package/scripts/build-core-supervisor-parity-oracle.mjs +734 -0
- package/scripts/build-cross-harness-failover-oracle.mjs +349 -0
- package/scripts/build-differential-entry.mjs +21 -1
- package/scripts/build.mjs +40 -1
- package/scripts/f5_live_cross_harness_failover_e2e.mjs +239 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 10, commander core / dispatch input queue).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the per-session
|
|
4
|
+
// dispatch input queue of the ReScript commander port, the sibling of
|
|
5
|
+
// build-pipeline-parity-oracle.mjs (cluster 9, which owns the two recovery
|
|
6
|
+
// watchdog primitives this queue's predicates feed). The oracle bundles the
|
|
7
|
+
// REAL runner.ts exports - createClaudeCodeInputQueue (the streaming-input
|
|
8
|
+
// FIFO with the single-live-turn park/release discipline, the #2589 honest
|
|
9
|
+
// enqueue disposition, the #2590 steer-application tracking, and the
|
|
10
|
+
// BLOCKER A dispatch-confirm watermark seam), claudeCodeTurnStallThresholdMs
|
|
11
|
+
// (the LINZUMI_CLAUDE_TURN_STALL_MS env resolution), and
|
|
12
|
+
// claudeCodeDispatchTerminalFailureReason (the kill-respawn-retry ladder's
|
|
13
|
+
// byte-frozen terminal copy) - behind the cluster 1/2 one-shot BATCH driver
|
|
14
|
+
// (a JSON array of cases on stdin, a JSON array of results on stdout).
|
|
15
|
+
//
|
|
16
|
+
// Byte parity: every trace event (dispatches with their payload bytes,
|
|
17
|
+
// predicate refusals, park positions, watermark commits, steer outcomes and
|
|
18
|
+
// applied resolutions, closed-input errors) is normalized by THIS driver
|
|
19
|
+
// with pinned key order, and delivered message payloads ride the TS message
|
|
20
|
+
// objects VERBATIM (their own key insertion order), so the ReScript side
|
|
21
|
+
// must reproduce field presence AND insertion order exactly.
|
|
22
|
+
//
|
|
23
|
+
// Determinism: the queue owns no timers and no clock - its only
|
|
24
|
+
// nondeterminism is promise scheduling, which the driver serializes by
|
|
25
|
+
// draining the microtask queue after every op (pull resolutions and steer
|
|
26
|
+
// `applied` resolutions land in trace order deterministically). Scripted
|
|
27
|
+
// deps: the isLiveSteerAllowed predicate is a mutable flag flipped via ops;
|
|
28
|
+
// the delivery/wait/watermark callbacks append trace events.
|
|
29
|
+
//
|
|
30
|
+
// Import-time side effects: runner.ts transitively reads
|
|
31
|
+
// src/assets/linzumi-logo.svg relative to the bundle at import time
|
|
32
|
+
// (helloLinzumiProject.ts), so the asset is copied next to the bundle -
|
|
33
|
+
// the build-harness-codex-parity-oracle.mjs convention. Externals match
|
|
34
|
+
// the existing oracles (ws/undici/blessed/claude-agent-sdk stay external;
|
|
35
|
+
// none of them is touched at import time on this driver's paths).
|
|
36
|
+
//
|
|
37
|
+
// SECRETS: the oracle only ever sees the conformance suite's synthetic
|
|
38
|
+
// payloads. The output is a local test artifact (.differential/ is
|
|
39
|
+
// gitignored, never published) and stays unminified for debuggability.
|
|
40
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
41
|
+
import { dirname, join } from 'node:path';
|
|
42
|
+
import { fileURLToPath } from 'node:url';
|
|
43
|
+
import { build } from 'esbuild';
|
|
44
|
+
|
|
45
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
46
|
+
|
|
47
|
+
const driverSource = `
|
|
48
|
+
import {
|
|
49
|
+
claudeCodeDispatchTerminalFailureReason,
|
|
50
|
+
claudeCodeTurnStallThresholdMs,
|
|
51
|
+
createClaudeCodeInputQueue,
|
|
52
|
+
} from './src/runner';
|
|
53
|
+
|
|
54
|
+
// Deterministic promise settlement: every queue-internal resolution is
|
|
55
|
+
// synchronous, so a bounded microtask drain after each op flushes every
|
|
56
|
+
// pending .then into the trace in resolution order.
|
|
57
|
+
async function drainMicrotasks() {
|
|
58
|
+
for (let tick = 0; tick < 8; tick += 1) {
|
|
59
|
+
await Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runInputQueueCase(caseInput) {
|
|
64
|
+
const trace = [];
|
|
65
|
+
let liveSteerAllowed = true;
|
|
66
|
+
let steerCount = 0;
|
|
67
|
+
let pullCount = 0;
|
|
68
|
+
const queue = createClaudeCodeInputQueue({
|
|
69
|
+
content: caseInput.content,
|
|
70
|
+
sourceSeq: caseInput.sourceSeq ?? undefined,
|
|
71
|
+
onInputDelivered: () => trace.push({ t: 'inputDelivered' }),
|
|
72
|
+
isLiveSteerAllowed:
|
|
73
|
+
caseInput.liveSteerMode === 'scripted' ? () => liveSteerAllowed : undefined,
|
|
74
|
+
onInputWait: (currentSourceSeq) =>
|
|
75
|
+
trace.push({ t: 'inputWait', seq: currentSourceSeq ?? null }),
|
|
76
|
+
onSourceSeqDelivered: (sourceSeq) => trace.push({ t: 'watermark', seq: sourceSeq }),
|
|
77
|
+
});
|
|
78
|
+
const iterator = queue.messages[Symbol.asyncIterator]();
|
|
79
|
+
for (const op of caseInput.ops ?? []) {
|
|
80
|
+
if (op.op === 'enqueue') {
|
|
81
|
+
try {
|
|
82
|
+
const disposition = queue.enqueue({
|
|
83
|
+
content: op.content,
|
|
84
|
+
sourceSeq: op.sourceSeq ?? undefined,
|
|
85
|
+
});
|
|
86
|
+
trace.push({
|
|
87
|
+
t: 'enqueue',
|
|
88
|
+
result:
|
|
89
|
+
disposition.disposition === 'parked'
|
|
90
|
+
? { disposition: 'parked', queuePosition: disposition.queuePosition }
|
|
91
|
+
: { disposition: 'delivered' },
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
trace.push({
|
|
95
|
+
t: 'enqueue',
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} else if (op.op === 'enqueueSteer') {
|
|
100
|
+
steerCount += 1;
|
|
101
|
+
const steerId = steerCount;
|
|
102
|
+
try {
|
|
103
|
+
const outcome = queue.enqueueSteer(op.content);
|
|
104
|
+
trace.push({ t: 'steer', steerId, steered: outcome.steered });
|
|
105
|
+
void outcome.applied.then((applied) =>
|
|
106
|
+
trace.push({ t: 'steerApplied', steerId, applied })
|
|
107
|
+
);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
trace.push({
|
|
110
|
+
t: 'steer',
|
|
111
|
+
steerId,
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} else if (op.op === 'next') {
|
|
116
|
+
pullCount += 1;
|
|
117
|
+
const pullId = pullCount;
|
|
118
|
+
trace.push({ t: 'pullStart', pullId });
|
|
119
|
+
void iterator.next().then((result) =>
|
|
120
|
+
trace.push(
|
|
121
|
+
result.done === true
|
|
122
|
+
? { t: 'pulled', pullId, done: true }
|
|
123
|
+
: { t: 'pulled', pullId, done: false, message: result.value }
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
} else if (op.op === 'completeTurn') {
|
|
127
|
+
trace.push({ t: 'completeTurn', completed: queue.completeTurn() ?? null });
|
|
128
|
+
} else if (op.op === 'currentSourceSeq') {
|
|
129
|
+
trace.push({ t: 'currentSourceSeq', seq: queue.currentSourceSeq() ?? null });
|
|
130
|
+
} else if (op.op === 'close') {
|
|
131
|
+
queue.close();
|
|
132
|
+
trace.push({ t: 'closed' });
|
|
133
|
+
} else if (op.op === 'drainUndelivered') {
|
|
134
|
+
trace.push({
|
|
135
|
+
t: 'drained',
|
|
136
|
+
followUps: queue.drainUndeliveredFollowUps().map((followUp) => ({
|
|
137
|
+
message: followUp.message,
|
|
138
|
+
sourceSeq: followUp.sourceSeq ?? null,
|
|
139
|
+
})),
|
|
140
|
+
});
|
|
141
|
+
} else if (op.op === 'setLiveSteerAllowed') {
|
|
142
|
+
liveSteerAllowed = op.value === true;
|
|
143
|
+
}
|
|
144
|
+
await drainMicrotasks();
|
|
145
|
+
}
|
|
146
|
+
await drainMicrotasks();
|
|
147
|
+
return { trace };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runCase(input) {
|
|
151
|
+
switch (input.kind) {
|
|
152
|
+
case 'constants':
|
|
153
|
+
return {
|
|
154
|
+
claudeCodeDispatchTerminalFailureReason,
|
|
155
|
+
};
|
|
156
|
+
case 'turnStallThresholdMs':
|
|
157
|
+
return claudeCodeTurnStallThresholdMs(input.env ?? {});
|
|
158
|
+
case 'inputQueueRun':
|
|
159
|
+
return runInputQueueCase(input);
|
|
160
|
+
default:
|
|
161
|
+
return { error: 'unknown case kind ' + String(input.kind) };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function runBatch() {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
168
|
+
const cases = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
169
|
+
const results = [];
|
|
170
|
+
for (const testCase of cases) {
|
|
171
|
+
results.push(await runCase(testCase));
|
|
172
|
+
}
|
|
173
|
+
process.stdout.write(JSON.stringify(results), () => {
|
|
174
|
+
process.exit(0);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
runBatch().then(
|
|
179
|
+
() => {},
|
|
180
|
+
(error) => {
|
|
181
|
+
process.stderr.write(String(error && error.stack ? error.stack : error) + '\\n');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
const outDir = join(packageRoot, '.differential');
|
|
188
|
+
const outPath = join(outDir, 'core-dispatch-input-queue-parity-oracle.mjs');
|
|
189
|
+
|
|
190
|
+
await mkdir(outDir, { recursive: true });
|
|
191
|
+
// runner.ts transitively reads this asset relative to the bundle at import
|
|
192
|
+
// time (helloLinzumiProject.ts readFileSync), so it must exist next to the
|
|
193
|
+
// oracle bundle.
|
|
194
|
+
await mkdir(join(outDir, 'assets'), { recursive: true });
|
|
195
|
+
await copyFile(
|
|
196
|
+
join(packageRoot, 'src/assets/linzumi-logo.svg'),
|
|
197
|
+
join(outDir, 'assets/linzumi-logo.svg')
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await build({
|
|
201
|
+
stdin: {
|
|
202
|
+
contents: driverSource,
|
|
203
|
+
resolveDir: packageRoot,
|
|
204
|
+
sourcefile: 'core-dispatch-input-queue-parity-driver.ts',
|
|
205
|
+
loader: 'ts',
|
|
206
|
+
},
|
|
207
|
+
bundle: true,
|
|
208
|
+
platform: 'node',
|
|
209
|
+
target: 'node20',
|
|
210
|
+
format: 'esm',
|
|
211
|
+
outfile: outPath,
|
|
212
|
+
minify: false,
|
|
213
|
+
sourcemap: false,
|
|
214
|
+
legalComments: 'none',
|
|
215
|
+
logLevel: 'silent',
|
|
216
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
process.stdout.write(outPath + '\n');
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 10, commander core).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the fate-ladder policy
|
|
4
|
+
// engine of the ReScript commander port, the sibling of
|
|
5
|
+
// build-pipeline-parity-oracle.mjs (cluster 9). The oracle bundles the REAL
|
|
6
|
+
// TS module (src/fateLadder.ts - the F2/D51/D53 policy engine: the policy
|
|
7
|
+
// table, the budget env resolution, the run/history record, the one
|
|
8
|
+
// decision entry point, the hold-dispatch commit, the visible-reason
|
|
9
|
+
// strings, the F4 wire receipts, and the rate-limit reset capture) behind
|
|
10
|
+
// the cluster 1/2 one-shot BATCH driver (a JSON array of cases on stdin, a
|
|
11
|
+
// JSON array of results on stdout).
|
|
12
|
+
//
|
|
13
|
+
// Byte parity: fateLadderWireHistory receipts are returned PRE-STRINGIFIED
|
|
14
|
+
// (the TS object's own key insertion order rides JSON.stringify), and every
|
|
15
|
+
// decision/run is serialized through one canonical shape both sides
|
|
16
|
+
// reproduce field-for-field. Determinism: clocks and budgets are injected
|
|
17
|
+
// per case op, the breaker gate is scripted per decide op (its calls are
|
|
18
|
+
// traced so the consult discipline is pinned too) - nothing here reads wall
|
|
19
|
+
// time or process.env except through case inputs.
|
|
20
|
+
//
|
|
21
|
+
// SECRETS: the oracle only ever sees the conformance suite's synthetic
|
|
22
|
+
// payloads. The output is a local test artifact (.differential/ is
|
|
23
|
+
// gitignored, never published) and stays unminified for debuggability.
|
|
24
|
+
import { mkdir } from 'node:fs/promises';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { build } from 'esbuild';
|
|
28
|
+
|
|
29
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
30
|
+
|
|
31
|
+
const driverSource = `
|
|
32
|
+
import {
|
|
33
|
+
appendFateLadderEntry,
|
|
34
|
+
commitFateLadderFailoverOutcome,
|
|
35
|
+
commitFateLadderHoldDispatch,
|
|
36
|
+
createFateLadderRun,
|
|
37
|
+
crossHarnessFailoverDetail,
|
|
38
|
+
crossHarnessFailoverEnabled,
|
|
39
|
+
crossHarnessFailoverReason,
|
|
40
|
+
crossHarnessFailoverRungSlot,
|
|
41
|
+
decideFateLadderStep,
|
|
42
|
+
defaultFateLadderBudgets,
|
|
43
|
+
fateLadderBudgets,
|
|
44
|
+
fateLadderPolicies,
|
|
45
|
+
fateLadderRungs,
|
|
46
|
+
fateLadderWireHistory,
|
|
47
|
+
inPlaceResumeMaxAttempts,
|
|
48
|
+
maxFateLadderHistoryEntries,
|
|
49
|
+
rateLimitHoldReason,
|
|
50
|
+
rateLimitHoldReasonPrefix,
|
|
51
|
+
rateLimitResetAtMsFromNotification,
|
|
52
|
+
redispatchAfterLocalFailureReason,
|
|
53
|
+
workerRebuildRetryReason,
|
|
54
|
+
} from './src/fateLadder';
|
|
55
|
+
|
|
56
|
+
// Canonical run/decision serialization shared (by construction) with the
|
|
57
|
+
// ReScript conformance: fixed key order, option fields as null.
|
|
58
|
+
function runJson(run) {
|
|
59
|
+
return {
|
|
60
|
+
fate: run.fate,
|
|
61
|
+
startedAtMs: run.startedAtMs,
|
|
62
|
+
rungCursor: run.rungCursor,
|
|
63
|
+
cursorAttempts: run.cursorAttempts,
|
|
64
|
+
recoveryRungsAttempted: run.recoveryRungsAttempted,
|
|
65
|
+
holdCommitted: run.holdCommitted,
|
|
66
|
+
holdDispatched: run.holdDispatched,
|
|
67
|
+
historyTruncated: run.historyTruncated,
|
|
68
|
+
entries: run.entries.map((entry) => ({
|
|
69
|
+
rung: entry.rung,
|
|
70
|
+
outcome: entry.outcome,
|
|
71
|
+
atMs: entry.atMs,
|
|
72
|
+
detail: entry.detail === undefined ? null : entry.detail,
|
|
73
|
+
resetAtMs: entry.resetAtMs === undefined ? null : entry.resetAtMs,
|
|
74
|
+
failover:
|
|
75
|
+
entry.failover === undefined
|
|
76
|
+
? null
|
|
77
|
+
: {
|
|
78
|
+
sourceHarness: entry.failover.sourceHarness,
|
|
79
|
+
targetHarness: entry.failover.targetHarness,
|
|
80
|
+
model: entry.failover.model === undefined ? null : entry.failover.model,
|
|
81
|
+
transpiledItemCount:
|
|
82
|
+
entry.failover.transpiledItemCount === undefined
|
|
83
|
+
? null
|
|
84
|
+
: entry.failover.transpiledItemCount,
|
|
85
|
+
redactedReasoningCount:
|
|
86
|
+
entry.failover.redactedReasoningCount === undefined
|
|
87
|
+
? null
|
|
88
|
+
: entry.failover.redactedReasoningCount,
|
|
89
|
+
},
|
|
90
|
+
})),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function decisionJson(decision) {
|
|
95
|
+
switch (decision.kind) {
|
|
96
|
+
case 'stand':
|
|
97
|
+
return { kind: 'stand', cause: decision.cause };
|
|
98
|
+
case 'execute_rung':
|
|
99
|
+
return { kind: 'execute_rung', rung: decision.rung, run: runJson(decision.run) };
|
|
100
|
+
case 'hold_until_reset':
|
|
101
|
+
return {
|
|
102
|
+
kind: 'hold_until_reset',
|
|
103
|
+
resetAtMs: decision.resetAtMs,
|
|
104
|
+
run: runJson(decision.run),
|
|
105
|
+
};
|
|
106
|
+
case 'breaker_hold':
|
|
107
|
+
return { kind: 'breaker_hold', reason: decision.reason, run: runJson(decision.run) };
|
|
108
|
+
case 'red':
|
|
109
|
+
return { kind: 'red', cause: decision.cause, run: runJson(decision.run) };
|
|
110
|
+
default:
|
|
111
|
+
return { kind: 'unknown' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function budgetsOf(raw) {
|
|
116
|
+
return raw === undefined
|
|
117
|
+
? defaultFateLadderBudgets
|
|
118
|
+
: {
|
|
119
|
+
maxRecoveryRungs: raw.maxRecoveryRungs,
|
|
120
|
+
maxLadderMs: raw.maxLadderMs,
|
|
121
|
+
maxRateLimitHoldMs: raw.maxRateLimitHoldMs,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// One scripted per-turn schedule: ops drive the pure engine exactly the way
|
|
126
|
+
// the session layer would (decide -> store returned run -> dispatch), with
|
|
127
|
+
// the breaker verdict scripted per decide op and every gate call traced.
|
|
128
|
+
function runScheduleCase(caseInput) {
|
|
129
|
+
let current = undefined;
|
|
130
|
+
const steps = [];
|
|
131
|
+
for (const op of caseInput.ops ?? []) {
|
|
132
|
+
if (op.op === 'decide') {
|
|
133
|
+
const breakerCalls = [];
|
|
134
|
+
const scripted = op.breaker ?? { action: 'proceed' };
|
|
135
|
+
const gate = (fate, nowMs) => {
|
|
136
|
+
breakerCalls.push({ fate, nowMs });
|
|
137
|
+
if (scripted.action === 'hold') {
|
|
138
|
+
return { action: 'hold', reason: scripted.reason ?? 'scripted_hold' };
|
|
139
|
+
}
|
|
140
|
+
if (scripted.action === 'canary') {
|
|
141
|
+
return { action: 'canary' };
|
|
142
|
+
}
|
|
143
|
+
return { action: 'proceed' };
|
|
144
|
+
};
|
|
145
|
+
const decision = decideFateLadderStep({
|
|
146
|
+
fate: op.fate ?? undefined,
|
|
147
|
+
run: current,
|
|
148
|
+
nowMs: op.nowMs,
|
|
149
|
+
budgets: budgetsOf(op.budgets),
|
|
150
|
+
breaker: gate,
|
|
151
|
+
grants: {
|
|
152
|
+
inPlaceResume: op.grants?.inPlaceResume === true,
|
|
153
|
+
redispatch: op.grants?.redispatch === true,
|
|
154
|
+
workerRebuild: op.grants?.workerRebuild === true,
|
|
155
|
+
crossHarnessFailover: op.grants?.crossHarnessFailover === true,
|
|
156
|
+
},
|
|
157
|
+
rateLimitResetAtMs: op.resetAtMs ?? undefined,
|
|
158
|
+
failover:
|
|
159
|
+
op.failover === undefined || op.failover === null
|
|
160
|
+
? undefined
|
|
161
|
+
: {
|
|
162
|
+
sourceHarness: op.failover.sourceHarness,
|
|
163
|
+
targetHarness: op.failover.targetHarness,
|
|
164
|
+
model: op.failover.model ?? undefined,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
if (decision.kind !== 'stand') {
|
|
168
|
+
current = decision.run;
|
|
169
|
+
}
|
|
170
|
+
steps.push({ decision: decisionJson(decision), breakerCalls });
|
|
171
|
+
} else if (op.op === 'commitHoldDispatch') {
|
|
172
|
+
if (current === undefined) {
|
|
173
|
+
steps.push({ skipped: true });
|
|
174
|
+
} else {
|
|
175
|
+
current = commitFateLadderHoldDispatch(current, op.nowMs);
|
|
176
|
+
steps.push({ run: runJson(current) });
|
|
177
|
+
}
|
|
178
|
+
} else if (op.op === 'commitFailover') {
|
|
179
|
+
if (current === undefined) {
|
|
180
|
+
steps.push({ skipped: true });
|
|
181
|
+
} else {
|
|
182
|
+
current = commitFateLadderFailoverOutcome(current, {
|
|
183
|
+
outcome: op.outcome,
|
|
184
|
+
atMs: op.nowMs,
|
|
185
|
+
failover: {
|
|
186
|
+
sourceHarness: op.failover.sourceHarness,
|
|
187
|
+
targetHarness: op.failover.targetHarness,
|
|
188
|
+
model: op.failover.model ?? undefined,
|
|
189
|
+
transpiledItemCount: op.failover.transpiledItemCount ?? undefined,
|
|
190
|
+
redactedReasoningCount: op.failover.redactedReasoningCount ?? undefined,
|
|
191
|
+
},
|
|
192
|
+
detail: op.detail ?? undefined,
|
|
193
|
+
});
|
|
194
|
+
steps.push({ run: runJson(current) });
|
|
195
|
+
}
|
|
196
|
+
} else if (op.op === 'append') {
|
|
197
|
+
if (current === undefined) {
|
|
198
|
+
steps.push({ skipped: true });
|
|
199
|
+
} else {
|
|
200
|
+
current = appendFateLadderEntry(current, {
|
|
201
|
+
rung: op.entry.rung,
|
|
202
|
+
outcome: op.entry.outcome,
|
|
203
|
+
atMs: op.entry.atMs,
|
|
204
|
+
detail: op.entry.detail ?? undefined,
|
|
205
|
+
resetAtMs: op.entry.resetAtMs ?? undefined,
|
|
206
|
+
});
|
|
207
|
+
steps.push({ run: runJson(current) });
|
|
208
|
+
}
|
|
209
|
+
} else if (op.op === 'seed') {
|
|
210
|
+
current = createFateLadderRun(op.fate, op.nowMs);
|
|
211
|
+
steps.push({ run: runJson(current) });
|
|
212
|
+
} else if (op.op === 'wire') {
|
|
213
|
+
steps.push({
|
|
214
|
+
wire: JSON.stringify(fateLadderWireHistory(current, op.fate, budgetsOf(op.budgets))),
|
|
215
|
+
});
|
|
216
|
+
} else if (op.op === 'reset') {
|
|
217
|
+
current = undefined;
|
|
218
|
+
steps.push({ reset: true });
|
|
219
|
+
} else {
|
|
220
|
+
steps.push({ error: 'unknown op ' + String(op.op) });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { steps };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function runCase(input) {
|
|
227
|
+
switch (input.kind) {
|
|
228
|
+
case 'constants':
|
|
229
|
+
return {
|
|
230
|
+
fateLadderRungs,
|
|
231
|
+
inPlaceResumeMaxAttempts,
|
|
232
|
+
fateLadderPolicies,
|
|
233
|
+
defaultFateLadderBudgets,
|
|
234
|
+
maxFateLadderHistoryEntries,
|
|
235
|
+
redispatchAfterLocalFailureReason,
|
|
236
|
+
workerRebuildRetryReason,
|
|
237
|
+
rateLimitHoldReasonPrefix,
|
|
238
|
+
crossHarnessFailoverRungSlot,
|
|
239
|
+
crossHarnessFailoverReason,
|
|
240
|
+
crossHarnessFailoverDetailSamples: [
|
|
241
|
+
crossHarnessFailoverDetail({
|
|
242
|
+
sourceHarness: 'codex',
|
|
243
|
+
targetHarness: 'claude_code',
|
|
244
|
+
model: 'parity-model-slug',
|
|
245
|
+
transpiledItemCount: 12,
|
|
246
|
+
redactedReasoningCount: 3,
|
|
247
|
+
}),
|
|
248
|
+
crossHarnessFailoverDetail({
|
|
249
|
+
sourceHarness: 'codex',
|
|
250
|
+
targetHarness: 'claude_code',
|
|
251
|
+
model: undefined,
|
|
252
|
+
}),
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
case 'failoverFlag':
|
|
256
|
+
return crossHarnessFailoverEnabled(input.env ?? {});
|
|
257
|
+
case 'budgets':
|
|
258
|
+
return fateLadderBudgets(input.env ?? {});
|
|
259
|
+
case 'holdReason':
|
|
260
|
+
return rateLimitHoldReason(input.resetAtMs);
|
|
261
|
+
case 'resetFromNotification':
|
|
262
|
+
return rateLimitResetAtMsFromNotification(input.params) ?? null;
|
|
263
|
+
case 'scheduleRun':
|
|
264
|
+
return runScheduleCase(input);
|
|
265
|
+
default:
|
|
266
|
+
return { error: 'unknown case kind ' + String(input.kind) };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function runBatch() {
|
|
271
|
+
const chunks = [];
|
|
272
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
273
|
+
const cases = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
274
|
+
const results = [];
|
|
275
|
+
for (const testCase of cases) {
|
|
276
|
+
results.push(runCase(testCase));
|
|
277
|
+
}
|
|
278
|
+
process.stdout.write(JSON.stringify(results), () => {
|
|
279
|
+
process.exit(0);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
runBatch().then(
|
|
284
|
+
() => {},
|
|
285
|
+
(error) => {
|
|
286
|
+
process.stderr.write(String(error && error.stack ? error.stack : error) + '\\n');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
`;
|
|
291
|
+
|
|
292
|
+
const outDir = join(packageRoot, '.differential');
|
|
293
|
+
const outPath = join(outDir, 'core-fate-ladder-parity-oracle.mjs');
|
|
294
|
+
|
|
295
|
+
await mkdir(outDir, { recursive: true });
|
|
296
|
+
|
|
297
|
+
await build({
|
|
298
|
+
stdin: {
|
|
299
|
+
contents: driverSource,
|
|
300
|
+
resolveDir: packageRoot,
|
|
301
|
+
sourcefile: 'core-fate-ladder-parity-driver.ts',
|
|
302
|
+
loader: 'ts',
|
|
303
|
+
},
|
|
304
|
+
bundle: true,
|
|
305
|
+
platform: 'node',
|
|
306
|
+
target: 'node20',
|
|
307
|
+
format: 'esm',
|
|
308
|
+
outfile: outPath,
|
|
309
|
+
minify: false,
|
|
310
|
+
sourcemap: false,
|
|
311
|
+
legalComments: 'none',
|
|
312
|
+
logLevel: 'silent',
|
|
313
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
process.stdout.write(outPath + '\n');
|