@sublang/playbook 16.0.0 → 17.1.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 +26 -20
- package/docs/embedding.md +28 -7
- package/package.json +4 -4
- 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/launch-config.js +5 -3
- 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 +5 -5
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +3 -0
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +32 -2
- package/reference/sdlc/code.playbook/playbook-captain.js +729 -284
- package/reference/sdlc/code.playbook/playbook-captain.ts +771 -358
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +4 -4
- 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.playbook/decide.fsm.d.ts +5 -5
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +5 -5
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +5 -5
- package/slc/gears2fsm.md +5 -5
- package/slc/link.md +77 -33
- package/src/runtime.d.ts +46 -2
- package/src/runtime.ts +42 -2
- package/src/xstate-playbook-runtime.d.ts +9 -8
- package/src/xstate-playbook-runtime.js +471 -215
- package/src/xstate-playbook-runtime.ts +525 -238
- package/src/xstate-runtime.d.ts +3 -4
- package/src/xstate-runtime.js +52 -0
- package/src/xstate-runtime.ts +59 -4
|
@@ -66,6 +66,8 @@ import type {
|
|
|
66
66
|
PlaybookEffectLogicalOperation,
|
|
67
67
|
PlaybookFailureCause,
|
|
68
68
|
PlaybookPendingBossQuestion,
|
|
69
|
+
PlaybookPendingCall,
|
|
70
|
+
PlaybookStepRecord,
|
|
69
71
|
PlaybookPorts,
|
|
70
72
|
PlaybookRepositoryReceipt,
|
|
71
73
|
PlaybookRunResult,
|
|
@@ -544,6 +546,11 @@ interface XStateRepositoryCohortResult<T> {
|
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
export interface XStateRepositoryCapability {
|
|
549
|
+
observe?(): Promise<PlaybookRepositoryReceipt['baseline']>;
|
|
550
|
+
acquire?(options: { readonly signal: AbortSignal }): Promise<{
|
|
551
|
+
assertOwner(): Promise<void>;
|
|
552
|
+
release(): Promise<void>;
|
|
553
|
+
}>;
|
|
547
554
|
runExclusive<T>(options: {
|
|
548
555
|
readonly signal: AbortSignal;
|
|
549
556
|
readonly effectBoundary: XStateEffectBoundarySeed;
|
|
@@ -950,14 +957,7 @@ interface XStatePlaybookRuntimeSpecBase<TOptions> {
|
|
|
950
957
|
entryEvent?: {
|
|
951
958
|
type: string;
|
|
952
959
|
textField: string;
|
|
953
|
-
/**
|
|
954
|
-
* DR-034: the FSM context member this machine's entry action copies the
|
|
955
|
-
* exact Boss text into. Where it is named, the failure-state retry
|
|
956
|
-
* builds its payload from that member of the live snapshot instead of
|
|
957
|
-
* from the process-local recorded event, so the action derives the same
|
|
958
|
-
* before and after `restore`. Absent: the recorded event stays the
|
|
959
|
-
* source and the action lives only as long as the process.
|
|
960
|
-
*/
|
|
960
|
+
/** @deprecated Step checkpoints now carry the accepted input directly. */
|
|
961
961
|
contextField?: string;
|
|
962
962
|
};
|
|
963
963
|
/**
|
|
@@ -1831,6 +1831,7 @@ function buildGovernedJudgePrompt(
|
|
|
1831
1831
|
'```',
|
|
1832
1832
|
'',
|
|
1833
1833
|
'Pick exactly one declared `guard` and reply with exactly that outcome\'s reply shape below: `guard` plus its semantic-owned fields and nothing else.',
|
|
1834
|
+
'If no declared outcome matches the player output, instead reply exactly {"blocked":"explain the missing outcome or contradictory requirements"}. Never force a match or invent a guard. A blocked reply pauses the playbook for Boss; it is not completion.',
|
|
1834
1835
|
'A field listed as runtime-supplied is owned by presentation, effect, or runtime evidence; the runtime fills it itself, and a reply that includes one is structurally invalid.',
|
|
1835
1836
|
'',
|
|
1836
1837
|
];
|
|
@@ -1979,6 +1980,7 @@ interface PlayerBridgeSpec {
|
|
|
1979
1980
|
adjudication: PlayerAdjudicationSpec;
|
|
1980
1981
|
resumableStateIds: ReadonlySet<string>;
|
|
1981
1982
|
allowsCorrectiveReplay?: (result: PlayerResult) => boolean;
|
|
1983
|
+
run?: (input: PlaybookPlayerInput, signal: AbortSignal, execute: () => Promise<PlaybookActorOutput>) => Promise<PlaybookActorOutput>;
|
|
1982
1984
|
}
|
|
1983
1985
|
|
|
1984
1986
|
export function createPlayerBridge(
|
|
@@ -1988,8 +1990,7 @@ export function createPlayerBridge(
|
|
|
1988
1990
|
boundary?: RuntimeBoundaryCalls,
|
|
1989
1991
|
onControlPlaneError?: (error: unknown) => void,
|
|
1990
1992
|
): PromiseActorLogic<PlaybookActorOutput, PlaybookPlayerInput> {
|
|
1991
|
-
|
|
1992
|
-
async ({ input, signal }) => {
|
|
1993
|
+
const execute = async ({ input, signal }: { input: PlaybookPlayerInput; signal: AbortSignal }): Promise<PlaybookActorOutput> => {
|
|
1993
1994
|
const activeSignal = combineAbortSignals(signal, getActiveSignal?.());
|
|
1994
1995
|
let roleId: string;
|
|
1995
1996
|
let prompt: string;
|
|
@@ -2079,8 +2080,9 @@ export function createPlayerBridge(
|
|
|
2079
2080
|
}
|
|
2080
2081
|
throw error;
|
|
2081
2082
|
}
|
|
2082
|
-
}
|
|
2083
|
-
)
|
|
2083
|
+
};
|
|
2084
|
+
return fromPromise<PlaybookActorOutput, PlaybookPlayerInput>((args) => spec.run
|
|
2085
|
+
? spec.run(args.input, args.signal, () => execute(args)) : execute(args));
|
|
2084
2086
|
}
|
|
2085
2087
|
|
|
2086
2088
|
// ---------------------------------------------------------------------------
|
|
@@ -3224,11 +3226,12 @@ function makeDefaultStatusesForState(
|
|
|
3224
3226
|
}
|
|
3225
3227
|
return [
|
|
3226
3228
|
...statuses,
|
|
3227
|
-
{ message: `${askerLabel(pending.asker)} asks: ${pending.question}
|
|
3229
|
+
{ message: `${askerLabel(pending.asker)} asks: ${pending.question}`, data: { kind: 'boss-question' } },
|
|
3228
3230
|
{
|
|
3229
3231
|
message:
|
|
3230
3232
|
`◆ awaiting Boss reply · ${pending.resumeStateId} · ` +
|
|
3231
3233
|
`${askerLabel(pending.asker)} · ${pending.sourceItem}`,
|
|
3234
|
+
data: { kind: 'boss-question' },
|
|
3232
3235
|
},
|
|
3233
3236
|
];
|
|
3234
3237
|
}
|
|
@@ -4106,11 +4109,13 @@ export function createXStatePlaybookRuntime<
|
|
|
4106
4109
|
statuses.push(
|
|
4107
4110
|
{
|
|
4108
4111
|
message: `${askerLabel(pending.asker)} asks: ${pending.question}`,
|
|
4112
|
+
data: { kind: 'boss-question' },
|
|
4109
4113
|
},
|
|
4110
4114
|
{
|
|
4111
4115
|
message:
|
|
4112
4116
|
`◆ awaiting Boss reply · ${pending.resumeStateId} · ` +
|
|
4113
4117
|
`${askerLabel(pending.asker)} · ${pending.sourceItem}`,
|
|
4118
|
+
data: { kind: 'boss-question' },
|
|
4114
4119
|
},
|
|
4115
4120
|
);
|
|
4116
4121
|
}
|
|
@@ -4210,6 +4215,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4210
4215
|
`${label} current host effect ledger`,
|
|
4211
4216
|
);
|
|
4212
4217
|
let effectLedgerMirror = currentEffectLedger();
|
|
4218
|
+
let recoveryCheckpoint: PlaybookRuntimeSnapshot['recoveryCheckpoint'];
|
|
4213
4219
|
let retainedEffectSourceSessionId: string | undefined;
|
|
4214
4220
|
let retainedEffectReconciliation:
|
|
4215
4221
|
| PlaybookRuntimeSnapshot['retainedEffectReconciliation']
|
|
@@ -4363,12 +4369,6 @@ export function createXStatePlaybookRuntime<
|
|
|
4363
4369
|
let playbookCallSequence = 0;
|
|
4364
4370
|
let captainCallSequence = 0;
|
|
4365
4371
|
let applyCallSequence = 0;
|
|
4366
|
-
// DR-029: the last event a public Boss boundary sent into the
|
|
4367
|
-
// machine — classified, deterministic entry, or Boss reply — kept with
|
|
4368
|
-
// its recorded payload so a failure-state retry action can replay the
|
|
4369
|
-
// event that drove the run into `failed`. Process-local: the durable
|
|
4370
|
-
// runtime snapshot does not persist it (PBRT-50).
|
|
4371
|
-
let lastBossEvent: EventObject | undefined;
|
|
4372
4372
|
// DR-029: process-local at-most-once `apply` execution — the accepted receipt
|
|
4373
4373
|
// recorded for each idempotency key, returned verbatim on a repeated
|
|
4374
4374
|
// key. A key whose call settled `rejected` or threw before reaching
|
|
@@ -4605,7 +4605,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4605
4605
|
function boundaryExcludesEffect(boundary: PlaybookEffectBoundary): boolean {
|
|
4606
4606
|
return (
|
|
4607
4607
|
boundary.logicalOperationId === undefined &&
|
|
4608
|
-
boundary.physicalReceipt?.classification === 'unchanged'
|
|
4608
|
+
(boundary.physicalReceipt?.classification === 'unchanged' || boundary.restored !== undefined)
|
|
4609
4609
|
);
|
|
4610
4610
|
}
|
|
4611
4611
|
|
|
@@ -4677,7 +4677,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4677
4677
|
({ sequence }) =>
|
|
4678
4678
|
sequence > reconstructedGovernedPrefixSequence!,
|
|
4679
4679
|
);
|
|
4680
|
-
if (candidate === undefined || candidate.sourceStateId !== state.stateId) {
|
|
4680
|
+
if (candidate === undefined || candidate.restored !== undefined || candidate.sourceStateId !== state.stateId) {
|
|
4681
4681
|
return;
|
|
4682
4682
|
}
|
|
4683
4683
|
const persisted = persistedBoundaryReconciliation(candidate, ledger);
|
|
@@ -6341,6 +6341,401 @@ export function createXStatePlaybookRuntime<
|
|
|
6341
6341
|
);
|
|
6342
6342
|
}
|
|
6343
6343
|
|
|
6344
|
+
let acceptedStepResult: PlaybookRuntimeSnapshot['recoveryCheckpoint'];
|
|
6345
|
+
let requiredRecoveryBaseline: PlaybookRepositoryReceipt['baseline'] | undefined;
|
|
6346
|
+
|
|
6347
|
+
function captureRecoveryCheckpoint(stateId: string, prompt: string, boundaryPrefix?: number): void {
|
|
6348
|
+
if (requiredRecoveryBaseline !== undefined) return;
|
|
6349
|
+
// Capture after the entry emissions drain: the invocation is now a
|
|
6350
|
+
// real active child in XState's persisted snapshot, before host work.
|
|
6351
|
+
const id = recoveryCheckpoint?.stateId === stateId ? recoveryCheckpoint.id : undefined;
|
|
6352
|
+
recoveryCheckpoint = undefined;
|
|
6353
|
+
if (!actor || currentState().stateId !== stateId) return;
|
|
6354
|
+
try {
|
|
6355
|
+
const current = currentEffectLedger();
|
|
6356
|
+
recoveryCheckpoint = deepFreeze({
|
|
6357
|
+
stateId,
|
|
6358
|
+
prompt,
|
|
6359
|
+
...(id === undefined ? {} : { id }),
|
|
6360
|
+
machine: detachPersistedMachineSnapshot(actor.getPersistedSnapshot()),
|
|
6361
|
+
boundaryPrefix: boundaryPrefix ?? current.boundaries.length,
|
|
6362
|
+
});
|
|
6363
|
+
} catch {
|
|
6364
|
+
// An in-memory-only context is still executable; it simply cannot
|
|
6365
|
+
// promise a durable step retry. Keep the legacy control behavior.
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
|
|
6369
|
+
function stepReplayIsSafe(): boolean {
|
|
6370
|
+
if (!recoveryCheckpoint || hasUnresolvedReconciliation()) return false;
|
|
6371
|
+
const current = currentEffectLedger();
|
|
6372
|
+
return current.boundaries
|
|
6373
|
+
.slice(recoveryCheckpoint.boundaryPrefix)
|
|
6374
|
+
.every(
|
|
6375
|
+
(boundary) =>
|
|
6376
|
+
runtimeBoundaryIsOwned(boundary) &&
|
|
6377
|
+
boundary.logicalOperationId === undefined &&
|
|
6378
|
+
(boundary.physicalReceipt?.classification === 'unchanged' || boundary.restored !== undefined),
|
|
6379
|
+
);
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
function repairableReadOnlyBoundary(): PlaybookEffectBoundary | undefined {
|
|
6383
|
+
if (!recoveryCheckpoint || !repositoryCapability?.observe || !repositoryCapability.acquire ||
|
|
6384
|
+
retainedEffectReconciliationRequired || deferredReconciliationOperationId !== undefined) return undefined;
|
|
6385
|
+
const suffix = currentEffectLedger().boundaries.slice(recoveryCheckpoint.boundaryPrefix);
|
|
6386
|
+
if (suffix.length !== 1) return undefined;
|
|
6387
|
+
const saved = suffix[0]!;
|
|
6388
|
+
if (!runtimeBoundaryIsOwned(saved) || saved.restored !== undefined ||
|
|
6389
|
+
saved.sourceStateId !== recoveryCheckpoint.stateId || saved.cohortId !== undefined ||
|
|
6390
|
+
saved.logicalOperationId !== undefined || saved.dispositions.some((item) => item !== 'unchanged') ||
|
|
6391
|
+
saved.physicalReceipt === undefined || saved.after?.head !== saved.baseline.head ||
|
|
6392
|
+
saved.physicalReceipt.classification === 'unchanged' ||
|
|
6393
|
+
[...unresolvedSemanticBoundaryIds].some((id) => id !== saved.boundaryId)) return undefined;
|
|
6394
|
+
return saved;
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
async function confirmReadOnlyRestoration(saved: PlaybookEffectBoundary, signal: AbortSignal): Promise<void> {
|
|
6398
|
+
const claim = await repositoryCapability!.acquire!({ signal });
|
|
6399
|
+
try {
|
|
6400
|
+
await claim.assertOwner();
|
|
6401
|
+
const restored = snapshotJsonValue(await repositoryCapability!.observe!()) as unknown as PlaybookRepositoryReceipt['baseline'];
|
|
6402
|
+
if (!isDeepStrictEqual(restored, saved.baseline)) {
|
|
6403
|
+
throw new Error(`${label} preparation has not restored the stopped step's repository; it remains paused`);
|
|
6404
|
+
}
|
|
6405
|
+
signal.throwIfAborted();
|
|
6406
|
+
effectLedgerMirror = assertPlaybookEffectLedger(await effectLedgerCapability!.writeAhead([
|
|
6407
|
+
{ kind: 'replace-boundaries', replacements: [{ expected: saved, next: { ...saved, restored } }] },
|
|
6408
|
+
]));
|
|
6409
|
+
if (!isDeepStrictEqual(effectLedgerMirror.boundaries.find(({ boundaryId }) => boundaryId === saved.boundaryId)?.restored, restored)) {
|
|
6410
|
+
throw new Error(`${label} repository restoration was not durably acknowledged`);
|
|
6411
|
+
}
|
|
6412
|
+
refreshUnresolvedSemanticReconciliation(effectLedgerMirror);
|
|
6413
|
+
} finally { await claim.release(); }
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6416
|
+
// A completed commit with saved presentation needs only the missing
|
|
6417
|
+
// tool-free judgment. Never repeat the player to recover that evidence.
|
|
6418
|
+
function recoverableJudgment(): PlaybookEffectBoundary | undefined {
|
|
6419
|
+
if (
|
|
6420
|
+
!recoveryCheckpoint || recoveryCheckpoint.result !== undefined || recoveryCheckpoint.delivered ||
|
|
6421
|
+
retainedEffectReconciliationRequired ||
|
|
6422
|
+
deferredReconciliationOperationId !== undefined
|
|
6423
|
+
)
|
|
6424
|
+
return undefined;
|
|
6425
|
+
const suffix = currentEffectLedger().boundaries.slice(
|
|
6426
|
+
recoveryCheckpoint.boundaryPrefix,
|
|
6427
|
+
);
|
|
6428
|
+
if (suffix.length !== 1) return undefined;
|
|
6429
|
+
const saved = suffix[0]!;
|
|
6430
|
+
if (
|
|
6431
|
+
!runtimeBoundaryIsOwned(saved) ||
|
|
6432
|
+
saved.sourceStateId !== recoveryCheckpoint.stateId ||
|
|
6433
|
+
saved.logicalOperationId !== undefined ||
|
|
6434
|
+
saved.correctionBudget.spent ||
|
|
6435
|
+
saved.physicalReceipt?.classification !== 'one-descendant-commit' ||
|
|
6436
|
+
!saved.finalText?.trim() ||
|
|
6437
|
+
governedOutcomesForBoundary(saved) === undefined ||
|
|
6438
|
+
(saved.semanticCandidate !== undefined &&
|
|
6439
|
+
persistedBoundaryReconciliation(saved, currentEffectLedger())
|
|
6440
|
+
?.reconciliation.status !== 'resolved') ||
|
|
6441
|
+
[...unresolvedSemanticBoundaryIds].some((id) => id !== saved.boundaryId)
|
|
6442
|
+
)
|
|
6443
|
+
return undefined;
|
|
6444
|
+
return saved;
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
async function recoverJudgment(
|
|
6448
|
+
saved: PlaybookEffectBoundary,
|
|
6449
|
+
signal: AbortSignal,
|
|
6450
|
+
): Promise<void> {
|
|
6451
|
+
const checkpoint = recoveryCheckpoint!;
|
|
6452
|
+
const persisted = checkpoint.machine as unknown as {
|
|
6453
|
+
children: Record<string, { snapshot: { input: PlaybookPlayerInput } }>;
|
|
6454
|
+
};
|
|
6455
|
+
const input = Object.values(persisted.children)[0]!;
|
|
6456
|
+
const actorInput = input.snapshot.input;
|
|
6457
|
+
const outcomes = governedOutcomesForBoundary(saved)!;
|
|
6458
|
+
const candidate =
|
|
6459
|
+
saved.semanticCandidate ??
|
|
6460
|
+
parseGovernedSemanticCandidate(
|
|
6461
|
+
await boundary.callJudge(
|
|
6462
|
+
'player-output-adjudication',
|
|
6463
|
+
saved.sourceStateId,
|
|
6464
|
+
buildGovernedJudgePrompt(actorInput, saved.finalText!, outcomes),
|
|
6465
|
+
signal,
|
|
6466
|
+
),
|
|
6467
|
+
);
|
|
6468
|
+
const result = reconcilePlaybookSemanticEvidence({
|
|
6469
|
+
outcomes,
|
|
6470
|
+
semanticCandidate: candidate,
|
|
6471
|
+
finalText: saved.finalText,
|
|
6472
|
+
receipt: saved.physicalReceipt,
|
|
6473
|
+
});
|
|
6474
|
+
signal.throwIfAborted();
|
|
6475
|
+
const next = {
|
|
6476
|
+
...saved,
|
|
6477
|
+
semanticCandidate: snapshotJsonValue(result.evidence.semanticCandidate),
|
|
6478
|
+
};
|
|
6479
|
+
const acknowledged = assertPlaybookEffectLedger(
|
|
6480
|
+
await effectLedgerCapability!.writeAhead([
|
|
6481
|
+
{
|
|
6482
|
+
kind: 'replace-boundaries',
|
|
6483
|
+
replacements: [{ expected: saved, next }],
|
|
6484
|
+
},
|
|
6485
|
+
]),
|
|
6486
|
+
);
|
|
6487
|
+
if (
|
|
6488
|
+
!isDeepStrictEqual(
|
|
6489
|
+
acknowledged.boundaries.find((b) => b.boundaryId === saved.boundaryId),
|
|
6490
|
+
next,
|
|
6491
|
+
)
|
|
6492
|
+
) {
|
|
6493
|
+
throw new Error(
|
|
6494
|
+
`${label} recovered judgment was not acknowledged exactly`,
|
|
6495
|
+
);
|
|
6496
|
+
}
|
|
6497
|
+
effectLedgerMirror = acknowledged;
|
|
6498
|
+
refreshUnresolvedSemanticReconciliation(acknowledged);
|
|
6499
|
+
if (result.status !== 'resolved') {
|
|
6500
|
+
const error = attachPlaybookFailureCause(new Error(
|
|
6501
|
+
result.status === 'unresolved' && 'reason' in result.cause.evidence && result.cause.evidence.reason
|
|
6502
|
+
? String(result.cause.evidence.reason)
|
|
6503
|
+
: `${label} saved result could not be reconciled; the playbook remains parked`,
|
|
6504
|
+
), result.status === 'unresolved' ? result.cause : runtimeDefectCause('Saved result still needs a Boss answer'));
|
|
6505
|
+
const paused = detachPersistedMachineSnapshot(actor!.getPersistedSnapshot()) as Record<string, JsonValue>;
|
|
6506
|
+
stopActor();
|
|
6507
|
+
actor = buildActor(runtimePorts!, {
|
|
6508
|
+
...paused, context: { ...(paused.context as Record<string, JsonValue>), lastError: snapshotJsonValue(normalizeErrorFull(error)) },
|
|
6509
|
+
});
|
|
6510
|
+
suppressInspectionEmissions = false;
|
|
6511
|
+
actor.start();
|
|
6512
|
+
throw error;
|
|
6513
|
+
}
|
|
6514
|
+
await resumeCheckpoint({ ...checkpoint, result: snapshotJsonValue(result.output) });
|
|
6515
|
+
}
|
|
6516
|
+
|
|
6517
|
+
async function resumeCheckpoint(checkpoint: NonNullable<PlaybookRuntimeSnapshot['recoveryCheckpoint']>): Promise<void> {
|
|
6518
|
+
acceptedStepResult = checkpoint;
|
|
6519
|
+
try {
|
|
6520
|
+
stopActor();
|
|
6521
|
+
actor = buildActor(runtimePorts!, checkpoint.machine);
|
|
6522
|
+
suppressInspectionEmissions = false;
|
|
6523
|
+
actor.start();
|
|
6524
|
+
await waitForPlaybookQuiescence(actor, { pendingCalls: nestedBridge });
|
|
6525
|
+
} finally { acceptedStepResult = undefined; }
|
|
6526
|
+
}
|
|
6527
|
+
|
|
6528
|
+
function exportRuntimeSnapshot(checkpoint?: { interrupted?: true; child?: PlaybookPendingCall }): PlaybookRuntimeSnapshot | undefined {
|
|
6529
|
+
if (!actor || !session || disposed || disposalPromise !== undefined) {
|
|
6530
|
+
return undefined;
|
|
6531
|
+
}
|
|
6532
|
+
if (activeSignal !== undefined && checkpoint === undefined) return undefined;
|
|
6533
|
+
if (deferredSettlementClosure !== undefined) return undefined;
|
|
6534
|
+
const pendingCall = checkpoint?.child ?? nestedBridge.getPendingCall();
|
|
6535
|
+
const bridgeSuspendedCall = checkpoint?.child ? nestedBridge.checkpointCall(checkpoint.child) : nestedBridge.getSuspendedCall();
|
|
6536
|
+
if ((pendingCall === undefined) !== (bridgeSuspendedCall === undefined)) {
|
|
6537
|
+
return undefined;
|
|
6538
|
+
}
|
|
6539
|
+
let suspendedCall: typeof bridgeSuspendedCall;
|
|
6540
|
+
if (bridgeSuspendedCall !== undefined) {
|
|
6541
|
+
if (
|
|
6542
|
+
pendingCall?.callId !== bridgeSuspendedCall.callId ||
|
|
6543
|
+
pendingCall?.playbookId !== bridgeSuspendedCall.playbookId ||
|
|
6544
|
+
pendingCall?.childSessionId !== bridgeSuspendedCall.childSessionId
|
|
6545
|
+
) {
|
|
6546
|
+
return undefined;
|
|
6547
|
+
}
|
|
6548
|
+
if (!playbookCallTurnIds.has(bridgeSuspendedCall.callId)) {
|
|
6549
|
+
return undefined;
|
|
6550
|
+
}
|
|
6551
|
+
const turnId = playbookCallTurnIds.get(bridgeSuspendedCall.callId);
|
|
6552
|
+
if (
|
|
6553
|
+
bridgeSuspendedCall.turnId !== undefined &&
|
|
6554
|
+
bridgeSuspendedCall.turnId !== turnId
|
|
6555
|
+
) {
|
|
6556
|
+
return undefined;
|
|
6557
|
+
}
|
|
6558
|
+
suspendedCall = {
|
|
6559
|
+
...bridgeSuspendedCall,
|
|
6560
|
+
...(turnId === undefined ? {} : { turnId }),
|
|
6561
|
+
...(hasGovernedPlayerStates
|
|
6562
|
+
? {
|
|
6563
|
+
effectBoundaryPrefixSequence:
|
|
6564
|
+
playbookCallEffectPrefixes.get(
|
|
6565
|
+
bridgeSuspendedCall.callId,
|
|
6566
|
+
) ?? null,
|
|
6567
|
+
}
|
|
6568
|
+
: {}),
|
|
6569
|
+
};
|
|
6570
|
+
}
|
|
6571
|
+
const interrupted = checkpoint?.interrupted && recoveryCheckpoint && machine.root.states.failed;
|
|
6572
|
+
const state = interrupted
|
|
6573
|
+
? normalizePlaybookSnapshot(machine.resolveState({ value: 'failed', context: {} }))
|
|
6574
|
+
: checkpoint?.child ? normalizePlaybookSnapshot(actor.getSnapshot(), { pendingCall }) : currentState();
|
|
6575
|
+
if (state.status !== 'active' || !state.quiescent) return undefined;
|
|
6576
|
+
const context = (actor.getSnapshot() as { context?: unknown })
|
|
6577
|
+
.context as Record<string, unknown>;
|
|
6578
|
+
// DR-063 §2: the failed state's `lastError` is persisted as every live
|
|
6579
|
+
// surface publishes it, completed from the runtime's own cause slot, so
|
|
6580
|
+
// a thrown value that refused the cause still explains itself after a
|
|
6581
|
+
// restart.
|
|
6582
|
+
const machineSnapshot = detachPersistedMachineSnapshot(
|
|
6583
|
+
interrupted ? { ...recoveryCheckpoint!.machine as object, value: 'failed', children: {} } : actor.getPersistedSnapshot(),
|
|
6584
|
+
state.stateId === 'failed'
|
|
6585
|
+
? { lastError: interrupted ? { name: 'InterruptedStep', message: 'The process stopped during this step. Review the recorded work before choosing how to continue.', cause: runtimeDefectCause('The process stopped during this step. Review the recorded work before choosing how to continue.') } : failedStateError(context ?? {}) }
|
|
6586
|
+
: {},
|
|
6587
|
+
);
|
|
6588
|
+
effectLedgerMirror = currentEffectLedger();
|
|
6589
|
+
refreshRetainedEffectReconciliation(effectLedgerMirror);
|
|
6590
|
+
syncDeferredReconciliationOverlay();
|
|
6591
|
+
refreshUnresolvedSemanticReconciliation(effectLedgerMirror);
|
|
6592
|
+
const pendingQuestions = !hasUnresolvedReconciliation()
|
|
6593
|
+
? pendingBossQuestionsFor(state, context ?? {})
|
|
6594
|
+
: [];
|
|
6595
|
+
const interruptedPrefix = interrupted ? activeEffectLedgerPrefixSequence : undefined;
|
|
6596
|
+
const interruptedAttempts = new Set(effectLedgerMirror.boundaries.filter(({ sequence }) => interruptedPrefix !== undefined && sequence > interruptedPrefix).map(({ attemptId }) => attemptId));
|
|
6597
|
+
const failedEffectAttempt = interrupted && interruptedPrefix !== undefined && interruptedAttempts.size <= 1
|
|
6598
|
+
? { boundaryPrefix: interruptedPrefix, attemptId: interruptedAttempts.values().next().value ?? null }
|
|
6599
|
+
:
|
|
6600
|
+
hasGovernedPlayerStates &&
|
|
6601
|
+
!interrupted && state.stateId === 'failed' &&
|
|
6602
|
+
failedAttemptMatchesCurrentLedger(effectLedgerMirror)
|
|
6603
|
+
? {
|
|
6604
|
+
boundaryPrefix: failedEffectBoundaryPrefix!,
|
|
6605
|
+
attemptId: failedGovernedAttemptId ?? null,
|
|
6606
|
+
}
|
|
6607
|
+
: undefined;
|
|
6608
|
+
return {
|
|
6609
|
+
schemaVersion: 4,
|
|
6610
|
+
playbookId: session.playbookId,
|
|
6611
|
+
machine: machineSnapshot,
|
|
6612
|
+
roleResumeTokens: snapshotRoleResumeTokens(),
|
|
6613
|
+
sequences: {
|
|
6614
|
+
trace: traceSequence,
|
|
6615
|
+
turn: turnSequence,
|
|
6616
|
+
judgeCall: judgeCallSequence,
|
|
6617
|
+
playerCall: playerCallSequence,
|
|
6618
|
+
playbookCall: playbookCallSequence,
|
|
6619
|
+
...(declaredActors.has('captain')
|
|
6620
|
+
? { captainCall: captainCallSequence }
|
|
6621
|
+
: {}),
|
|
6622
|
+
},
|
|
6623
|
+
state,
|
|
6624
|
+
pendingBossQuestions: pendingQuestions.map((pending) => ({
|
|
6625
|
+
questionId: pending.questionId,
|
|
6626
|
+
asker: pending.asker,
|
|
6627
|
+
question: pending.question,
|
|
6628
|
+
sourceItem: pending.sourceItem,
|
|
6629
|
+
})),
|
|
6630
|
+
effectLedger: effectLedgerMirror,
|
|
6631
|
+
...(retainedEffectSourceSessionId === undefined
|
|
6632
|
+
? {}
|
|
6633
|
+
: { retainedEffectSourceSessionId }),
|
|
6634
|
+
...(retainedEffectReconciliation === undefined
|
|
6635
|
+
? {}
|
|
6636
|
+
: { retainedEffectReconciliation }),
|
|
6637
|
+
...(failedEffectAttempt === undefined
|
|
6638
|
+
? {}
|
|
6639
|
+
: { failedEffectAttempt }),
|
|
6640
|
+
...((state.stateId === 'failed' || pendingQuestions.length > 0) && recoveryCheckpoint !== undefined
|
|
6641
|
+
? { recoveryCheckpoint } : {}),
|
|
6642
|
+
...(suspendedCall === undefined ? {} : { suspendedCall }),
|
|
6643
|
+
};
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
async function runJournalledStep(
|
|
6647
|
+
kind: PlaybookStepRecord['kind'],
|
|
6648
|
+
input: PlaybookPlayerInput | PlaybookCaptainInput | PlaybookScriptInput,
|
|
6649
|
+
signal: AbortSignal,
|
|
6650
|
+
execute: () => Promise<PlaybookActorOutput>,
|
|
6651
|
+
): Promise<PlaybookActorOutput> {
|
|
6652
|
+
const saved = acceptedStepResult;
|
|
6653
|
+
let output: PlaybookActorOutput;
|
|
6654
|
+
if (saved?.stateId === input.stateId && saved.result !== undefined) {
|
|
6655
|
+
acceptedStepResult = undefined;
|
|
6656
|
+
recoveryCheckpoint = saved;
|
|
6657
|
+
output = snapshotJsonValue(saved.result) as PlaybookActorOutput;
|
|
6658
|
+
} else {
|
|
6659
|
+
const record = savedPorts?.recordStep;
|
|
6660
|
+
await drainEmissions();
|
|
6661
|
+
signal.throwIfAborted();
|
|
6662
|
+
try {
|
|
6663
|
+
captureRecoveryCheckpoint(input.stateId, 'command' in input ? input.command : 'role' in input ? composeBoundPlayerPrompt(input) : composeCaptainPrompt(input));
|
|
6664
|
+
} catch (error) {
|
|
6665
|
+
if (!isAbortFailure(error, signal)) controlPlaneError ??= error;
|
|
6666
|
+
throw error;
|
|
6667
|
+
}
|
|
6668
|
+
if (record) {
|
|
6669
|
+
const id = randomUUID();
|
|
6670
|
+
if (recoveryCheckpoint) recoveryCheckpoint = { ...recoveryCheckpoint, id };
|
|
6671
|
+
const step = { id, kind, stateId: input.stateId };
|
|
6672
|
+
await record(step, exportRuntimeSnapshot({ interrupted: true }));
|
|
6673
|
+
signal.throwIfAborted();
|
|
6674
|
+
const result = snapshotJsonValue(await execute());
|
|
6675
|
+
// Keep the known output if its save loses acknowledgement. A drained
|
|
6676
|
+
// failure can then preserve it without treating the call as unexecuted.
|
|
6677
|
+
if (recoveryCheckpoint?.id === id) recoveryCheckpoint = { ...recoveryCheckpoint, result };
|
|
6678
|
+
await record({ ...step, result });
|
|
6679
|
+
output = result as PlaybookActorOutput;
|
|
6680
|
+
} else {
|
|
6681
|
+
output = await execute();
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
if (recoveryCheckpoint?.stateId === input.stateId) {
|
|
6685
|
+
const { result: _result, ...start } = recoveryCheckpoint;
|
|
6686
|
+
recoveryCheckpoint = { ...start, delivered: true };
|
|
6687
|
+
}
|
|
6688
|
+
return output;
|
|
6689
|
+
}
|
|
6690
|
+
|
|
6691
|
+
function validateRecoveryCheckpoint(
|
|
6692
|
+
checkpoint: PlaybookRuntimeSnapshot['recoveryCheckpoint'],
|
|
6693
|
+
): void {
|
|
6694
|
+
if (!checkpoint) return;
|
|
6695
|
+
const node = machine.root.states[checkpoint.stateId];
|
|
6696
|
+
const persisted = checkpoint.machine as Record<string, JsonValue>;
|
|
6697
|
+
const children = persisted.children;
|
|
6698
|
+
if (
|
|
6699
|
+
!node ||
|
|
6700
|
+
node.invoke.length !== 1 ||
|
|
6701
|
+
!['player', 'captain', 'script'].includes(String(node.invoke[0]!.src)) ||
|
|
6702
|
+
!isPlainObject(children) ||
|
|
6703
|
+
Object.keys(children).length !== 1
|
|
6704
|
+
) {
|
|
6705
|
+
throw new TypeError(
|
|
6706
|
+
`${label} recovery checkpoint does not name one current invocation`,
|
|
6707
|
+
);
|
|
6708
|
+
}
|
|
6709
|
+
const child = children[node.invoke[0]!.id] as
|
|
6710
|
+
Record<string, JsonValue> | undefined;
|
|
6711
|
+
if (
|
|
6712
|
+
!child ||
|
|
6713
|
+
child.src !== node.invoke[0]!.src ||
|
|
6714
|
+
!isPlainObject(child.snapshot) ||
|
|
6715
|
+
child.snapshot.status !== 'active' ||
|
|
6716
|
+
!isPlainObject(child.snapshot.input) ||
|
|
6717
|
+
child.snapshot.input.stateId !== checkpoint.stateId
|
|
6718
|
+
) {
|
|
6719
|
+
throw new TypeError(
|
|
6720
|
+
`${label} recovery checkpoint invocation is incompatible`,
|
|
6721
|
+
);
|
|
6722
|
+
}
|
|
6723
|
+
if (checkpoint.result !== undefined) {
|
|
6724
|
+
const output = checkpoint.result;
|
|
6725
|
+
const input = child.snapshot.input;
|
|
6726
|
+
if (!isPlainObject(output) || typeof output.guard !== 'string' ||
|
|
6727
|
+
!isPlainObject(input.result) || !Object.hasOwn(input.result, output.guard) ||
|
|
6728
|
+
(node.invoke[0]!.src === 'script' &&
|
|
6729
|
+
(!Number.isInteger(output.exitStatus) || Object.keys(output).sort().join(',') !== 'exitStatus,guard' ||
|
|
6730
|
+
output.guard !== Object.keys(input.result)[output.exitStatus === 0 ? 0 : Math.min(1, Object.keys(input.result).length - 1)]))) {
|
|
6731
|
+
throw new TypeError(`${label} saved step result is incompatible with its invocation`);
|
|
6732
|
+
}
|
|
6733
|
+
for (const field of extractFields(String(input.result[output.guard]))) {
|
|
6734
|
+
if (output[field] === undefined || output[field] === null) throw new TypeError(`${label} saved step result is missing ${field}`);
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
|
|
6344
6739
|
function captureEffectLedgerPrefixSequence(): number | undefined {
|
|
6345
6740
|
if (!hasGovernedPlayerStates) return undefined;
|
|
6346
6741
|
try {
|
|
@@ -7153,14 +7548,23 @@ export function createXStatePlaybookRuntime<
|
|
|
7153
7548
|
// captured it under this exclusive claim. A host that supplies
|
|
7154
7549
|
// none states no pre-existing change, exactly as an empty
|
|
7155
7550
|
// baseline projection does.
|
|
7156
|
-
operation: (context) =>
|
|
7157
|
-
|
|
7551
|
+
operation: async (context) => {
|
|
7552
|
+
if (requiredRecoveryBaseline !== undefined) {
|
|
7553
|
+
if (!isDeepStrictEqual(snapshotJsonValue(context.baseline), requiredRecoveryBaseline)) {
|
|
7554
|
+
throw new Error(`${label} repository changed after preparation; the stopped step remains paused`);
|
|
7555
|
+
}
|
|
7556
|
+
requiredRecoveryBaseline = undefined;
|
|
7557
|
+
captureRecoveryCheckpoint(input.stateId, freshPrompt,
|
|
7558
|
+
currentEffectLedger().boundaries.findIndex(({ boundaryId }) => boundaryId === effectBoundary.boundaryId));
|
|
7559
|
+
}
|
|
7560
|
+
return runTracedPlayerCall(
|
|
7158
7561
|
selectedResume,
|
|
7159
7562
|
effectAuthorizedPreExistingBlock(
|
|
7160
7563
|
effectBoundary,
|
|
7161
7564
|
context?.baseline,
|
|
7162
7565
|
),
|
|
7163
|
-
)
|
|
7566
|
+
);
|
|
7567
|
+
},
|
|
7164
7568
|
completeEffectBoundary: completionEvidenceFor(
|
|
7165
7569
|
input,
|
|
7166
7570
|
roleId,
|
|
@@ -7454,6 +7858,7 @@ export function createXStatePlaybookRuntime<
|
|
|
7454
7858
|
): PromiseActorLogic<PlaybookActorOutput, PlaybookPlayerInput> {
|
|
7455
7859
|
return createPlayerBridge(
|
|
7456
7860
|
{
|
|
7861
|
+
run: (input, signal, execute) => runJournalledStep('player', input, signal, execute),
|
|
7457
7862
|
resolveRoleId: requireRoleId,
|
|
7458
7863
|
validateInput: (input) =>
|
|
7459
7864
|
assertGovernedPlayerInput(
|
|
@@ -7487,7 +7892,7 @@ export function createXStatePlaybookRuntime<
|
|
|
7487
7892
|
PlaybookCaptainInput
|
|
7488
7893
|
> {
|
|
7489
7894
|
return fromPromise<PlaybookActorOutput, PlaybookCaptainInput>(
|
|
7490
|
-
|
|
7895
|
+
({ input, signal }) => runJournalledStep('captain', input, signal, async () => {
|
|
7491
7896
|
const active = combineAbortSignals(signal, activeSignal);
|
|
7492
7897
|
try {
|
|
7493
7898
|
await drainEmissions();
|
|
@@ -7564,7 +7969,7 @@ export function createXStatePlaybookRuntime<
|
|
|
7564
7969
|
}
|
|
7565
7970
|
throw error;
|
|
7566
7971
|
}
|
|
7567
|
-
},
|
|
7972
|
+
}),
|
|
7568
7973
|
);
|
|
7569
7974
|
}
|
|
7570
7975
|
|
|
@@ -7578,7 +7983,7 @@ export function createXStatePlaybookRuntime<
|
|
|
7578
7983
|
PlaybookScriptInput
|
|
7579
7984
|
> {
|
|
7580
7985
|
return fromPromise<PlaybookActorOutput, PlaybookScriptInput>(
|
|
7581
|
-
|
|
7986
|
+
({ input, signal }) => runJournalledStep('script', input, signal, async () => {
|
|
7582
7987
|
await drainEmissions();
|
|
7583
7988
|
const active = combineAbortSignals(signal, activeSignal);
|
|
7584
7989
|
const guards = Object.keys(input.result);
|
|
@@ -7767,7 +8172,7 @@ export function createXStatePlaybookRuntime<
|
|
|
7767
8172
|
if (killTimer !== undefined) clearTimeout(killTimer);
|
|
7768
8173
|
}
|
|
7769
8174
|
}
|
|
7770
|
-
},
|
|
8175
|
+
}),
|
|
7771
8176
|
);
|
|
7772
8177
|
}
|
|
7773
8178
|
|
|
@@ -8169,6 +8574,10 @@ export function createXStatePlaybookRuntime<
|
|
|
8169
8574
|
latchRuntimeError(error);
|
|
8170
8575
|
}
|
|
8171
8576
|
if (state.stateId === 'failed') {
|
|
8577
|
+
if (previousState?.stateId !== 'failed' &&
|
|
8578
|
+
previousState?.stateId !== recoveryCheckpoint?.stateId) {
|
|
8579
|
+
recoveryCheckpoint = undefined;
|
|
8580
|
+
}
|
|
8172
8581
|
if (
|
|
8173
8582
|
previousState?.stateId !== 'failed' ||
|
|
8174
8583
|
activeGovernedBoundarySeen
|
|
@@ -8453,7 +8862,7 @@ export function createXStatePlaybookRuntime<
|
|
|
8453
8862
|
activeDeferredContinuation = undefined;
|
|
8454
8863
|
deferInspectionEmissions = false;
|
|
8455
8864
|
deferredInspectionEmissions = [];
|
|
8456
|
-
|
|
8865
|
+
recoveryCheckpoint = undefined;
|
|
8457
8866
|
suppressInspectionEmissions = false;
|
|
8458
8867
|
initialized = false;
|
|
8459
8868
|
traceSequence = 0;
|
|
@@ -8475,6 +8884,9 @@ export function createXStatePlaybookRuntime<
|
|
|
8475
8884
|
event?: EventObject;
|
|
8476
8885
|
deferredRestoreOperationId?: string;
|
|
8477
8886
|
unresolvedEffectAction?: 'reconcile' | 'abandon';
|
|
8887
|
+
retryStep?: true;
|
|
8888
|
+
retryJudgment?: PlaybookEffectBoundary;
|
|
8889
|
+
restoreReadOnly?: PlaybookEffectBoundary;
|
|
8478
8890
|
}
|
|
8479
8891
|
|
|
8480
8892
|
function snapshotCan(snapshot: unknown, event: EventObject): boolean {
|
|
@@ -8486,78 +8898,24 @@ export function createXStatePlaybookRuntime<
|
|
|
8486
8898
|
);
|
|
8487
8899
|
}
|
|
8488
8900
|
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
// falling back to the record, which would make the action depend on the
|
|
8501
|
-
// process again — the very thing this source exists to end.
|
|
8502
|
-
function retryEventFrom(snapshot: unknown): EventObject | undefined {
|
|
8503
|
-
const entryEvent = spec.entryEvent;
|
|
8504
|
-
if (entryEvent?.contextField === undefined) return lastBossEvent;
|
|
8505
|
-
const context = (snapshot as { context?: unknown } | null)?.context;
|
|
8506
|
-
const text = isPlainObject(context)
|
|
8507
|
-
? context[entryEvent.contextField]
|
|
8508
|
-
: undefined;
|
|
8509
|
-
if (typeof text !== 'string' || text.trim() === '') return undefined;
|
|
8510
|
-
return { type: entryEvent.type, [entryEvent.textField]: text };
|
|
8511
|
-
}
|
|
8512
|
-
|
|
8513
|
-
// The failure-state retry entry replays the recorded last classified
|
|
8514
|
-
// event with its recorded payload, or the entry event the declared
|
|
8515
|
-
// context member above sources. A candidate whose event the live
|
|
8516
|
-
// snapshot does not accept — or whose payload the runtime can source
|
|
8517
|
-
// from neither — is excluded rather than completed with invented text.
|
|
8518
|
-
// The caller has already applied the PBRT-71 replay fence.
|
|
8519
|
-
function retryActionFor(
|
|
8520
|
-
snapshot: unknown,
|
|
8521
|
-
stateId: string | undefined,
|
|
8522
|
-
): DerivedControlAction | undefined {
|
|
8523
|
-
if (stateId !== 'failed') return undefined;
|
|
8524
|
-
const retryEvent = retryEventFrom(snapshot);
|
|
8525
|
-
if (retryEvent === undefined) return undefined;
|
|
8526
|
-
if (!snapshotCan(snapshot, retryEvent)) return undefined;
|
|
8527
|
-
// A recorded explicit-state-jump event names the exact state its
|
|
8528
|
-
// replay re-enters: the root BOSS_INTERRUPT shape is a guarded
|
|
8529
|
-
// multi-arm list keyed on `targetId`, so the first configured arm
|
|
8530
|
-
// may label a different state than the one the recorded event
|
|
8531
|
-
// actually resumes.
|
|
8532
|
-
const recordedTargetId =
|
|
8533
|
-
retryEvent.type === JUMP_EVENT_TYPE
|
|
8534
|
-
? (retryEvent as { targetId?: unknown }).targetId
|
|
8535
|
-
: undefined;
|
|
8536
|
-
const target =
|
|
8537
|
-
typeof recordedTargetId === 'string' &&
|
|
8538
|
-
recordedTargetId.trim().length > 0
|
|
8539
|
-
? recordedTargetId
|
|
8540
|
-
: firstTransitionTarget(machine, stateId, retryEvent.type);
|
|
8541
|
-
// PBRT-52: a label is written from a source state description, never
|
|
8542
|
-
// from an identifier. Falling back to the target id — or, with no
|
|
8543
|
-
// resolvable target, to the FSM event type — makes the label *be* the
|
|
8544
|
-
// internal name, which defeats the substitution the label exists for
|
|
8545
|
-
// and puts a machine identifier into Boss-facing text
|
|
8546
|
-
// (CAPPLAY-5). A candidate whose label can only be an id is excluded
|
|
8547
|
-
// exactly like one whose payload cannot be sourced.
|
|
8548
|
-
const description =
|
|
8549
|
-
(target === undefined ? undefined : stateDescriptions.get(target)) ??
|
|
8550
|
-
stateDescriptions.get(stateId);
|
|
8551
|
-
if (description === undefined) return undefined;
|
|
8901
|
+
function retryActionFor(stateId: string | undefined): DerivedControlAction | undefined {
|
|
8902
|
+
if (stateId !== 'failed' || !recoveryCheckpoint) return undefined;
|
|
8903
|
+
const description = stateDescriptions.get(recoveryCheckpoint.stateId);
|
|
8904
|
+
const savedResult = recoveryCheckpoint.result !== undefined;
|
|
8905
|
+
const safe = savedResult
|
|
8906
|
+
? !hasUnresolvedReconciliation() && currentEffectLedger().boundaries.slice(recoveryCheckpoint.boundaryPrefix)
|
|
8907
|
+
.every((boundary) => runtimeBoundaryIsOwned(boundary) && boundary.sourceStateId === recoveryCheckpoint!.stateId)
|
|
8908
|
+
: stepReplayIsSafe();
|
|
8909
|
+
if (description === undefined || !safe) return undefined;
|
|
8910
|
+
const entry = spec.entryEvent;
|
|
8911
|
+
const target = entry && firstTransitionTarget(machine, stateId, entry.type);
|
|
8552
8912
|
return {
|
|
8553
8913
|
action: {
|
|
8554
|
-
id: `retry:${
|
|
8555
|
-
label:
|
|
8556
|
-
// DR-063 §3: retrying a failed call is `ready` — the runtime cannot
|
|
8557
|
-
// see whether the Boss changed the outside world since it failed.
|
|
8914
|
+
id: target === recoveryCheckpoint.stateId ? `retry:${entry!.type}` : 'retry:step',
|
|
8915
|
+
label: `${savedResult ? 'Continue from saved result' : 'Retry'}: ${description}`,
|
|
8558
8916
|
standing: 'ready',
|
|
8559
8917
|
},
|
|
8560
|
-
|
|
8918
|
+
retryStep: true,
|
|
8561
8919
|
};
|
|
8562
8920
|
}
|
|
8563
8921
|
|
|
@@ -8614,6 +8972,18 @@ export function createXStatePlaybookRuntime<
|
|
|
8614
8972
|
return [];
|
|
8615
8973
|
}
|
|
8616
8974
|
const derived: DerivedControlAction[] = [];
|
|
8975
|
+
const repairable = state.stateId === 'failed' ? repairableReadOnlyBoundary() : undefined;
|
|
8976
|
+
if (repairable !== undefined) derived.push({
|
|
8977
|
+
action: { id: 'retry:restored-step', label: 'Restore the repository and retry the read-only step', standing: 'ready' },
|
|
8978
|
+
retryStep: true,
|
|
8979
|
+
restoreReadOnly: repairable,
|
|
8980
|
+
});
|
|
8981
|
+
const judgment = state.stateId === 'failed' ? recoverableJudgment() : undefined;
|
|
8982
|
+
if (judgment !== undefined) derived.push({
|
|
8983
|
+
action: { id: 'retry:adjudication', label: 'Retry assessment of the saved result', standing: 'ready' },
|
|
8984
|
+
retryJudgment: judgment,
|
|
8985
|
+
});
|
|
8986
|
+
if (judgment !== undefined && !hasUnresolvedReconciliation()) return derived;
|
|
8617
8987
|
if (hasUnresolvedReconciliation()) {
|
|
8618
8988
|
const operation =
|
|
8619
8989
|
deferredReconciliationOperationId === undefined
|
|
@@ -8650,17 +9020,13 @@ export function createXStatePlaybookRuntime<
|
|
|
8650
9020
|
);
|
|
8651
9021
|
return derived;
|
|
8652
9022
|
}
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
//
|
|
8656
|
-
//
|
|
8657
|
-
// which revalidates against this list, accepts neither — so no
|
|
8658
|
-
// control can replay a player call whose effect may already exist.
|
|
9023
|
+
const retry = retryActionFor(state.stateId);
|
|
9024
|
+
if (retry !== undefined) derived.push(retry);
|
|
9025
|
+
// A jump can repeat earlier work, unlike checkpoint retry. Its whole
|
|
9026
|
+
// failed attempt must therefore still pass the replay fence.
|
|
8659
9027
|
if (state.stateId === 'failed' && !failedAttemptAllowsReplay()) {
|
|
8660
9028
|
return derived;
|
|
8661
9029
|
}
|
|
8662
|
-
const retry = retryActionFor(snapshot, state.stateId);
|
|
8663
|
-
if (retry !== undefined) derived.push(retry);
|
|
8664
9030
|
// Jump entries: resumable targets whose explicit-state-jump event the
|
|
8665
9031
|
// live snapshot accepts (state guards included), sent with the
|
|
8666
9032
|
// advertised target id and optional textual fields omitted.
|
|
@@ -8792,6 +9158,7 @@ export function createXStatePlaybookRuntime<
|
|
|
8792
9158
|
boundSession.playbookId,
|
|
8793
9159
|
{ allowSuspendedCall: true },
|
|
8794
9160
|
);
|
|
9161
|
+
validateRecoveryCheckpoint(boundSnapshot.recoveryCheckpoint);
|
|
8795
9162
|
if (
|
|
8796
9163
|
kind === 'adopt' &&
|
|
8797
9164
|
effectLedgerCapability !== undefined &&
|
|
@@ -8899,6 +9266,7 @@ export function createXStatePlaybookRuntime<
|
|
|
8899
9266
|
initInFlight = initialization;
|
|
8900
9267
|
const initTask = (async () => {
|
|
8901
9268
|
session = boundSession;
|
|
9269
|
+
recoveryCheckpoint = boundSnapshot.recoveryCheckpoint;
|
|
8902
9270
|
syncDeferredReconciliationOverlay();
|
|
8903
9271
|
refreshUnresolvedSemanticReconciliation(effectLedgerMirror);
|
|
8904
9272
|
prepareReconstructedGovernedDelivery(
|
|
@@ -9133,6 +9501,7 @@ export function createXStatePlaybookRuntime<
|
|
|
9133
9501
|
signal: AbortSignal,
|
|
9134
9502
|
turnId: number,
|
|
9135
9503
|
classificationLine?: string,
|
|
9504
|
+
onAccepted?: () => void,
|
|
9136
9505
|
): Promise<'continued' | 'unresolved'> {
|
|
9137
9506
|
if (repositoryCapability === undefined) {
|
|
9138
9507
|
throw new Error(
|
|
@@ -9183,6 +9552,7 @@ export function createXStatePlaybookRuntime<
|
|
|
9183
9552
|
effectBoundary.roleId, boundPlayerId,
|
|
9184
9553
|
);
|
|
9185
9554
|
continuationStarted = true;
|
|
9555
|
+
onAccepted?.();
|
|
9186
9556
|
actor!.send(event);
|
|
9187
9557
|
// The host has durably started this boundary and the FSM has
|
|
9188
9558
|
// moved: publish the buffered classification line and the
|
|
@@ -9519,114 +9889,7 @@ export function createXStatePlaybookRuntime<
|
|
|
9519
9889
|
// Defined only at a safe capture point — initialized, not disposing
|
|
9520
9890
|
// or disposed, no active public boundary, and the actor quiescent with
|
|
9521
9891
|
// status `active`.
|
|
9522
|
-
exportSnapshot():
|
|
9523
|
-
if (!actor || !session || disposed || disposalPromise !== undefined) {
|
|
9524
|
-
return undefined;
|
|
9525
|
-
}
|
|
9526
|
-
if (activeSignal !== undefined) return undefined;
|
|
9527
|
-
if (deferredSettlementClosure !== undefined) return undefined;
|
|
9528
|
-
const pendingCall = nestedBridge.getPendingCall();
|
|
9529
|
-
const bridgeSuspendedCall = nestedBridge.getSuspendedCall();
|
|
9530
|
-
if ((pendingCall === undefined) !== (bridgeSuspendedCall === undefined)) {
|
|
9531
|
-
return undefined;
|
|
9532
|
-
}
|
|
9533
|
-
let suspendedCall: typeof bridgeSuspendedCall;
|
|
9534
|
-
if (bridgeSuspendedCall !== undefined) {
|
|
9535
|
-
if (
|
|
9536
|
-
pendingCall?.callId !== bridgeSuspendedCall.callId ||
|
|
9537
|
-
pendingCall?.playbookId !== bridgeSuspendedCall.playbookId ||
|
|
9538
|
-
pendingCall?.childSessionId !== bridgeSuspendedCall.childSessionId
|
|
9539
|
-
) {
|
|
9540
|
-
return undefined;
|
|
9541
|
-
}
|
|
9542
|
-
if (!playbookCallTurnIds.has(bridgeSuspendedCall.callId)) {
|
|
9543
|
-
return undefined;
|
|
9544
|
-
}
|
|
9545
|
-
const turnId = playbookCallTurnIds.get(bridgeSuspendedCall.callId);
|
|
9546
|
-
if (
|
|
9547
|
-
bridgeSuspendedCall.turnId !== undefined &&
|
|
9548
|
-
bridgeSuspendedCall.turnId !== turnId
|
|
9549
|
-
) {
|
|
9550
|
-
return undefined;
|
|
9551
|
-
}
|
|
9552
|
-
suspendedCall = {
|
|
9553
|
-
...bridgeSuspendedCall,
|
|
9554
|
-
...(turnId === undefined ? {} : { turnId }),
|
|
9555
|
-
...(hasGovernedPlayerStates
|
|
9556
|
-
? {
|
|
9557
|
-
effectBoundaryPrefixSequence:
|
|
9558
|
-
playbookCallEffectPrefixes.get(
|
|
9559
|
-
bridgeSuspendedCall.callId,
|
|
9560
|
-
) ?? null,
|
|
9561
|
-
}
|
|
9562
|
-
: {}),
|
|
9563
|
-
};
|
|
9564
|
-
}
|
|
9565
|
-
const state = currentState();
|
|
9566
|
-
if (state.status !== 'active' || !state.quiescent) return undefined;
|
|
9567
|
-
const context = (actor.getSnapshot() as { context?: unknown })
|
|
9568
|
-
.context as Record<string, unknown>;
|
|
9569
|
-
// DR-063 §2: the failed state's `lastError` is persisted as every live
|
|
9570
|
-
// surface publishes it, completed from the runtime's own cause slot, so
|
|
9571
|
-
// a thrown value that refused the cause still explains itself after a
|
|
9572
|
-
// restart.
|
|
9573
|
-
const machineSnapshot = detachPersistedMachineSnapshot(
|
|
9574
|
-
actor.getPersistedSnapshot(),
|
|
9575
|
-
state.stateId === 'failed'
|
|
9576
|
-
? { lastError: failedStateError(context ?? {}) }
|
|
9577
|
-
: {},
|
|
9578
|
-
);
|
|
9579
|
-
effectLedgerMirror = currentEffectLedger();
|
|
9580
|
-
refreshRetainedEffectReconciliation(effectLedgerMirror);
|
|
9581
|
-
syncDeferredReconciliationOverlay();
|
|
9582
|
-
refreshUnresolvedSemanticReconciliation(effectLedgerMirror);
|
|
9583
|
-
const pendingQuestions = !hasUnresolvedReconciliation()
|
|
9584
|
-
? pendingBossQuestionsFor(state, context ?? {})
|
|
9585
|
-
: [];
|
|
9586
|
-
const failedEffectAttempt =
|
|
9587
|
-
hasGovernedPlayerStates &&
|
|
9588
|
-
state.stateId === 'failed' &&
|
|
9589
|
-
failedAttemptMatchesCurrentLedger(effectLedgerMirror)
|
|
9590
|
-
? {
|
|
9591
|
-
boundaryPrefix: failedEffectBoundaryPrefix!,
|
|
9592
|
-
attemptId: failedGovernedAttemptId ?? null,
|
|
9593
|
-
}
|
|
9594
|
-
: undefined;
|
|
9595
|
-
return {
|
|
9596
|
-
schemaVersion: 4,
|
|
9597
|
-
playbookId: session.playbookId,
|
|
9598
|
-
machine: machineSnapshot,
|
|
9599
|
-
roleResumeTokens: snapshotRoleResumeTokens(),
|
|
9600
|
-
sequences: {
|
|
9601
|
-
trace: traceSequence,
|
|
9602
|
-
turn: turnSequence,
|
|
9603
|
-
judgeCall: judgeCallSequence,
|
|
9604
|
-
playerCall: playerCallSequence,
|
|
9605
|
-
playbookCall: playbookCallSequence,
|
|
9606
|
-
...(declaredActors.has('captain')
|
|
9607
|
-
? { captainCall: captainCallSequence }
|
|
9608
|
-
: {}),
|
|
9609
|
-
},
|
|
9610
|
-
state,
|
|
9611
|
-
pendingBossQuestions: pendingQuestions.map((pending) => ({
|
|
9612
|
-
questionId: pending.questionId,
|
|
9613
|
-
asker: pending.asker,
|
|
9614
|
-
question: pending.question,
|
|
9615
|
-
sourceItem: pending.sourceItem,
|
|
9616
|
-
})),
|
|
9617
|
-
effectLedger: effectLedgerMirror,
|
|
9618
|
-
...(retainedEffectSourceSessionId === undefined
|
|
9619
|
-
? {}
|
|
9620
|
-
: { retainedEffectSourceSessionId }),
|
|
9621
|
-
...(retainedEffectReconciliation === undefined
|
|
9622
|
-
? {}
|
|
9623
|
-
: { retainedEffectReconciliation }),
|
|
9624
|
-
...(failedEffectAttempt === undefined
|
|
9625
|
-
? {}
|
|
9626
|
-
: { failedEffectAttempt }),
|
|
9627
|
-
...(suspendedCall === undefined ? {} : { suspendedCall }),
|
|
9628
|
-
};
|
|
9629
|
-
},
|
|
9892
|
+
exportSnapshot: (checkpoint?: { child?: PlaybookPendingCall }) => exportRuntimeSnapshot(checkpoint?.child ? { child: checkpoint.child } : undefined),
|
|
9630
9893
|
|
|
9631
9894
|
// DR-014 §1 / PBRT-45: alternative to `init` that rehydrates an
|
|
9632
9895
|
// exported snapshot under the same immutable session identity.
|
|
@@ -9697,6 +9960,31 @@ export function createXStatePlaybookRuntime<
|
|
|
9697
9960
|
!hasUnresolvedReconciliation()
|
|
9698
9961
|
? stateDescriptionFor(state)
|
|
9699
9962
|
: undefined;
|
|
9963
|
+
const actions = deriveControlActions(snapshot).map(({ action }) => action);
|
|
9964
|
+
const retry = actions.find(({ id, standing }) =>
|
|
9965
|
+
id.startsWith('retry:') &&
|
|
9966
|
+
(standing ?? 'ready') === 'ready');
|
|
9967
|
+
const stoppedBoundary = recoveryCheckpoint === undefined ? undefined :
|
|
9968
|
+
currentEffectLedger().boundaries.slice(recoveryCheckpoint.boundaryPrefix)
|
|
9969
|
+
.filter((boundary) => runtimeBoundaryIsOwned(boundary) && boundary.sourceStateId === recoveryCheckpoint!.stateId).at(-1);
|
|
9970
|
+
const recovery = recoveryCheckpoint === undefined ||
|
|
9971
|
+
(pendingQuestions.length === 0 && retry === undefined) ? undefined : {
|
|
9972
|
+
prompt: recoveryCheckpoint.prompt,
|
|
9973
|
+
...(stoppedBoundary === undefined ? {} : { evidence: snapshotJsonValue({
|
|
9974
|
+
declaredResults: stoppedBoundary.sourceOutcomeSchema,
|
|
9975
|
+
...(stoppedBoundary.finalText === undefined ? {} : { savedPlayerText: stoppedBoundary.finalText }),
|
|
9976
|
+
...(stoppedBoundary.semanticCandidate === undefined ? {} : { selectedResult: stoppedBoundary.semanticCandidate }),
|
|
9977
|
+
...(stoppedBoundary.physicalReceipt === undefined ? {} : { repositoryReceipt: stoppedBoundary.physicalReceipt }),
|
|
9978
|
+
}) }),
|
|
9979
|
+
...(repairableReadOnlyBoundary() === undefined ? {} : {
|
|
9980
|
+
preparation: `This read-only step changed the repository. Restore its exact original HEAD and visible files before retrying; do not accept its saved result. Preserve user work. You may put generated outputs aside and prevent their return using local environment or ignore settings. Original observation: ${JSON.stringify(repairableReadOnlyBoundary()!.baseline)}. Observed after the failed step: ${JSON.stringify(repairableReadOnlyBoundary()!.after)}.`,
|
|
9981
|
+
}),
|
|
9982
|
+
...(stateDescriptions.get(recoveryCheckpoint.stateId) === undefined ? {} :
|
|
9983
|
+
{ description: stateDescriptions.get(recoveryCheckpoint.stateId)! }),
|
|
9984
|
+
continuation: pendingQuestions.length === 0
|
|
9985
|
+
? { kind: 'runtime' as const, actionId: retry!.id }
|
|
9986
|
+
: { kind: 'reply' as const },
|
|
9987
|
+
};
|
|
9700
9988
|
return deepFreeze({
|
|
9701
9989
|
state,
|
|
9702
9990
|
...(stateDescription === undefined ? {} : { stateDescription }),
|
|
@@ -9710,7 +9998,8 @@ export function createXStatePlaybookRuntime<
|
|
|
9710
9998
|
sourceItem: pending.sourceItem,
|
|
9711
9999
|
})),
|
|
9712
10000
|
...(lastError !== undefined ? { lastError } : {}),
|
|
9713
|
-
actions
|
|
10001
|
+
actions,
|
|
10002
|
+
...(recovery === undefined ? {} : { recovery }),
|
|
9714
10003
|
});
|
|
9715
10004
|
},
|
|
9716
10005
|
|
|
@@ -9911,7 +10200,10 @@ export function createXStatePlaybookRuntime<
|
|
|
9911
10200
|
accepted = true;
|
|
9912
10201
|
try {
|
|
9913
10202
|
let run: PlaybookRunResult;
|
|
9914
|
-
if (candidate.
|
|
10203
|
+
if (candidate.retryJudgment !== undefined) {
|
|
10204
|
+
await recoverJudgment(candidate.retryJudgment, signal);
|
|
10205
|
+
run = runResultFor(settledOutcome(signal));
|
|
10206
|
+
} else if (candidate.unresolvedEffectAction === 'abandon') {
|
|
9915
10207
|
signal.throwIfAborted();
|
|
9916
10208
|
run = runResultFor('unresolved-effect');
|
|
9917
10209
|
} else if (
|
|
@@ -9934,6 +10226,19 @@ export function createXStatePlaybookRuntime<
|
|
|
9934
10226
|
? 'no-action'
|
|
9935
10227
|
: 'quiescent',
|
|
9936
10228
|
);
|
|
10229
|
+
} else if (candidate.retryStep) {
|
|
10230
|
+
if (candidate.restoreReadOnly !== undefined) {
|
|
10231
|
+
await confirmReadOnlyRestoration(candidate.restoreReadOnly, signal);
|
|
10232
|
+
}
|
|
10233
|
+
const checkpoint = recoveryCheckpoint!;
|
|
10234
|
+
requiredRecoveryBaseline = currentEffectLedger().boundaries
|
|
10235
|
+
.slice(checkpoint.boundaryPrefix).find(({ restored }) => restored !== undefined)?.restored;
|
|
10236
|
+
try {
|
|
10237
|
+
await resumeCheckpoint(checkpoint);
|
|
10238
|
+
} finally {
|
|
10239
|
+
requiredRecoveryBaseline = undefined;
|
|
10240
|
+
}
|
|
10241
|
+
run = runResultFor(settledOutcome(signal));
|
|
9937
10242
|
} else {
|
|
9938
10243
|
actor.send(candidate.event!);
|
|
9939
10244
|
await waitForPlaybookQuiescence(actor, {
|
|
@@ -9946,6 +10251,7 @@ export function createXStatePlaybookRuntime<
|
|
|
9946
10251
|
run.outcome === 'failed' || run.outcome === 'aborted'
|
|
9947
10252
|
? {
|
|
9948
10253
|
disposition: 'failed',
|
|
10254
|
+
run,
|
|
9949
10255
|
error:
|
|
9950
10256
|
('error' in run ? run.error : undefined) ??
|
|
9951
10257
|
normalizeError(
|
|
@@ -10059,9 +10365,11 @@ export function createXStatePlaybookRuntime<
|
|
|
10059
10365
|
async handleBossInput({
|
|
10060
10366
|
text,
|
|
10061
10367
|
signal,
|
|
10368
|
+
onAccepted,
|
|
10062
10369
|
}: {
|
|
10063
10370
|
text: string;
|
|
10064
10371
|
signal: AbortSignal;
|
|
10372
|
+
onAccepted?: () => void;
|
|
10065
10373
|
}): Promise<PlaybookRunResult> {
|
|
10066
10374
|
if (!actor || !savedPorts) {
|
|
10067
10375
|
throw new Error(
|
|
@@ -10207,16 +10515,9 @@ export function createXStatePlaybookRuntime<
|
|
|
10207
10515
|
signal,
|
|
10208
10516
|
turnId,
|
|
10209
10517
|
statusLine,
|
|
10518
|
+
onAccepted,
|
|
10210
10519
|
);
|
|
10211
10520
|
if (continuation === 'continued') {
|
|
10212
|
-
try {
|
|
10213
|
-
lastBossEvent = snapshotJsonValue(
|
|
10214
|
-
event,
|
|
10215
|
-
'recorded Boss event',
|
|
10216
|
-
) as unknown as EventObject;
|
|
10217
|
-
} catch {
|
|
10218
|
-
lastBossEvent = undefined;
|
|
10219
|
-
}
|
|
10220
10521
|
if (controlPlaneError !== undefined) {
|
|
10221
10522
|
throw controlPlaneError;
|
|
10222
10523
|
}
|
|
@@ -10226,7 +10527,6 @@ export function createXStatePlaybookRuntime<
|
|
|
10226
10527
|
: 'no-action',
|
|
10227
10528
|
);
|
|
10228
10529
|
} else {
|
|
10229
|
-
lastBossEvent = undefined;
|
|
10230
10530
|
result = runResultFor('no-action');
|
|
10231
10531
|
}
|
|
10232
10532
|
handledDeferred = true;
|
|
@@ -10239,7 +10539,6 @@ export function createXStatePlaybookRuntime<
|
|
|
10239
10539
|
deferredOperation.operationId,
|
|
10240
10540
|
signal,
|
|
10241
10541
|
);
|
|
10242
|
-
lastBossEvent = undefined;
|
|
10243
10542
|
result = runResultFor('no-action');
|
|
10244
10543
|
handledDeferred = true;
|
|
10245
10544
|
}
|
|
@@ -10268,18 +10567,7 @@ export function createXStatePlaybookRuntime<
|
|
|
10268
10567
|
suppressInspectionEmissions = false;
|
|
10269
10568
|
actor.start();
|
|
10270
10569
|
}
|
|
10271
|
-
|
|
10272
|
-
// as the retry-replay source. Recording is sanitizing, not
|
|
10273
|
-
// load-bearing: an override classifier's non-JSON-safe event is
|
|
10274
|
-
// simply not recorded, and the turn proceeds unchanged.
|
|
10275
|
-
try {
|
|
10276
|
-
lastBossEvent = snapshotJsonValue(
|
|
10277
|
-
event,
|
|
10278
|
-
'recorded Boss event',
|
|
10279
|
-
) as unknown as EventObject;
|
|
10280
|
-
} catch {
|
|
10281
|
-
lastBossEvent = undefined;
|
|
10282
|
-
}
|
|
10570
|
+
onAccepted?.();
|
|
10283
10571
|
actor.send(event);
|
|
10284
10572
|
await waitForPlaybookQuiescence(actor, {
|
|
10285
10573
|
pendingCalls: nestedBridge,
|
|
@@ -10587,7 +10875,6 @@ export function createXStatePlaybookRuntime<
|
|
|
10587
10875
|
failedGovernedAttemptId = undefined;
|
|
10588
10876
|
controlPlaneError = undefined;
|
|
10589
10877
|
emissionFailure = undefined;
|
|
10590
|
-
lastBossEvent = undefined;
|
|
10591
10878
|
retainedEffectSourceSessionId = undefined;
|
|
10592
10879
|
retainedEffectReconciliation = undefined;
|
|
10593
10880
|
retainedEffectReconciliationRequired = false;
|