@linzumi/cli 1.0.256 → 1.0.258
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/impl-res/cloud-supervisor.mjs +83 -83
- package/dist/impl-res/index.js +262 -258
- package/dist/impl-ts/cloud-supervisor.mjs +83 -83
- package/dist/impl-ts/index.js +501 -501
- package/package.json +1 -1
- package/scripts/build-core-commander-seams-parity-oracle.mjs +110 -0
- package/scripts/build-core-integration-parity-oracle.mjs +69 -1
- package/scripts/build-protocol-parity-oracle.mjs +8 -7
- package/scripts/build-queue-parity-oracle.mjs +96 -1
package/package.json
CHANGED
|
@@ -78,6 +78,14 @@ import {
|
|
|
78
78
|
staleStartInstanceAgeMs,
|
|
79
79
|
staleStartInstanceWindowMs,
|
|
80
80
|
} from './src/runner';
|
|
81
|
+
import {
|
|
82
|
+
registerRequiredAdtCandidate, acknowledgeRequiredAdtCandidate,
|
|
83
|
+
consumeRequiredAdtAdmission, closeRequiredAdtAdmissionForDisconnect,
|
|
84
|
+
reconcileRequiredAdtCandidates, requiredTransportFinalAdmission,
|
|
85
|
+
findRequiredAdtCandidate, releaseRequiredAdtCandidate,
|
|
86
|
+
recoverRetainedTerminalDebt,
|
|
87
|
+
} from './src/channelSession';
|
|
88
|
+
import {adoptAdtStreamBinding, installAdtPublicationBinding} from './src/pipeline/contracts';
|
|
81
89
|
|
|
82
90
|
// --- Placeholder + snapshot plumbing --------------------------------------------
|
|
83
91
|
|
|
@@ -524,8 +532,110 @@ async function handleRespawn(testCase) {
|
|
|
524
532
|
|
|
525
533
|
// --- Dispatch --------------------------------------------------------------------------------
|
|
526
534
|
|
|
535
|
+
// Spec: spec/commander-recovery-rules.md rule01 and rule05; retained terminal
|
|
536
|
+
// debt is not an unknown provider-start ACK and cannot reset unrelated jobs.
|
|
537
|
+
async function terminalDebtStorm(testCase) {
|
|
538
|
+
const binding = {session_id: 3600, lease_epoch: 1, next_frame_index: 39,
|
|
539
|
+
checkpoint_config: {version: 3, thresholds: {max_bytes: 1, max_deltas: 1, max_interval_ms: 2000}}};
|
|
540
|
+
const adopted = adoptAdtStreamBinding(binding, 44, 0);
|
|
541
|
+
if (adopted.kind !== 'accepted') throw new Error('terminal debt fixture binding refused');
|
|
542
|
+
const session = adopted.session;
|
|
543
|
+
const logs = [];
|
|
544
|
+
let resets = 0;
|
|
545
|
+
let restarts = 0;
|
|
546
|
+
let reconciliations = 0;
|
|
547
|
+
const transport = {
|
|
548
|
+
push: async (_topic, event) => {
|
|
549
|
+
if (event === 'adt_binding_ack') return {status: 'ok', response: {ok: true}};
|
|
550
|
+
if (event === 'adt_binding_reconcile') {
|
|
551
|
+
reconciliations += 1;
|
|
552
|
+
if(testCase.control === 'repair-unavailable' && reconciliations > 1) return {status:'ok',response:{ok:true,adt:{verdict:'still_unavailable'}}};
|
|
553
|
+
return {status: 'ok', response: {
|
|
554
|
+
ok: true, adt: {verdict: 'already_applied', binding: testCase.control === 'malformed-binding' ? {session_id: 3600} : binding},
|
|
555
|
+
}};
|
|
556
|
+
}
|
|
557
|
+
throw new Error('unexpected terminal-debt fixture event: ' + event);
|
|
558
|
+
},
|
|
559
|
+
hardReset: () => {resets += 1; return true;},
|
|
560
|
+
};
|
|
561
|
+
const candidate = registerRequiredAdtCandidate({transport,
|
|
562
|
+
identity: {thread_id: 'synthetic-terminal-debt', source_seq: 44, session_id: 3600,
|
|
563
|
+
lease_epoch: 1, checkpoint_config_version: 3},
|
|
564
|
+
installFreshBinding: raw => {
|
|
565
|
+
const fresh = adoptAdtStreamBinding(raw, 44, 0);
|
|
566
|
+
if (fresh.kind !== 'accepted') return false;
|
|
567
|
+
if (testCase.localState === 'idle') return false;
|
|
568
|
+
return installAdtPublicationBinding(session, fresh.session.binding, true).kind === 'accepted';
|
|
569
|
+
},
|
|
570
|
+
publicationSession: () => testCase.localState === 'idle' ? undefined : session,
|
|
571
|
+
failedTerminalEntries: () => {
|
|
572
|
+
const entries = [{entrySeq: 61, kind: 'adt.terminal_attempt',
|
|
573
|
+
data: {seq: 44, status: 'processed', turn_id: 'synthetic-completed-turn',
|
|
574
|
+
terminal_frame: {session_id: testCase.control === 'foreign-debt' ? 3601 : 3600, lease_epoch: 1, frame_index: 40,
|
|
575
|
+
frame_kind: 'terminal', assistant_phase: 'final_answer',
|
|
576
|
+
source_stream_key: 'synthetic-rejected-key', body: 'Synthetic completed answer.'}}}];
|
|
577
|
+
if (testCase.control === 'missing-debt') return [];
|
|
578
|
+
if (testCase.control === 'ambiguous-debt') return [...entries, {...entries[0], entrySeq: 62}];
|
|
579
|
+
return entries;
|
|
580
|
+
},
|
|
581
|
+
onUnrecoverable: () => {restarts += 1;},
|
|
582
|
+
log: event => logs.push(event),
|
|
583
|
+
});
|
|
584
|
+
try {
|
|
585
|
+
await acknowledgeRequiredAdtCandidate({topic: 'local_runner:fixture', payload: adopted.ackPayload, ticket: candidate});
|
|
586
|
+
if (testCase.control !== 'unconsumed-provider' && consumeRequiredAdtAdmission(candidate).kind !== 'admitted') throw new Error('fixture provider was not admitted');
|
|
587
|
+
session.emitter.fenced = true;
|
|
588
|
+
session.done = true;
|
|
589
|
+
for (let reconnect = 0; reconnect < 3; reconnect += 1) {
|
|
590
|
+
closeRequiredAdtAdmissionForDisconnect(transport);
|
|
591
|
+
await reconcileRequiredAdtCandidates({transport, topic: 'local_runner:fixture'});
|
|
592
|
+
}
|
|
593
|
+
return {resets, restarts, retained: findRequiredAdtCandidate(transport,
|
|
594
|
+
identity => identity.thread_id === 'synthetic-terminal-debt' && identity.source_seq === 44) === candidate,
|
|
595
|
+
providerEffect: candidate.providerEffect, verdict: candidate.verdict,
|
|
596
|
+
nextProviderAdmission: consumeRequiredAdtAdmission(candidate).kind,
|
|
597
|
+
admission: requiredTransportFinalAdmission(transport).kind, logs};
|
|
598
|
+
} finally {releaseRequiredAdtCandidate(candidate);}
|
|
599
|
+
}
|
|
600
|
+
|
|
527
601
|
async function handle(testCase) {
|
|
528
602
|
switch (testCase.op) {
|
|
603
|
+
case 'terminalDebtCold': {
|
|
604
|
+
const events = [];
|
|
605
|
+
let repairs = 0;
|
|
606
|
+
const identity = {thread_id:'synthetic-terminal-debt',source_seq:44,session_id:3600,lease_epoch:1,checkpoint_config_version:7};
|
|
607
|
+
const binding = {session_id:3600,lease_epoch:2,next_frame_index:39,checkpoint_config:{version:8,thresholds:{max_bytes:512,max_deltas:1,max_interval_ms:2000}}};
|
|
608
|
+
const transport = {push:async (_topic,event,payload) => {
|
|
609
|
+
events.push(event);
|
|
610
|
+
if(event === 'terminal_debt_identity') {
|
|
611
|
+
if(testCase.control === 'disconnect') closeRequiredAdtAdmissionForDisconnect(transport);
|
|
612
|
+
return {status:'ok',response:{ok:true,identity:{...identity,...(testCase.control === 'foreign' ? {thread_id:'foreign-thread'} : {})}}};
|
|
613
|
+
}
|
|
614
|
+
if(event === 'adt_binding_reconcile') {
|
|
615
|
+
if(payload.checkpoint_config_version !== 7 || payload.lease_epoch !== 1) throw new Error('cold lookup changed original generation');
|
|
616
|
+
return {status:'ok',response:{ok:true,adt:{verdict:'already_applied',binding}}};
|
|
617
|
+
}
|
|
618
|
+
throw new Error('cold recovery attempted unexpected event: '+event);
|
|
619
|
+
}};
|
|
620
|
+
const pipeline = {
|
|
621
|
+
failedTerminalEntries:()=>[{entrySeq:61,kind:'adt.terminal_attempt',data:{seq:44,status:'processed',turn_id:'synthetic-completed-turn',terminal_frame:{session_id:3600,lease_epoch:1,frame_index:40,frame_kind:'terminal',assistant_phase:'final_answer',source_stream_key:'wrong-key',body:'Synthetic completed answer.'}}}],
|
|
622
|
+
repairTerminalDebt:async request => {
|
|
623
|
+
if(request.sessionId !== 3600 || request.originalLeaseEpoch !== 1 || request.sourceSeq !== 44 || request.binding !== binding || request.authoritativeTerminal) throw new Error('cold repair identity mismatch');
|
|
624
|
+
if(!request.authorize({seq:44})) throw new Error('cold repair unexpectedly unauthorized');
|
|
625
|
+
repairs += 1;
|
|
626
|
+
return {kind:'ready',id:'fixture',serializedData:'fixture'};
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
try {
|
|
630
|
+
await recoverRetainedTerminalDebt({transport,topic:'local_runner:fixture',threadId:identity.thread_id,pipeline,log:()=>{}});
|
|
631
|
+
return {events,repairs};
|
|
632
|
+
} finally {
|
|
633
|
+
const candidate = findRequiredAdtCandidate(transport, value => value.thread_id === identity.thread_id);
|
|
634
|
+
if(candidate !== undefined) releaseRequiredAdtCandidate(candidate);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
case 'terminalDebtStorm':
|
|
638
|
+
return await terminalDebtStorm(testCase);
|
|
529
639
|
case 'controlDispatch':
|
|
530
640
|
return await handleControlDispatch(testCase);
|
|
531
641
|
case 'staleness':
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
preReduceSnapshotItems,
|
|
46
46
|
rawSnapshotItemsForTurn,
|
|
47
47
|
} from './src/pipeline/integration';
|
|
48
|
-
import { outboxEntryKinds } from './src/pipeline/contracts';
|
|
48
|
+
import { outboxEntryKinds, adoptAdtStreamBinding } from './src/pipeline/contracts';
|
|
49
49
|
import { phoenixPushErrors } from './src/phoenix';
|
|
50
50
|
import {
|
|
51
51
|
rememberTurnTraceparent,
|
|
@@ -306,8 +306,74 @@ async function runSessionCase(caseInput) {
|
|
|
306
306
|
return { trace };
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// Spec: spec/adt-final-answer-identity-rules.md, rules 1–3.
|
|
310
|
+
// Actual producer/outbox boundary; assertions remain in the ReScript owner.
|
|
311
|
+
async function runFinalCheckpointIdentity(input) {
|
|
312
|
+
const adoption = adoptAdtStreamBinding({session_id: 3600, lease_epoch: 1, next_frame_index: 37,
|
|
313
|
+
checkpoint_config: {version: 3, thresholds: {max_bytes: 1, max_deltas: 1, max_interval_ms: 2000}}}, 44, Date.now());
|
|
314
|
+
if (adoption.kind !== 'accepted') throw new Error('fixture_adoption_refused');
|
|
315
|
+
const pushes = [];
|
|
316
|
+
const logs = [];
|
|
317
|
+
let finishProvider;
|
|
318
|
+
const providerTerminal = new Promise(resolve => {finishProvider = resolve;});
|
|
319
|
+
const pipeline = createSessionPipeline({
|
|
320
|
+
instanceId: 'synthetic-yc-identity', threadKey: 'synthetic-final-identity', persistDir: undefined,
|
|
321
|
+
messageStateReplyShape: {kind: 'inner_response'},
|
|
322
|
+
wire: () => ({workspaceSlug: 'fixture', channelSlug: 'office', kandanThreadId: 'fixture-thread',
|
|
323
|
+
codexThreadId: 'fixture-provider-thread', rootSeq: 1}),
|
|
324
|
+
runnerPayload: (eventType, codexThreadId, sourceSeq, extra) => ({metadata: {local_codex_runner: {
|
|
325
|
+
...extra, event_type: eventType, codex_thread_id: codexThreadId, source_message_seq: sourceSeq,
|
|
326
|
+
}}}),
|
|
327
|
+
sourceSeqForTurn: () => 44,
|
|
328
|
+
adtAuthorityForSourceSeq: seq => seq === 44 ? adoption.session : undefined,
|
|
329
|
+
resolveSnapshot: async () => ({items: mergeSnapshotItems(input.readItems, input.sessionItems)}),
|
|
330
|
+
onTurnTerminal: () => finishProvider(),
|
|
331
|
+
log: (event, fields) => logs.push({event, fields}), statsIntervalMs: 0,
|
|
332
|
+
push: async (event, payload) => {
|
|
333
|
+
pushes.push({event, payload});
|
|
334
|
+
if (event === 'terminal_attempt') return {terminal_attempt: {
|
|
335
|
+
frame: {accepted: true}, message_state: {disposition: 'applied', thread_id: 'fixture-thread', source_seq: 44},
|
|
336
|
+
}};
|
|
337
|
+
return {seq: pushes.length, accepted: true};
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
pipeline.submitNotification('item/agentMessage/delta', {threadId: 'fixture-provider-thread',
|
|
342
|
+
turnId: 'fixture-turn', itemId: 'msg-live-final', delta: input.body});
|
|
343
|
+
pipeline.submitNotification('item/completed', {threadId: 'fixture-provider-thread', turnId: 'fixture-turn',
|
|
344
|
+
item: {id: 'msg-live-final', type: 'agentMessage', text: input.body, phase: 'final_answer'}});
|
|
345
|
+
await pipeline.flushOutbox(1000);
|
|
346
|
+
pipeline.submitNotification('turn/completed', {threadId: 'fixture-provider-thread', turn: {id: 'fixture-turn'}});
|
|
347
|
+
await providerTerminal;
|
|
348
|
+
await pipeline.flushOutbox(1000);
|
|
349
|
+
const metrics = pipeline.metrics();
|
|
350
|
+
return {
|
|
351
|
+
standalone: pushes.filter(push => push.event === 'adt_frame').map(push => push.payload),
|
|
352
|
+
terminals: pushes.filter(push => push.event === 'terminal_attempt').map(push => push.payload.terminal_frame),
|
|
353
|
+
depth: metrics.depth, failures: metrics.totalPermanentFailures,
|
|
354
|
+
faults: logs.filter(log => /rejected|refused|failed/.test(log.event)),
|
|
355
|
+
};
|
|
356
|
+
} finally { await pipeline.close(1000); }
|
|
357
|
+
}
|
|
358
|
+
|
|
309
359
|
async function runCase(input) {
|
|
310
360
|
switch (input.kind) {
|
|
361
|
+
// Spec: spec/commander-recovery-rules.md authenticated terminal evidence.
|
|
362
|
+
case 'terminalRepairReceipt': {
|
|
363
|
+
const remembered = [];
|
|
364
|
+
const builder = createSessionPipelineBuilders({
|
|
365
|
+
instanceId: 'terminal-repair-fixture',
|
|
366
|
+
wire: () => ({workspaceSlug: 'synthetic', channelSlug: 'synthetic',
|
|
367
|
+
kandanThreadId: 'repair-thread', codexThreadId: 'provider-thread'}),
|
|
368
|
+
runnerPayload: () => ({}),
|
|
369
|
+
messageStateReplyShape: {kind: 'full_reply'},
|
|
370
|
+
log: () => {},
|
|
371
|
+
})['adt.terminal_attempt'];
|
|
372
|
+
const context = {remember: (key, value) => remembered.push({key, value}), recall: () => undefined};
|
|
373
|
+
const verdict = builder.replyPolicy.adjudicateReply(input.data, input.reply, context);
|
|
374
|
+
if (verdict.kind === 'accept') builder.onReply(input.data, input.reply, context);
|
|
375
|
+
return {accepted: verdict.kind === 'accept', remembered};
|
|
376
|
+
}
|
|
311
377
|
case 'constants':
|
|
312
378
|
return {
|
|
313
379
|
defaultOutboxMaxInFlightPushes,
|
|
@@ -325,6 +391,8 @@ async function runCase(input) {
|
|
|
325
391
|
return mergeSnapshotItems(input.readItems, input.sessionItems);
|
|
326
392
|
case 'sessionRun':
|
|
327
393
|
return runSessionCase(input);
|
|
394
|
+
case 'finalCheckpointIdentity':
|
|
395
|
+
return runFinalCheckpointIdentity(input);
|
|
328
396
|
default:
|
|
329
397
|
return { error: 'unknown case kind ' + String(input.kind) };
|
|
330
398
|
}
|
|
@@ -24,7 +24,7 @@ import { build } from 'esbuild';
|
|
|
24
24
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
25
25
|
|
|
26
26
|
const driverSource = `
|
|
27
|
-
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
28
28
|
import { tmpdir } from 'node:os';
|
|
29
29
|
import { join } from 'node:path';
|
|
30
30
|
import {
|
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
} from './src/pipeline/contracts';
|
|
94
94
|
import { discoverCodexModelCatalog } from './src/channelSession';
|
|
95
95
|
import { discoverClaudeCodeModelCatalog } from './src/claudeCodeSession';
|
|
96
|
+
import { claudeCodeRoutedProxyModel } from './src/claudeModelTransportLane';
|
|
96
97
|
|
|
97
98
|
const jsonHelpers = {
|
|
98
99
|
isJsonObject,
|
|
@@ -354,18 +355,18 @@ async function handle(testCase) {
|
|
|
354
355
|
);
|
|
355
356
|
return { outcome: outcomeValue, closeCount };
|
|
356
357
|
}
|
|
358
|
+
case 'claudeCodeRoutedProxyModel':
|
|
359
|
+
return outcome(() =>
|
|
360
|
+
claudeCodeRoutedProxyModel(testCase.model, testCase.nativeModelCatalog)
|
|
361
|
+
);
|
|
357
362
|
default:
|
|
358
363
|
return { threw: true, message: 'unknown op ' + String(testCase.op) };
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
366
|
|
|
362
|
-
|
|
363
|
-
process.stdin.setEncoding('utf8');
|
|
364
|
-
for await (const chunk of process.stdin) {
|
|
365
|
-
stdin += chunk;
|
|
366
|
-
}
|
|
367
|
+
const stdin = process.argv[2] ?? readFileSync(0, 'utf8');
|
|
367
368
|
const cases = JSON.parse(stdin);
|
|
368
|
-
|
|
369
|
+
writeFileSync(1, JSON.stringify(await Promise.all(cases.map(handle))));
|
|
369
370
|
`;
|
|
370
371
|
|
|
371
372
|
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
@@ -41,8 +41,10 @@ import {
|
|
|
41
41
|
mkdtempSync,
|
|
42
42
|
mkdirSync,
|
|
43
43
|
readdirSync,
|
|
44
|
+
rmSync,
|
|
44
45
|
readFileSync,
|
|
45
46
|
realpathSync,
|
|
47
|
+
renameSync,
|
|
46
48
|
statSync,
|
|
47
49
|
writeFileSync,
|
|
48
50
|
} from 'node:fs';
|
|
@@ -95,8 +97,10 @@ import {
|
|
|
95
97
|
import {
|
|
96
98
|
outboxJournalPath,
|
|
97
99
|
recoverOutboxJournal,
|
|
100
|
+
createOutboxJournal,
|
|
98
101
|
} from './src/pipeline/outboxJournal';
|
|
99
102
|
import { createThreadOutbox } from './src/pipeline/outbox';
|
|
103
|
+
import { createSessionPipelineBuilders, enqueueTerminalDebtRepair } from './src/pipeline/integration';
|
|
100
104
|
import {
|
|
101
105
|
applyResumeDispatchOutcome,
|
|
102
106
|
commitLiveAcceptedSourceSeq,
|
|
@@ -484,6 +488,29 @@ function handleOutboxJournal(testCase) {
|
|
|
484
488
|
return substitute({ recovered, events }, dir, dirPlaceholder);
|
|
485
489
|
}
|
|
486
490
|
|
|
491
|
+
// Spec: spec/commander-recovery-rules.md rule01. Failed terminal publication
|
|
492
|
+
// remains evidence/debt, never ordinary replay or an acknowledged terminal.
|
|
493
|
+
async function handleFailedTerminalJournal(testCase) {
|
|
494
|
+
const dir = scratch();
|
|
495
|
+
const path = join(dir, 'failed-terminal.outbox.jsonl');
|
|
496
|
+
try {
|
|
497
|
+
writeFileSync(path, testCase.contents, 'utf8');
|
|
498
|
+
const events = [];
|
|
499
|
+
let recovered = recoverOutboxJournal(path, collectingLog(events, dir));
|
|
500
|
+
if (testCase.compact) {
|
|
501
|
+
const snapshot = recovered;
|
|
502
|
+
const journal = createOutboxJournal({path, compactionThreshold: 1,
|
|
503
|
+
snapshot: () => snapshot, log: collectingLog(events, dir)});
|
|
504
|
+
journal.appendAck(63);
|
|
505
|
+
await journal.settled();
|
|
506
|
+
await journal.close();
|
|
507
|
+
recovered = recoverOutboxJournal(path, collectingLog(events, dir));
|
|
508
|
+
}
|
|
509
|
+
return {pendingEntries: recovered.pendingEntries,
|
|
510
|
+
failedTerminalEntries: recovered.failedTerminalEntries, counters: recovered.counters, events};
|
|
511
|
+
} finally {rmSync(dir, {recursive: true, force: true});}
|
|
512
|
+
}
|
|
513
|
+
|
|
487
514
|
// --- Outbox flow scenarios ---------------------------------------------------------------------
|
|
488
515
|
|
|
489
516
|
function buildersFromSpec(spec, recordedReplies) {
|
|
@@ -548,6 +575,14 @@ async function runOutboxPhaseInner(phase, shared) {
|
|
|
548
575
|
const events = [];
|
|
549
576
|
const log = collectingLog(events, shared.dir);
|
|
550
577
|
let nowMs = phase.nowMs ?? 1_000;
|
|
578
|
+
const builders = buildersFromSpec(phase.builders ?? shared.builders, recordedReplies);
|
|
579
|
+
if (phase.terminalRepairBuilder === true) {
|
|
580
|
+
builders['adt.terminal_attempt'] = createSessionPipelineBuilders({
|
|
581
|
+
instanceId: 'repair-fixture', wire: () => ({workspaceSlug: 'synthetic', channelSlug: 'synthetic',
|
|
582
|
+
kandanThreadId: shared.threadKey, codexThreadId: 'synthetic-provider'}),
|
|
583
|
+
runnerPayload: () => ({}), messageStateReplyShape: {kind: 'full_reply'}, log,
|
|
584
|
+
})['adt.terminal_attempt'];
|
|
585
|
+
}
|
|
551
586
|
const outbox = createThreadOutbox({
|
|
552
587
|
threadKey: phase.threadKey ?? shared.threadKey,
|
|
553
588
|
push: async (event, payload, timeoutMs, onSent) => {
|
|
@@ -570,7 +605,7 @@ async function runOutboxPhaseInner(phase, shared) {
|
|
|
570
605
|
}
|
|
571
606
|
throw failure;
|
|
572
607
|
},
|
|
573
|
-
builders
|
|
608
|
+
builders,
|
|
574
609
|
persistDir: shared.persistDir,
|
|
575
610
|
retryDelaysMs: phase.retryDelaysMs ?? [1, 1],
|
|
576
611
|
isRetryBudgetExempt: (error) => error.name === 'ScriptedBudgetExempt',
|
|
@@ -613,6 +648,13 @@ async function runOutboxPhaseInner(phase, shared) {
|
|
|
613
648
|
)
|
|
614
649
|
);
|
|
615
650
|
break;
|
|
651
|
+
case 'cycleReceipt': {
|
|
652
|
+
const receipt = outbox.recallDurableFact(action.key);
|
|
653
|
+
if(receipt === null || typeof receipt !== 'object') throw new Error('cyclic receipt fixture missing stored object');
|
|
654
|
+
receipt.cycle = receipt;
|
|
655
|
+
results.push({defined:false});
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
616
658
|
case 'pause':
|
|
617
659
|
results.push(outcome(() => outbox.pause()));
|
|
618
660
|
break;
|
|
@@ -654,6 +696,52 @@ async function runOutboxPhaseInner(phase, shared) {
|
|
|
654
696
|
await outbox.journalSettled();
|
|
655
697
|
results.push({ defined: false });
|
|
656
698
|
break;
|
|
699
|
+
// Spec: spec/commander-recovery-rules.md. Only this scenario's fresh
|
|
700
|
+
// scratch directory is moved; a real filesystem refusal tests both implementations.
|
|
701
|
+
case 'blockJournalWrites':
|
|
702
|
+
case 'restoreJournalWrites': {
|
|
703
|
+
await outbox.journalSettled();
|
|
704
|
+
const blocked = shared.persistDir + '.blocked';
|
|
705
|
+
if (action.fn === 'blockJournalWrites') renameSync(shared.persistDir, blocked);
|
|
706
|
+
else renameSync(blocked, shared.persistDir);
|
|
707
|
+
results.push({defined: false});
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
case 'appendUncertainTail':
|
|
711
|
+
case 'blockJournalCompaction':
|
|
712
|
+
case 'blockRekeyDestination':
|
|
713
|
+
case 'restoreJournalCompaction': {
|
|
714
|
+
await outbox.journalSettled();
|
|
715
|
+
const path = outboxJournalPath(shared.persistDir, shared.threadKey);
|
|
716
|
+
if (action.fn === 'blockRekeyDestination') mkdirSync(outboxJournalPath(shared.persistDir, action.threadKey));
|
|
717
|
+
else if (action.fn === 'appendUncertainTail') appendFileSync(path, '{"t":"kv","key":', 'utf8');
|
|
718
|
+
else if (action.fn === 'blockJournalCompaction') mkdirSync(path + '.tmp');
|
|
719
|
+
else renameSync(path + '.tmp', path + '.blocked-compaction');
|
|
720
|
+
results.push({defined: false});
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
case 'failedTerminalEntries':
|
|
724
|
+
results.push({failedTerminalEntries: outbox.failedTerminalEntries()});
|
|
725
|
+
break;
|
|
726
|
+
case 'terminalPublicationDebt':
|
|
727
|
+
results.push({terminalPublicationDebt: outbox.terminalPublicationDebt()});
|
|
728
|
+
break;
|
|
729
|
+
case 'rememberDurableFact':
|
|
730
|
+
outbox.rememberDurableFact(action.key, action.value);
|
|
731
|
+
await outbox.journalSettled();
|
|
732
|
+
results.push({defined: false});
|
|
733
|
+
break;
|
|
734
|
+
case 'rememberWithoutSettle':
|
|
735
|
+
outbox.rememberDurableFact(action.key, action.value);
|
|
736
|
+
results.push({defined: false});
|
|
737
|
+
break;
|
|
738
|
+
case 'repairTerminalDebt':
|
|
739
|
+
results.push({repair: await enqueueTerminalDebtRepair(outbox, {
|
|
740
|
+
threadId: shared.threadKey, sourceSeq: 44, sessionId: 3600, originalLeaseEpoch: 1,
|
|
741
|
+
binding: action.binding, authoritativeTerminal: action.authoritativeTerminal === true,
|
|
742
|
+
authorize: () => true,
|
|
743
|
+
}, log)});
|
|
744
|
+
break;
|
|
657
745
|
case 'rekeyJournal':
|
|
658
746
|
results.push(outcome(() => outbox.rekeyJournal(action.threadKey)));
|
|
659
747
|
await outbox.journalSettled();
|
|
@@ -855,6 +943,8 @@ async function handle(testCase) {
|
|
|
855
943
|
return handleQuarantine(testCase);
|
|
856
944
|
case 'outboxJournal':
|
|
857
945
|
return handleOutboxJournal(testCase);
|
|
946
|
+
case 'failedTerminalJournal':
|
|
947
|
+
return await handleFailedTerminalJournal(testCase);
|
|
858
948
|
case 'outboxScenario':
|
|
859
949
|
return await handleOutboxScenario(testCase);
|
|
860
950
|
case 'keystoneScenario':
|
|
@@ -892,6 +982,11 @@ await build({
|
|
|
892
982
|
loader: 'ts',
|
|
893
983
|
},
|
|
894
984
|
bundle: true,
|
|
985
|
+
// Same production ESM/CJS bridge as the existing core integration oracle;
|
|
986
|
+
// the shared typed terminal-repair validator reaches CommanderNode bindings.
|
|
987
|
+
banner: {
|
|
988
|
+
js: "import { createRequire as __queueOracleCreateRequire } from 'node:module';\nconst require = __queueOracleCreateRequire(import.meta.url);",
|
|
989
|
+
},
|
|
895
990
|
platform: 'node',
|
|
896
991
|
target: 'node20',
|
|
897
992
|
format: 'esm',
|