@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
package/src/xstate-runtime.d.ts
CHANGED
|
@@ -69,11 +69,9 @@ export type PlaybookReconciledSemanticOutput = Readonly<Record<string, string>>
|
|
|
69
69
|
/** Retained presentation and semantic evidence, kept separate from output. */
|
|
70
70
|
export interface PlaybookRetainedSemanticEvidence {
|
|
71
71
|
readonly finalText?: string;
|
|
72
|
-
readonly semanticCandidate: Readonly<Record<string, string
|
|
73
|
-
readonly guard: string;
|
|
74
|
-
};
|
|
72
|
+
readonly semanticCandidate: Readonly<Record<string, string>>;
|
|
75
73
|
}
|
|
76
|
-
export type PlaybookSemanticReconciliationReason = 'missing-presentation-evidence' | 'missing-repository-receipt' | 'invalid-repository-receipt' | 'repository-disposition-mismatch' | 'missing-effect-evidence' | 'missing-runtime-evidence' | 'inconsistent-runtime-evidence';
|
|
74
|
+
export type PlaybookSemanticReconciliationReason = 'no-matching-outcome' | 'missing-presentation-evidence' | 'missing-repository-receipt' | 'invalid-repository-receipt' | 'repository-disposition-mismatch' | 'missing-effect-evidence' | 'missing-runtime-evidence' | 'inconsistent-runtime-evidence';
|
|
77
75
|
/** A fail-closed semantic/effect reconciliation decision. */
|
|
78
76
|
export type PlaybookSemanticReconciliation = {
|
|
79
77
|
readonly status: 'resolved' | 'deferred';
|
|
@@ -163,6 +161,7 @@ export interface NestedPlaybookBridge<TInput extends NestedPlaybookInput = Neste
|
|
|
163
161
|
confirmRestore(): void;
|
|
164
162
|
/** Complete durable identity; undefined until a normal or restored call suspends. */
|
|
165
163
|
getSuspendedCall(): PlaybookSuspendedCall | undefined;
|
|
164
|
+
checkpointCall(child: PlaybookPendingCall): PlaybookSuspendedCall | undefined;
|
|
166
165
|
resume(input: {
|
|
167
166
|
callId: string;
|
|
168
167
|
result: PlaybookCallResult;
|
package/src/xstate-runtime.js
CHANGED
|
@@ -351,6 +351,7 @@ export function snapshotPlaybookSession(session) {
|
|
|
351
351
|
callPlaybook: capturedPort(portDescriptors, 'callPlaybook'),
|
|
352
352
|
emitStatus: capturedPort(portDescriptors, 'emitStatus'),
|
|
353
353
|
emitTelemetry: capturedPort(portDescriptors, 'emitTelemetry'),
|
|
354
|
+
...(portDescriptors.recordStep === undefined ? {} : { recordStep: capturedPort(portDescriptors, 'recordStep') }),
|
|
354
355
|
});
|
|
355
356
|
const playerSessions = hasPlayerSessions
|
|
356
357
|
? capturedSessionStore(sessionDescriptors)
|
|
@@ -1102,6 +1103,20 @@ function unresolvedSemanticEvidence(reason, evidence, cause = assertPlaybookFail
|
|
|
1102
1103
|
* repository fact is inferred from that prose or from the semantic candidate.
|
|
1103
1104
|
*/
|
|
1104
1105
|
export function reconcilePlaybookSemanticEvidence(input) {
|
|
1106
|
+
let report;
|
|
1107
|
+
try {
|
|
1108
|
+
report = snapshotJsonValue(input.semanticCandidate, 'semantic candidate');
|
|
1109
|
+
}
|
|
1110
|
+
catch (error) {
|
|
1111
|
+
semanticCandidateStructureError(`must be detached plain JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
1112
|
+
}
|
|
1113
|
+
if (isRecord(report) && Object.keys(report).length === 1 &&
|
|
1114
|
+
typeof report.blocked === 'string' && report.blocked.trim().length > 0) {
|
|
1115
|
+
return unresolvedSemanticEvidence('no-matching-outcome', retainedSemanticEvidence(report, input.finalText), assertPlaybookFailureCause({
|
|
1116
|
+
code: 'runtime-defect',
|
|
1117
|
+
evidence: { reason: `No declared outcome matches: ${report.blocked}` },
|
|
1118
|
+
}));
|
|
1119
|
+
}
|
|
1105
1120
|
const { candidate, outcome } = snapshotSemanticCandidate(input.semanticCandidate, input.outcomes);
|
|
1106
1121
|
const evidence = retainedSemanticEvidence(candidate, input.finalText);
|
|
1107
1122
|
const finalText = typeof input.finalText === 'string' ? input.finalText.trim() : undefined;
|
|
@@ -1256,6 +1271,7 @@ function effectBoundary(value, index) {
|
|
|
1256
1271
|
'baseline',
|
|
1257
1272
|
'after',
|
|
1258
1273
|
'physicalReceipt',
|
|
1274
|
+
'restored',
|
|
1259
1275
|
'finalText',
|
|
1260
1276
|
'semanticCandidate',
|
|
1261
1277
|
'initialSemanticCandidate',
|
|
@@ -1315,6 +1331,15 @@ function effectBoundary(value, index) {
|
|
|
1315
1331
|
if (own(value, 'finalText') && typeof value.finalText !== 'string') {
|
|
1316
1332
|
throw new TypeError(`${path}.finalText must be a string`);
|
|
1317
1333
|
}
|
|
1334
|
+
if (own(value, 'restored')) {
|
|
1335
|
+
const restored = effectObservation(value.restored, `${path}.restored`);
|
|
1336
|
+
if (!jsonValuesEqual(restored, baseline) ||
|
|
1337
|
+
!isRecord(value.physicalReceipt) || after?.head !== baseline.head ||
|
|
1338
|
+
value.dispositions.some((item) => item !== 'unchanged') ||
|
|
1339
|
+
value.cohortId !== undefined || value.logicalOperationId !== undefined) {
|
|
1340
|
+
throw new TypeError(`${path}.restored requires a standalone read-only call restored exactly without commit movement`);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1318
1343
|
if (own(value, 'initialSemanticCandidate')) {
|
|
1319
1344
|
if (!own(value, 'semanticCandidate') ||
|
|
1320
1345
|
!isRecord(value.correctionBudget) ||
|
|
@@ -1578,6 +1603,7 @@ export function isPlaybookEffectLedgerMonotonicExtension(baselineValue, currentV
|
|
|
1578
1603
|
const boundaryEvidenceKeys = [
|
|
1579
1604
|
'after',
|
|
1580
1605
|
'physicalReceipt',
|
|
1606
|
+
'restored',
|
|
1581
1607
|
'finalText',
|
|
1582
1608
|
'initialSemanticCandidate',
|
|
1583
1609
|
];
|
|
@@ -1698,6 +1724,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
1698
1724
|
'retainedEffectSourceSessionId',
|
|
1699
1725
|
'retainedEffectReconciliation',
|
|
1700
1726
|
'failedEffectAttempt',
|
|
1727
|
+
'recoveryCheckpoint',
|
|
1701
1728
|
'suspendedCall',
|
|
1702
1729
|
], 'runtime snapshot');
|
|
1703
1730
|
let suspendedCall;
|
|
@@ -1862,6 +1889,27 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
1862
1889
|
}
|
|
1863
1890
|
failedEffectAttempt = Object.freeze({ boundaryPrefix, attemptId });
|
|
1864
1891
|
}
|
|
1892
|
+
let recoveryCheckpoint;
|
|
1893
|
+
if (own(snapshot, 'recoveryCheckpoint')) {
|
|
1894
|
+
const checkpoint = snapshot.recoveryCheckpoint;
|
|
1895
|
+
if ((state.stateId !== 'failed' && pendingBossQuestions.length === 0) || !isRecord(checkpoint)) {
|
|
1896
|
+
throw new TypeError('runtime recoveryCheckpoint requires an interrupted state and an object');
|
|
1897
|
+
}
|
|
1898
|
+
rejectUnknownKeys(checkpoint, ['stateId', 'prompt', 'machine', 'boundaryPrefix', 'result', 'id', 'delivered'], 'runtime recoveryCheckpoint');
|
|
1899
|
+
const stateId = requireNonEmptyString(checkpoint.stateId, 'recoveryCheckpoint.stateId');
|
|
1900
|
+
const prompt = requireNonEmptyString(checkpoint.prompt, 'recoveryCheckpoint.prompt');
|
|
1901
|
+
const boundaryPrefix = checkpoint.boundaryPrefix;
|
|
1902
|
+
if (!Number.isSafeInteger(boundaryPrefix) || typeof boundaryPrefix !== 'number' ||
|
|
1903
|
+
boundaryPrefix < 0 || boundaryPrefix > effectLedger.boundaries.length ||
|
|
1904
|
+
!isRecord(checkpoint.machine) || checkpoint.machine.status !== 'active' ||
|
|
1905
|
+
checkpoint.machine.value !== stateId) {
|
|
1906
|
+
throw new TypeError('runtime recoveryCheckpoint has an invalid machine or boundary prefix');
|
|
1907
|
+
}
|
|
1908
|
+
if (own(checkpoint, 'delivered') && checkpoint.delivered !== true) {
|
|
1909
|
+
throw new TypeError('recoveryCheckpoint.delivered must be true');
|
|
1910
|
+
}
|
|
1911
|
+
recoveryCheckpoint = Object.freeze({ stateId, prompt, boundaryPrefix, machine: snapshotJsonValue(checkpoint.machine), ...(own(checkpoint, 'id') ? { id: effectUuid(checkpoint.id, 'recoveryCheckpoint.id') } : {}), ...(own(checkpoint, 'result') ? { result: snapshotJsonValue(checkpoint.result) } : {}), ...(checkpoint.delivered === true ? { delivered: true } : {}) });
|
|
1912
|
+
}
|
|
1865
1913
|
const fields = {
|
|
1866
1914
|
playbookId,
|
|
1867
1915
|
machine,
|
|
@@ -1879,6 +1927,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
1879
1927
|
...(failedEffectAttempt === undefined
|
|
1880
1928
|
? {}
|
|
1881
1929
|
: { failedEffectAttempt }),
|
|
1930
|
+
...(recoveryCheckpoint === undefined ? {} : { recoveryCheckpoint }),
|
|
1882
1931
|
};
|
|
1883
1932
|
return Object.freeze({
|
|
1884
1933
|
schemaVersion: 4,
|
|
@@ -2718,6 +2767,9 @@ export function createNestedPlaybookBridge(options) {
|
|
|
2718
2767
|
actorLogic,
|
|
2719
2768
|
getPendingCall: () => pendingIdentity(current),
|
|
2720
2769
|
getSuspendedCall: () => suspendedIdentity(current),
|
|
2770
|
+
checkpointCall: (child) => current && current.callId === child.callId && current.input.playbookId === child.playbookId
|
|
2771
|
+
? { ...child, stateId: current.input.stateId, text: current.input.text, ...(current.turnId === undefined ? {} : { turnId: current.turnId }) }
|
|
2772
|
+
: undefined,
|
|
2721
2773
|
prepareRestore(call) {
|
|
2722
2774
|
// Capture the complete host-owned descriptor before observing or
|
|
2723
2775
|
// mutating bridge state, so a rejected preparation cannot leave state.
|
package/src/xstate-runtime.ts
CHANGED
|
@@ -598,6 +598,7 @@ export function snapshotPlaybookSession(
|
|
|
598
598
|
callPlaybook: capturedPort(portDescriptors, 'callPlaybook'),
|
|
599
599
|
emitStatus: capturedPort(portDescriptors, 'emitStatus'),
|
|
600
600
|
emitTelemetry: capturedPort(portDescriptors, 'emitTelemetry'),
|
|
601
|
+
...(portDescriptors.recordStep === undefined ? {} : { recordStep: capturedPort(portDescriptors, 'recordStep') }),
|
|
601
602
|
});
|
|
602
603
|
const playerSessions = hasPlayerSessions
|
|
603
604
|
? capturedSessionStore(sessionDescriptors)
|
|
@@ -1364,12 +1365,11 @@ export type PlaybookReconciledSemanticOutput = Readonly<
|
|
|
1364
1365
|
/** Retained presentation and semantic evidence, kept separate from output. */
|
|
1365
1366
|
export interface PlaybookRetainedSemanticEvidence {
|
|
1366
1367
|
readonly finalText?: string;
|
|
1367
|
-
readonly semanticCandidate: Readonly<Record<string, string
|
|
1368
|
-
readonly guard: string;
|
|
1369
|
-
};
|
|
1368
|
+
readonly semanticCandidate: Readonly<Record<string, string>>;
|
|
1370
1369
|
}
|
|
1371
1370
|
|
|
1372
1371
|
export type PlaybookSemanticReconciliationReason =
|
|
1372
|
+
| 'no-matching-outcome'
|
|
1373
1373
|
| 'missing-presentation-evidence'
|
|
1374
1374
|
| 'missing-repository-receipt'
|
|
1375
1375
|
| 'invalid-repository-receipt'
|
|
@@ -1468,7 +1468,7 @@ function snapshotSemanticCandidate(
|
|
|
1468
1468
|
}
|
|
1469
1469
|
|
|
1470
1470
|
function retainedSemanticEvidence(
|
|
1471
|
-
candidate: Readonly<Record<string, string
|
|
1471
|
+
candidate: Readonly<Record<string, string>>,
|
|
1472
1472
|
finalText: unknown,
|
|
1473
1473
|
): PlaybookRetainedSemanticEvidence {
|
|
1474
1474
|
return Object.freeze({
|
|
@@ -1654,6 +1654,23 @@ function unresolvedSemanticEvidence(
|
|
|
1654
1654
|
export function reconcilePlaybookSemanticEvidence(
|
|
1655
1655
|
input: PlaybookSemanticEvidenceInput,
|
|
1656
1656
|
): PlaybookSemanticReconciliation {
|
|
1657
|
+
let report: JsonValue;
|
|
1658
|
+
try {
|
|
1659
|
+
report = snapshotJsonValue(input.semanticCandidate, 'semantic candidate');
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
semanticCandidateStructureError(`must be detached plain JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
1662
|
+
}
|
|
1663
|
+
if (isRecord(report) && Object.keys(report).length === 1 &&
|
|
1664
|
+
typeof report.blocked === 'string' && report.blocked.trim().length > 0) {
|
|
1665
|
+
return unresolvedSemanticEvidence(
|
|
1666
|
+
'no-matching-outcome',
|
|
1667
|
+
retainedSemanticEvidence(report as Readonly<Record<string, string>>, input.finalText),
|
|
1668
|
+
assertPlaybookFailureCause({
|
|
1669
|
+
code: 'runtime-defect',
|
|
1670
|
+
evidence: { reason: `No declared outcome matches: ${report.blocked}` },
|
|
1671
|
+
}),
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1657
1674
|
const { candidate, outcome } = snapshotSemanticCandidate(
|
|
1658
1675
|
input.semanticCandidate,
|
|
1659
1676
|
input.outcomes,
|
|
@@ -1859,6 +1876,7 @@ function effectBoundary(value: unknown, index: number): PlaybookEffectBoundary {
|
|
|
1859
1876
|
'baseline',
|
|
1860
1877
|
'after',
|
|
1861
1878
|
'physicalReceipt',
|
|
1879
|
+
'restored',
|
|
1862
1880
|
'finalText',
|
|
1863
1881
|
'semanticCandidate',
|
|
1864
1882
|
'initialSemanticCandidate',
|
|
@@ -1939,6 +1957,15 @@ function effectBoundary(value: unknown, index: number): PlaybookEffectBoundary {
|
|
|
1939
1957
|
if (own(value, 'finalText') && typeof value.finalText !== 'string') {
|
|
1940
1958
|
throw new TypeError(`${path}.finalText must be a string`);
|
|
1941
1959
|
}
|
|
1960
|
+
if (own(value, 'restored')) {
|
|
1961
|
+
const restored = effectObservation(value.restored, `${path}.restored`);
|
|
1962
|
+
if (!jsonValuesEqual(restored as unknown as JsonValue, baseline as unknown as JsonValue) ||
|
|
1963
|
+
!isRecord(value.physicalReceipt) || after?.head !== baseline.head ||
|
|
1964
|
+
(value.dispositions as string[]).some((item) => item !== 'unchanged') ||
|
|
1965
|
+
value.cohortId !== undefined || value.logicalOperationId !== undefined) {
|
|
1966
|
+
throw new TypeError(`${path}.restored requires a standalone read-only call restored exactly without commit movement`);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1942
1969
|
if (own(value, 'initialSemanticCandidate')) {
|
|
1943
1970
|
if (
|
|
1944
1971
|
!own(value, 'semanticCandidate') ||
|
|
@@ -2359,6 +2386,7 @@ export function isPlaybookEffectLedgerMonotonicExtension(
|
|
|
2359
2386
|
const boundaryEvidenceKeys = [
|
|
2360
2387
|
'after',
|
|
2361
2388
|
'physicalReceipt',
|
|
2389
|
+
'restored',
|
|
2362
2390
|
'finalText',
|
|
2363
2391
|
'initialSemanticCandidate',
|
|
2364
2392
|
] as const;
|
|
@@ -2567,6 +2595,7 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
2567
2595
|
'retainedEffectSourceSessionId',
|
|
2568
2596
|
'retainedEffectReconciliation',
|
|
2569
2597
|
'failedEffectAttempt',
|
|
2598
|
+
'recoveryCheckpoint',
|
|
2570
2599
|
'suspendedCall',
|
|
2571
2600
|
],
|
|
2572
2601
|
'runtime snapshot',
|
|
@@ -2854,6 +2883,27 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
2854
2883
|
}
|
|
2855
2884
|
failedEffectAttempt = Object.freeze({ boundaryPrefix, attemptId });
|
|
2856
2885
|
}
|
|
2886
|
+
let recoveryCheckpoint: PlaybookRuntimeSnapshot['recoveryCheckpoint'];
|
|
2887
|
+
if (own(snapshot, 'recoveryCheckpoint')) {
|
|
2888
|
+
const checkpoint = snapshot.recoveryCheckpoint;
|
|
2889
|
+
if ((state.stateId !== 'failed' && pendingBossQuestions.length === 0) || !isRecord(checkpoint)) {
|
|
2890
|
+
throw new TypeError('runtime recoveryCheckpoint requires an interrupted state and an object');
|
|
2891
|
+
}
|
|
2892
|
+
rejectUnknownKeys(checkpoint, ['stateId', 'prompt', 'machine', 'boundaryPrefix', 'result', 'id', 'delivered'], 'runtime recoveryCheckpoint');
|
|
2893
|
+
const stateId = requireNonEmptyString(checkpoint.stateId, 'recoveryCheckpoint.stateId');
|
|
2894
|
+
const prompt = requireNonEmptyString(checkpoint.prompt, 'recoveryCheckpoint.prompt');
|
|
2895
|
+
const boundaryPrefix = checkpoint.boundaryPrefix;
|
|
2896
|
+
if (!Number.isSafeInteger(boundaryPrefix) || typeof boundaryPrefix !== 'number' ||
|
|
2897
|
+
boundaryPrefix < 0 || boundaryPrefix > effectLedger.boundaries.length ||
|
|
2898
|
+
!isRecord(checkpoint.machine) || checkpoint.machine.status !== 'active' ||
|
|
2899
|
+
checkpoint.machine.value !== stateId) {
|
|
2900
|
+
throw new TypeError('runtime recoveryCheckpoint has an invalid machine or boundary prefix');
|
|
2901
|
+
}
|
|
2902
|
+
if (own(checkpoint, 'delivered') && checkpoint.delivered !== true) {
|
|
2903
|
+
throw new TypeError('recoveryCheckpoint.delivered must be true');
|
|
2904
|
+
}
|
|
2905
|
+
recoveryCheckpoint = Object.freeze({stateId, prompt, boundaryPrefix, machine: snapshotJsonValue(checkpoint.machine), ...(own(checkpoint, 'id') ? { id: effectUuid(checkpoint.id, 'recoveryCheckpoint.id') } : {}), ...(own(checkpoint, 'result') ? { result: snapshotJsonValue(checkpoint.result) } : {}), ...(checkpoint.delivered === true ? { delivered: true as const } : {})});
|
|
2906
|
+
}
|
|
2857
2907
|
const fields = {
|
|
2858
2908
|
playbookId,
|
|
2859
2909
|
machine,
|
|
@@ -2871,6 +2921,7 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
2871
2921
|
...(failedEffectAttempt === undefined
|
|
2872
2922
|
? {}
|
|
2873
2923
|
: { failedEffectAttempt }),
|
|
2924
|
+
...(recoveryCheckpoint === undefined ? {} : { recoveryCheckpoint }),
|
|
2874
2925
|
};
|
|
2875
2926
|
return Object.freeze({
|
|
2876
2927
|
schemaVersion: 4,
|
|
@@ -3004,6 +3055,7 @@ export interface NestedPlaybookBridge<
|
|
|
3004
3055
|
confirmRestore(): void;
|
|
3005
3056
|
/** Complete durable identity; undefined until a normal or restored call suspends. */
|
|
3006
3057
|
getSuspendedCall(): PlaybookSuspendedCall | undefined;
|
|
3058
|
+
checkpointCall(child: PlaybookPendingCall): PlaybookSuspendedCall | undefined;
|
|
3007
3059
|
resume(input: {
|
|
3008
3060
|
callId: string;
|
|
3009
3061
|
result: PlaybookCallResult;
|
|
@@ -4102,6 +4154,9 @@ export function createNestedPlaybookBridge<
|
|
|
4102
4154
|
actorLogic,
|
|
4103
4155
|
getPendingCall: () => pendingIdentity(current),
|
|
4104
4156
|
getSuspendedCall: () => suspendedIdentity(current),
|
|
4157
|
+
checkpointCall: (child) => current && current.callId === child.callId && current.input.playbookId === child.playbookId
|
|
4158
|
+
? { ...child, stateId: current.input.stateId, text: current.input.text, ...(current.turnId === undefined ? {} : { turnId: current.turnId }) }
|
|
4159
|
+
: undefined,
|
|
4105
4160
|
prepareRestore(call) {
|
|
4106
4161
|
// Capture the complete host-owned descriptor before observing or
|
|
4107
4162
|
// mutating bridge state, so a rejected preparation cannot leave state.
|