@sublang/playbook 15.1.0 → 17.0.0
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 +2 -0
- package/docs/cli.md +36 -18
- package/docs/configuration.md +7 -5
- package/docs/embedding.md +28 -7
- package/package.json +4 -4
- package/reference/sdlc/branch.playbook/branch.fsm.d.ts +63 -33
- package/reference/sdlc/branch.playbook/branch.fsm.js +190 -108
- package/reference/sdlc/branch.playbook/branch.fsm.ts +269 -147
- package/reference/sdlc/branch.playbook/branch.gears.md +7 -17
- package/reference/sdlc/branch.playbook/branch.playbook.d.ts +12 -16
- package/reference/sdlc/branch.playbook/branch.playbook.js +112 -101
- package/reference/sdlc/branch.playbook/branch.playbook.ts +128 -171
- package/reference/sdlc/branch.playbook/branch.registry.d.ts +3 -2
- package/reference/sdlc/branch.playbook/branch.registry.ts +6 -2
- package/reference/sdlc/captain.md +19 -10
- package/reference/sdlc/captain.playbook/captain.fsm.d.ts +6 -2
- package/reference/sdlc/captain.playbook/captain.fsm.js +19 -7
- package/reference/sdlc/captain.playbook/captain.fsm.ts +25 -10
- package/reference/sdlc/captain.playbook/captain.gears.md +16 -8
- package/reference/sdlc/captain.playbook/captain.playbook.d.ts +1 -1
- package/reference/sdlc/captain.playbook/captain.playbook.js +3 -2
- package/reference/sdlc/captain.playbook/captain.playbook.ts +4 -3
- package/reference/sdlc/code.playbook/bin/interactive-session.js +14 -1
- package/reference/sdlc/code.playbook/bin/portable-codec.js +3 -0
- package/reference/sdlc/code.playbook/bin/repository-effects.js +1 -0
- package/reference/sdlc/code.playbook/bin/run.js +128 -85
- package/reference/sdlc/code.playbook/bin/session-host.js +40 -8
- package/reference/sdlc/code.playbook/bin/session-store.js +162 -34
- package/reference/sdlc/code.playbook/code.fsm.d.ts +146 -93
- package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +8 -8
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +13 -13
- package/reference/sdlc/code.playbook/code.fsm.js +609 -649
- package/reference/sdlc/code.playbook/code.fsm.ts +890 -821
- package/reference/sdlc/code.playbook/code.gears.md +39 -29
- package/reference/sdlc/code.playbook/code.playbook.d.ts +13 -16
- package/reference/sdlc/code.playbook/code.playbook.js +166 -147
- package/reference/sdlc/code.playbook/code.playbook.ts +180 -229
- package/reference/sdlc/code.playbook/code.registry.d.ts +3 -2
- package/reference/sdlc/code.playbook/code.registry.ts +6 -2
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +3 -0
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +74 -2
- package/reference/sdlc/code.playbook/playbook-captain.js +1548 -468
- package/reference/sdlc/code.playbook/playbook-captain.ts +1993 -573
- package/reference/sdlc/code.playbook/session-host.d.ts +4 -1
- package/reference/sdlc/code.playbook/session-store.d.ts +17 -0
- package/reference/sdlc/code.playbook/session-store.js +1 -0
- package/reference/sdlc/decide.md +1 -0
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +273 -157
- package/reference/sdlc/decide.playbook/decide.fsm.js +765 -732
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1167 -945
- package/reference/sdlc/decide.playbook/decide.gears.md +28 -21
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -59
- package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3696
- package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5244
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +8 -4
- package/reference/sdlc/decide.playbook/decide.registry.js +2 -2
- package/reference/sdlc/decide.playbook/decide.registry.ts +13 -6
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +116 -134
- package/reference/sdlc/dev.playbook/dev.fsm.js +517 -575
- package/reference/sdlc/dev.playbook/dev.fsm.ts +667 -787
- package/reference/sdlc/dev.playbook/dev.gears.md +52 -54
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +13 -10
- package/reference/sdlc/dev.playbook/dev.playbook.js +128 -120
- package/reference/sdlc/dev.playbook/dev.playbook.ts +143 -193
- package/reference/sdlc/dev.playbook/dev.registry.d.ts +3 -2
- package/reference/sdlc/dev.playbook/dev.registry.ts +6 -2
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +95 -88
- package/reference/sdlc/pr.playbook/pr.fsm.js +461 -518
- package/reference/sdlc/pr.playbook/pr.fsm.ts +533 -663
- package/reference/sdlc/pr.playbook/pr.gears.md +47 -63
- package/reference/sdlc/pr.playbook/pr.playbook.d.ts +13 -23
- package/reference/sdlc/pr.playbook/pr.playbook.js +118 -112
- package/reference/sdlc/pr.playbook/pr.playbook.ts +136 -190
- package/reference/sdlc/pr.playbook/pr.registry.d.ts +3 -2
- package/reference/sdlc/pr.playbook/pr.registry.ts +4 -2
- package/reference/sdlc/review.md +3 -3
- package/reference/sdlc/review.playbook/review.fsm.d.ts +150 -116
- package/reference/sdlc/review.playbook/review.fsm.js +479 -550
- package/reference/sdlc/review.playbook/review.fsm.ts +717 -662
- package/reference/sdlc/review.playbook/review.gears.md +32 -29
- package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -12
- package/reference/sdlc/review.playbook/review.playbook.js +180 -155
- package/reference/sdlc/review.playbook/review.playbook.ts +190 -230
- package/reference/sdlc/review.playbook/review.registry.d.ts +6 -5
- package/reference/sdlc/review.playbook/review.registry.js +3 -3
- package/reference/sdlc/review.playbook/review.registry.ts +9 -5
- package/slc/gears2fsm.md +77 -8
- package/slc/link.md +127 -60
- package/slc/materialize-link.mjs +51 -13
- package/slc/optimize.md +4 -1
- package/slc/prefix-prompts.mjs +29 -28
- package/slc/prefix.md +27 -24
- package/slc/slc.pin-inputs.json +11 -2
- package/slc/text2gears.md +71 -11
- package/src/runtime.d.ts +46 -2
- package/src/runtime.ts +42 -2
- package/src/xstate-playbook-runtime.d.ts +63 -23
- package/src/xstate-playbook-runtime.js +1637 -475
- package/src/xstate-playbook-runtime.ts +2105 -532
- package/src/xstate-runtime.d.ts +3 -4
- package/src/xstate-runtime.js +52 -0
- package/src/xstate-runtime.ts +59 -4
|
@@ -2551,24 +2551,21 @@ async function createLease({
|
|
|
2551
2551
|
now,
|
|
2552
2552
|
}) {
|
|
2553
2553
|
let released = false;
|
|
2554
|
-
let
|
|
2554
|
+
let operationTail = Promise.resolve();
|
|
2555
2555
|
let indeterminateEffectLedgerWrite;
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
} finally {
|
|
2570
|
-
operationActive = false;
|
|
2571
|
-
}
|
|
2556
|
+
let releasing = false;
|
|
2557
|
+
const ownerChecks = new Set();
|
|
2558
|
+
|
|
2559
|
+
// All lifecycle, step and repository writes share one order, including
|
|
2560
|
+
// calls from parallel players. A failed write does not poison the queue.
|
|
2561
|
+
const runExclusive = (operation) => {
|
|
2562
|
+
if (releasing) return Promise.reject(new Error('Captain session lease is releasing'));
|
|
2563
|
+
const run = operationTail.then(() => {
|
|
2564
|
+
if (released) throw new Error('Captain session lease was already released');
|
|
2565
|
+
return operation();
|
|
2566
|
+
});
|
|
2567
|
+
operationTail = run.then(() => undefined, () => undefined);
|
|
2568
|
+
return run;
|
|
2572
2569
|
};
|
|
2573
2570
|
|
|
2574
2571
|
const assertOwnerUnchecked = async () => {
|
|
@@ -2580,7 +2577,14 @@ async function createLease({
|
|
|
2580
2577
|
return current;
|
|
2581
2578
|
};
|
|
2582
2579
|
|
|
2583
|
-
const assertOwner = () =>
|
|
2580
|
+
const assertOwner = () => {
|
|
2581
|
+
if (releasing) return Promise.reject(new Error('Captain session lease is releasing'));
|
|
2582
|
+
// This reads only the lease token. Parallel players must not compete
|
|
2583
|
+
// for the mutation guard, but release must still join admitted reads.
|
|
2584
|
+
const check = assertOwnerUnchecked();
|
|
2585
|
+
ownerChecks.add(check);
|
|
2586
|
+
return check.finally(() => ownerChecks.delete(check));
|
|
2587
|
+
};
|
|
2584
2588
|
|
|
2585
2589
|
const replayWriter = await createLeaseReplayWriter({
|
|
2586
2590
|
sessionsDir,
|
|
@@ -3113,6 +3117,7 @@ async function createLease({
|
|
|
3113
3117
|
...settledAbandonmentMember(prior),
|
|
3114
3118
|
uncertain: {
|
|
3115
3119
|
baseUpdatedAt: prior.uncertain.baseUpdatedAt,
|
|
3120
|
+
...(prior.uncertain.progress === undefined ? {} : { progress: prior.uncertain.progress }),
|
|
3116
3121
|
input: prior.uncertain.input,
|
|
3117
3122
|
attemptId: nextAttemptId,
|
|
3118
3123
|
attemptNumber: prior.uncertain.attemptNumber + 1,
|
|
@@ -3607,6 +3612,49 @@ async function createLease({
|
|
|
3607
3612
|
return record.effectLedger;
|
|
3608
3613
|
});
|
|
3609
3614
|
|
|
3615
|
+
const recordProgress = (value) => runExclusive(async () => {
|
|
3616
|
+
const change = requireRecord(snapshotJsonValue(value, 'Captain progress change'), 'Captain progress change');
|
|
3617
|
+
exactOptionalKeys(change, [], ['snapshot', 'step'], 'Captain progress change');
|
|
3618
|
+
await assertOwnerUnchecked();
|
|
3619
|
+
const prior = await readRecord(sessionId, { missing: 'undefined' });
|
|
3620
|
+
if (prior?.state !== 'uncertain' || prior.uncertain.abandonment) {
|
|
3621
|
+
throw new Error('Saving progress requires an active uncertain turn');
|
|
3622
|
+
}
|
|
3623
|
+
const progress = structuredClone(prior.uncertain.progress ?? { snapshot: null, steps: [], positionStepId: null });
|
|
3624
|
+
const newStepStart = change.step && !Object.hasOwn(change.step, 'result') && !progress.steps.some(({ id }) => id === change.step.id);
|
|
3625
|
+
if (Object.hasOwn(change, 'snapshot')) {
|
|
3626
|
+
if (change.snapshot !== null && !isDeepStrictEqual(change.snapshot.effectLedger, prior.effectLedger)) {
|
|
3627
|
+
throw new Error('Saved progress must contain the current effect ledger');
|
|
3628
|
+
}
|
|
3629
|
+
progress.snapshot = change.snapshot;
|
|
3630
|
+
progress.positionStepId = change.snapshot !== null && newStepStart ? change.step.id : null;
|
|
3631
|
+
}
|
|
3632
|
+
if (change.step !== undefined) {
|
|
3633
|
+
const step = snapshotJsonValue(change.step, 'Captain step');
|
|
3634
|
+
const index = progress.steps.findIndex(({ id }) => id === step.id);
|
|
3635
|
+
if (index < 0) {
|
|
3636
|
+
if (!Object.hasOwn(change, 'snapshot')) throw new Error('A step start requires its position or explicit null');
|
|
3637
|
+
if (Object.hasOwn(step, 'result') && !['completion', 'answer'].includes(step.kind)) throw new Error('A step result requires its saved start');
|
|
3638
|
+
progress.steps.push(step);
|
|
3639
|
+
} else {
|
|
3640
|
+
const { result, ...start } = step;
|
|
3641
|
+
const saved = progress.steps[index];
|
|
3642
|
+
const { result: priorResult, ...priorStart } = saved;
|
|
3643
|
+
if (!isDeepStrictEqual(start, priorStart) ||
|
|
3644
|
+
(priorResult !== undefined && !isDeepStrictEqual(priorResult, result))) {
|
|
3645
|
+
throw new Error('A saved step cannot be replaced');
|
|
3646
|
+
}
|
|
3647
|
+
progress.steps[index] = step;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
const record = validateCaptainSessionRecord(projectRecovery({
|
|
3651
|
+
...prior, uncertain: { ...prior.uncertain, progress },
|
|
3652
|
+
}));
|
|
3653
|
+
await assertOwnerUnchecked();
|
|
3654
|
+
await writeRecord(record, { noReplace: false });
|
|
3655
|
+
await assertOwnerUnchecked();
|
|
3656
|
+
});
|
|
3657
|
+
|
|
3610
3658
|
const discard = ({ attemptId } = {}) =>
|
|
3611
3659
|
runExclusive(async () => {
|
|
3612
3660
|
assertUuid(attemptId, 'Captain session attempt id');
|
|
@@ -3620,10 +3668,8 @@ async function createLease({
|
|
|
3620
3668
|
'Captain session unresolved-effect abandonment must recover before discard',
|
|
3621
3669
|
);
|
|
3622
3670
|
}
|
|
3623
|
-
if (!
|
|
3624
|
-
throw new Error(
|
|
3625
|
-
'Captain session uncertain effect ledger differs from its pre-turn checkpoint and cannot be discarded',
|
|
3626
|
-
);
|
|
3671
|
+
if (!isUncertainTurnDiscardable(prior)) {
|
|
3672
|
+
throw new Error('Recorded work or changed repository evidence must be restored and reported; the turn cannot be discarded');
|
|
3627
3673
|
}
|
|
3628
3674
|
await assertOwnerUnchecked();
|
|
3629
3675
|
if (prior.uncertain.baseUpdatedAt === null) {
|
|
@@ -3655,19 +3701,27 @@ async function createLease({
|
|
|
3655
3701
|
});
|
|
3656
3702
|
|
|
3657
3703
|
const release = () => {
|
|
3704
|
+
if (released || releasing) return Promise.reject(new Error(`Captain session lease is ${released ? 'released' : 'releasing'}`));
|
|
3658
3705
|
replayWriter.closeAppendAdmission();
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
if (
|
|
3706
|
+
const pending = runExclusive(async () => {
|
|
3707
|
+
try {
|
|
3708
|
+
await Promise.allSettled([...ownerChecks]);
|
|
3709
|
+
await replayWriter.prepareRelease();
|
|
3710
|
+
if (replayWriter.status().incomplete && previousManifest?.replay?.incomplete !== true) {
|
|
3711
|
+
const record = await readRecord(sessionId, { missing: 'undefined' });
|
|
3712
|
+
if (record !== undefined) await writeRecord(record, { noReplace: false });
|
|
3713
|
+
}
|
|
3714
|
+
const current = await assertOwnerUnchecked();
|
|
3715
|
+
await retireObservedLease(sessionId, current);
|
|
3716
|
+
released = true;
|
|
3717
|
+
portableWriters.delete(sessionId);
|
|
3718
|
+
return replayWriter.status();
|
|
3719
|
+
} finally {
|
|
3720
|
+
releasing = false;
|
|
3664
3721
|
}
|
|
3665
|
-
const current = await assertOwnerUnchecked();
|
|
3666
|
-
await retireObservedLease(sessionId, current);
|
|
3667
|
-
released = true;
|
|
3668
|
-
portableWriters.delete(sessionId);
|
|
3669
|
-
return replayWriter.status();
|
|
3670
3722
|
});
|
|
3723
|
+
releasing = true;
|
|
3724
|
+
return pending;
|
|
3671
3725
|
};
|
|
3672
3726
|
|
|
3673
3727
|
return Object.freeze({
|
|
@@ -3691,6 +3745,7 @@ async function createLease({
|
|
|
3691
3745
|
completeUnresolvedEffectAbandonment,
|
|
3692
3746
|
recoverUnresolvedEffectAbandonment,
|
|
3693
3747
|
writeEffectLedger,
|
|
3748
|
+
recordProgress,
|
|
3694
3749
|
settle,
|
|
3695
3750
|
discard,
|
|
3696
3751
|
assertOwner,
|
|
@@ -4034,7 +4089,7 @@ function validateCanonicalCaptainSessionRecord(
|
|
|
4034
4089
|
exactOptionalKeys(
|
|
4035
4090
|
uncertain,
|
|
4036
4091
|
UNCERTAIN_KEYS,
|
|
4037
|
-
['abandonment'],
|
|
4092
|
+
['abandonment', 'progress'],
|
|
4038
4093
|
'Captain session record uncertain',
|
|
4039
4094
|
);
|
|
4040
4095
|
if (uncertain.baseUpdatedAt !== null) {
|
|
@@ -4112,6 +4167,65 @@ function validateCanonicalCaptainSessionRecord(
|
|
|
4112
4167
|
uncertain.attemptedExecutionProjection,
|
|
4113
4168
|
artifactSchemas,
|
|
4114
4169
|
);
|
|
4170
|
+
if (uncertain.progress !== undefined) {
|
|
4171
|
+
const progress = requireRecord(uncertain.progress, 'Captain progress');
|
|
4172
|
+
exactOptionalKeys(progress, ['snapshot', 'steps'], ['positionStepId'], 'Captain progress');
|
|
4173
|
+
if (!Array.isArray(progress.steps)) throw new Error('Captain steps must be an array');
|
|
4174
|
+
const ids = new Set();
|
|
4175
|
+
for (const step of progress.steps) {
|
|
4176
|
+
exactOptionalKeys(requireRecord(step, 'Captain step'), ['id', 'kind', 'stateId', 'runtimeSessionId', 'playbookId'], ['result'], 'Captain step');
|
|
4177
|
+
assertUuid(step.id, 'Captain step id');
|
|
4178
|
+
assertUuid(step.runtimeSessionId, 'Captain step runtime');
|
|
4179
|
+
if (ids.has(step.id)) throw new Error('Captain step ids must be unique');
|
|
4180
|
+
ids.add(step.id);
|
|
4181
|
+
if (!['player', 'captain', 'script', 'preparation', 'completion', 'answer'].includes(step.kind)) throw new Error('Unknown Captain step kind');
|
|
4182
|
+
if (['completion', 'answer'].includes(step.kind) && (!step.result || typeof step.result !== 'object' || Array.isArray(step.result))) throw new Error('A recorded completion or answer requires its result');
|
|
4183
|
+
if (step.kind === 'completion') {
|
|
4184
|
+
exactOptionalKeys(step.result, ['state'], ['description', 'terminalOutcome', 'retention'], 'Completed root result');
|
|
4185
|
+
if (step.result.retention !== undefined && !['clear', 'keep'].includes(step.result.retention)) throw new Error('Completed root retention must be clear or keep');
|
|
4186
|
+
if (step.result.state?.status !== 'done' || (step.result.state.stateId !== undefined && step.result.state.stateId !== step.stateId)) throw new Error('Completed root result must name its final state');
|
|
4187
|
+
if (step.result.description !== undefined) requireNonblank(step.result.description, 'Completed root description');
|
|
4188
|
+
if (step.result.terminalOutcome !== undefined) {
|
|
4189
|
+
const terminal = step.result.terminalOutcome;
|
|
4190
|
+
exactOptionalKeys(terminal, ['stateId', 'kind'], ['description'], 'Completed root outcome');
|
|
4191
|
+
if (terminal.stateId !== step.stateId || !['success', 'failure'].includes(terminal.kind)) throw new Error('Completed root outcome must name its final state and kind');
|
|
4192
|
+
if (terminal.description !== undefined) requireNonblank(terminal.description, 'Completed root outcome description');
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
if (step.kind === 'answer') {
|
|
4196
|
+
exactOptionalKeys(step.result, ['questions', 'instruction'], [], 'Selected answer');
|
|
4197
|
+
requireNonblank(step.result.instruction, 'Selected answer instruction');
|
|
4198
|
+
if (!Array.isArray(step.result.questions) || !step.result.questions.length) throw new Error('Selected answer requires questions');
|
|
4199
|
+
for (const question of step.result.questions) {
|
|
4200
|
+
exactOptionalKeys(question, ['asker', 'question'], [], 'Selected answer question');
|
|
4201
|
+
if (question.asker?.kind === 'captain') exactOptionalKeys(question.asker, ['kind'], [], 'Selected answer asker');
|
|
4202
|
+
else {
|
|
4203
|
+
exactOptionalKeys(question.asker, ['kind', 'roleId'], [], 'Selected answer asker');
|
|
4204
|
+
if (question.asker.kind !== 'role') throw new Error('Unknown selected answer asker');
|
|
4205
|
+
requireCanonicalNonblank(question.asker.roleId, 'Selected answer role');
|
|
4206
|
+
}
|
|
4207
|
+
requireNonblank(question.question, 'Selected answer question');
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
requireCanonicalNonblank(step.stateId, 'Captain step state');
|
|
4211
|
+
if (!Object.hasOwn(structural.catalog, step.playbookId)) throw new Error('Captain step names an unknown playbook');
|
|
4212
|
+
}
|
|
4213
|
+
if (progress.positionStepId !== undefined && progress.positionStepId !== null && !ids.has(progress.positionStepId)) throw new Error('Captain progress positionStepId must name a recorded step');
|
|
4214
|
+
if (progress.snapshot === null && progress.positionStepId != null) throw new Error('Captain progress positionStepId requires a saved position');
|
|
4215
|
+
if (progress.snapshot !== null) {
|
|
4216
|
+
const prepared = assertPlaybookCaptainShellSnapshot(progress.snapshot);
|
|
4217
|
+
assertSnapshotMatchesStructure(prepared, structural, artifactSchemas);
|
|
4218
|
+
if ((Object.hasOwn(progress.snapshot, 'presentedEffectPrefix') && prepared.presentedEffectPrefix !== snapshot.presentedEffectPrefix) ||
|
|
4219
|
+
!isDeepStrictEqual(prepared.captain, snapshot.captain) ||
|
|
4220
|
+
!isDeepStrictEqual(prepared.journal, snapshot.journal) ||
|
|
4221
|
+
!isDeepStrictEqual(prepared.sequences, snapshot.sequences) ||
|
|
4222
|
+
!isPlaybookEffectLedgerMonotonicExtension(snapshot.effectLedger, prepared.effectLedger) ||
|
|
4223
|
+
!isPlaybookEffectLedgerMonotonicExtension(prepared.effectLedger, effectLedger)) {
|
|
4224
|
+
throw new Error('Saved progress must retain the settled controller and current work');
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4115
4229
|
if (uncertain.baseUpdatedAt === null) {
|
|
4116
4230
|
if (!isDeepStrictEqual(lastApplied, attempted)) {
|
|
4117
4231
|
throw new Error(
|
|
@@ -4436,6 +4550,7 @@ const EFFECT_BOUNDARY_IDENTITY_KEYS = [
|
|
|
4436
4550
|
const EFFECT_BOUNDARY_OPTIONAL_KEYS = [
|
|
4437
4551
|
'after',
|
|
4438
4552
|
'physicalReceipt',
|
|
4553
|
+
'restored',
|
|
4439
4554
|
'finalText',
|
|
4440
4555
|
'semanticCandidate',
|
|
4441
4556
|
'initialSemanticCandidate',
|
|
@@ -5018,6 +5133,7 @@ function assertMonotonicBoundaryReplacement(expected, next, path) {
|
|
|
5018
5133
|
for (const key of [
|
|
5019
5134
|
'after',
|
|
5020
5135
|
'physicalReceipt',
|
|
5136
|
+
'restored',
|
|
5021
5137
|
'finalText',
|
|
5022
5138
|
'initialSemanticCandidate',
|
|
5023
5139
|
]) {
|
|
@@ -6001,9 +6117,13 @@ function validateRetainedGenerationFrame(
|
|
|
6001
6117
|
'roleBindings',
|
|
6002
6118
|
'runtime',
|
|
6003
6119
|
],
|
|
6004
|
-
['parentSessionId', 'parentCallId'],
|
|
6120
|
+
['parentSessionId', 'parentCallId', 'request', 'inputs'],
|
|
6005
6121
|
path,
|
|
6006
6122
|
);
|
|
6123
|
+
if (frame.request !== undefined) {
|
|
6124
|
+
if (index !== 0) throw new Error(`${path}.request belongs only to the root`);
|
|
6125
|
+
requireNonblank(frame.request, `${path}.request`);
|
|
6126
|
+
}
|
|
6007
6127
|
const playbookId = requireCanonicalNonblank(
|
|
6008
6128
|
frame.playbookId,
|
|
6009
6129
|
`${path}.playbookId`,
|
|
@@ -6014,6 +6134,7 @@ function validateRetainedGenerationFrame(
|
|
|
6014
6134
|
`${path}.playbookId names unknown stored playbook ${JSON.stringify(playbookId)}`,
|
|
6015
6135
|
);
|
|
6016
6136
|
}
|
|
6137
|
+
if (frame.inputs !== undefined && (index !== 0 || !Array.isArray(frame.inputs) || frame.inputs.some((input) => typeof input !== 'string' || !input.trim()))) throw new Error(`${path}.inputs belongs only to the root and must contain nonempty strings`);
|
|
6017
6138
|
assertUuid(frame.sessionId, `${path}.sessionId`);
|
|
6018
6139
|
assertUuid(frame.rootSessionId, `${path}.rootSessionId`);
|
|
6019
6140
|
if (sessionIds.has(frame.sessionId)) {
|
|
@@ -6574,3 +6695,10 @@ async function writePrivateBytes(path, bytes, fs) {
|
|
|
6574
6695
|
try { await fs.unlink(temporary); } catch (cause) { if (cause?.code !== 'ENOENT') throw cause; }
|
|
6575
6696
|
}
|
|
6576
6697
|
}
|
|
6698
|
+
|
|
6699
|
+
/** One shared decision for discard controls and durable rollback. */
|
|
6700
|
+
export function isUncertainTurnDiscardable(record) {
|
|
6701
|
+
return record?.state === 'uncertain' && !record.uncertain.abandonment &&
|
|
6702
|
+
(record.uncertain.progress?.steps.length ?? 0) === 0 &&
|
|
6703
|
+
isDeepStrictEqual(record.effectLedger, record.snapshot.effectLedger);
|
|
6704
|
+
}
|