@sublang/playbook 14.1.0 → 15.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 -2
- package/docs/configuration.md +15 -9
- package/docs/embedding.md +7 -5
- package/package.json +3 -1
- package/reference/sdlc/code.md +1 -1
- package/reference/sdlc/code.playbook/bin/launch-config.js +55 -16
- package/reference/sdlc/code.playbook/bin/playbook.js +14 -9
- package/reference/sdlc/code.playbook/bin/repository-effects.js +54 -13
- package/reference/sdlc/code.playbook/bin/run.js +12 -8
- package/reference/sdlc/code.playbook/code.fsm.js +1 -1
- package/reference/sdlc/code.playbook/code.fsm.ts +1 -1
- package/reference/sdlc/code.playbook/code.gears.md +2 -2
- package/reference/sdlc/decide.md +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.gears.md +1 -1
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +1 -9
- package/reference/sdlc/decide.playbook/decide.playbook.js +158 -32
- package/reference/sdlc/decide.playbook/decide.playbook.ts +229 -46
- package/reference/sdlc/review.md +1 -1
- package/reference/sdlc/review.playbook/review.fsm.js +1 -1
- package/reference/sdlc/review.playbook/review.fsm.ts +1 -1
- package/reference/sdlc/review.playbook/review.gears.md +1 -1
- package/slc/prefix-prompts.mjs +190 -0
- package/slc/prefix.md +200 -0
- package/slc/slc.pin-inputs.json +1 -0
- package/src/xstate-playbook-runtime.d.ts +35 -1
- package/src/xstate-playbook-runtime.js +82 -47
- package/src/xstate-playbook-runtime.ts +107 -60
- package/src/xstate-runtime.d.ts +3 -1
- package/src/xstate-runtime.js +9 -2
- package/src/xstate-runtime.ts +12 -2
|
@@ -70,11 +70,11 @@ const GOVERNED_JUDGE_FAILURE_REASONS = [
|
|
|
70
70
|
'corrective semantic candidate is invalid',
|
|
71
71
|
'semantic correction budget is unavailable',
|
|
72
72
|
];
|
|
73
|
-
const ABORTED_FAILURE_CAUSE = assertPlaybookFailureCause({
|
|
73
|
+
export const ABORTED_FAILURE_CAUSE = assertPlaybookFailureCause({
|
|
74
74
|
code: 'aborted',
|
|
75
75
|
evidence: {},
|
|
76
76
|
});
|
|
77
|
-
function runtimeDefectCause(reason) {
|
|
77
|
+
export function runtimeDefectCause(reason) {
|
|
78
78
|
return assertPlaybookFailureCause({
|
|
79
79
|
code: 'runtime-defect',
|
|
80
80
|
evidence: { reason: reason.length === 0 ? 'unknown runtime defect' : reason },
|
|
@@ -98,7 +98,7 @@ function failureErrorCode(error) {
|
|
|
98
98
|
return undefined;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
function playerFailureCause(input) {
|
|
101
|
+
export function playerFailureCause(input) {
|
|
102
102
|
return assertPlaybookFailureCause({
|
|
103
103
|
code: 'player-failed',
|
|
104
104
|
evidence: {
|
|
@@ -114,6 +114,49 @@ function playerFailureCause(input) {
|
|
|
114
114
|
},
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* DR-063 §1: the cause of an unresolved governed settlement. A reconciled
|
|
119
|
+
* mismatch supplies its own receipt-read cause; the reasons a runtime owns map
|
|
120
|
+
* to adjudication, abort, and runtime-defect codes. Shared with DECIDE's
|
|
121
|
+
* bespoke runtime, which decides the same reasons.
|
|
122
|
+
*/
|
|
123
|
+
export function governedSettlementCause(reason, error, aborted, supplied) {
|
|
124
|
+
if (supplied !== undefined)
|
|
125
|
+
return supplied;
|
|
126
|
+
if (aborted || reason.includes('aborted'))
|
|
127
|
+
return ABORTED_FAILURE_CAUSE;
|
|
128
|
+
if (GOVERNED_JUDGE_FAILURE_REASONS.includes(reason)) {
|
|
129
|
+
const transport = failureErrorEvidence(error);
|
|
130
|
+
return assertPlaybookFailureCause({
|
|
131
|
+
code: 'judge-failed',
|
|
132
|
+
evidence: {
|
|
133
|
+
reason,
|
|
134
|
+
...(transport === undefined ? {} : { error: transport }),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return runtimeDefectCause(reason);
|
|
139
|
+
}
|
|
140
|
+
const FAILURE_CAUSE_RETENTION_LIMIT = 8;
|
|
141
|
+
export function createFailureCauseRetention() {
|
|
142
|
+
const causes = new Map();
|
|
143
|
+
return {
|
|
144
|
+
retain(error, cause) {
|
|
145
|
+
if (error === undefined || error === null)
|
|
146
|
+
return;
|
|
147
|
+
causes.delete(error);
|
|
148
|
+
causes.set(error, cause);
|
|
149
|
+
while (causes.size > FAILURE_CAUSE_RETENTION_LIMIT) {
|
|
150
|
+
causes.delete(causes.keys().next().value);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
causeOf(error) {
|
|
154
|
+
if (error === undefined || error === null)
|
|
155
|
+
return undefined;
|
|
156
|
+
return normalizeError(error).cause ?? causes.get(error);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
117
160
|
// ---------------------------------------------------------------------------
|
|
118
161
|
// DR-028: both call boundaries treat an `ok` result whose `finalText` is
|
|
119
162
|
// missing, empty, or whitespace-only under one empty predicate, and that
|
|
@@ -578,7 +621,7 @@ function preExistingChangesBlock(baseline) {
|
|
|
578
621
|
* `one-descendant-commit` disposition may commit nothing, so it is told
|
|
579
622
|
* nothing.
|
|
580
623
|
*/
|
|
581
|
-
function effectAuthorizedPreExistingBlock(effectBoundary, baseline) {
|
|
624
|
+
export function effectAuthorizedPreExistingBlock(effectBoundary, baseline) {
|
|
582
625
|
return effectBoundary.dispositions.includes('one-descendant-commit')
|
|
583
626
|
? preExistingChangesBlock(baseline)
|
|
584
627
|
: undefined;
|
|
@@ -2186,15 +2229,17 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
2186
2229
|
let deferInspectionEmissions = false;
|
|
2187
2230
|
let deferredInspectionEmissions = [];
|
|
2188
2231
|
let controlPlaneError;
|
|
2189
|
-
// DR-063 §2: the cause decided
|
|
2190
|
-
//
|
|
2191
|
-
//
|
|
2192
|
-
//
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2232
|
+
// DR-063 §2: the cause decided for a non-`ok` player result before the
|
|
2233
|
+
// compiled artifact builds the Error that carries it and hands it to
|
|
2234
|
+
// `markPlayerResultFailure`. It is cleared at each Boss turn start and at
|
|
2235
|
+
// each successful result, so it is never stale.
|
|
2236
|
+
let pendingResultFailureCause;
|
|
2237
|
+
// DR-063 §2: the causes decided for thrown values that cannot carry the
|
|
2238
|
+
// marker — a string, a frozen error — kept by identity for as long as the
|
|
2239
|
+
// failure stands, so every surface that reads the FSM's own `lastError`
|
|
2240
|
+
// publishes the cause decided for exactly that value, whatever turn reads
|
|
2241
|
+
// it.
|
|
2242
|
+
const failureCauses = createFailureCauseRetention();
|
|
2198
2243
|
// Previous root-machine state for the inspect-driven telemetry /
|
|
2199
2244
|
// status emitter. undefined before the first inspect firing.
|
|
2200
2245
|
let priorState;
|
|
@@ -3102,33 +3147,13 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3102
3147
|
},
|
|
3103
3148
|
});
|
|
3104
3149
|
}
|
|
3105
|
-
// DR-063 §1: the cause of an unresolved governed settlement. A reconciled
|
|
3106
|
-
// mismatch supplies its own receipt-read cause; the reasons this function
|
|
3107
|
-
// owns map to adjudication, abort, and runtime-defect codes.
|
|
3108
|
-
function governedSettlementCause(reason, error, aborted, supplied) {
|
|
3109
|
-
if (supplied !== undefined)
|
|
3110
|
-
return supplied;
|
|
3111
|
-
if (aborted || reason.includes('aborted'))
|
|
3112
|
-
return ABORTED_FAILURE_CAUSE;
|
|
3113
|
-
if (GOVERNED_JUDGE_FAILURE_REASONS.includes(reason)) {
|
|
3114
|
-
const transport = failureErrorEvidence(error);
|
|
3115
|
-
return assertPlaybookFailureCause({
|
|
3116
|
-
code: 'judge-failed',
|
|
3117
|
-
evidence: {
|
|
3118
|
-
reason,
|
|
3119
|
-
...(transport === undefined ? {} : { error: transport }),
|
|
3120
|
-
},
|
|
3121
|
-
});
|
|
3122
|
-
}
|
|
3123
|
-
return runtimeDefectCause(reason);
|
|
3124
|
-
}
|
|
3125
3150
|
function unresolvedGovernedSettlement(reason, error, signal = activeSignal, cause) {
|
|
3126
3151
|
const aborted = error !== undefined && signal?.aborted === true
|
|
3127
3152
|
? Object.is(error, signal.reason)
|
|
3128
3153
|
: false;
|
|
3129
3154
|
const decided = governedSettlementCause(reason, error, aborted, cause);
|
|
3130
|
-
rememberFailureCause(decided);
|
|
3131
3155
|
if (aborted) {
|
|
3156
|
+
failureCauses.retain(error, decided);
|
|
3132
3157
|
return {
|
|
3133
3158
|
status: 'unresolved',
|
|
3134
3159
|
error: attachPlaybookFailureCause(error, decided),
|
|
@@ -3137,6 +3162,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3137
3162
|
const failure = error instanceof Error
|
|
3138
3163
|
? error
|
|
3139
3164
|
: new Error(`${label} governed outcome remains unresolved: ${reason}`);
|
|
3165
|
+
failureCauses.retain(failure, decided);
|
|
3140
3166
|
return {
|
|
3141
3167
|
status: 'unresolved',
|
|
3142
3168
|
error: attachPlaybookFailureCause(markFsmResultFailure(failure), decided),
|
|
@@ -3766,18 +3792,18 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3766
3792
|
// the player, and the reported error are known, and a successful
|
|
3767
3793
|
// result clears it so no later failure inherits it.
|
|
3768
3794
|
if (result.status === 'ok') {
|
|
3769
|
-
|
|
3795
|
+
pendingResultFailureCause = undefined;
|
|
3770
3796
|
}
|
|
3771
3797
|
else if (result.status === 'aborted') {
|
|
3772
|
-
|
|
3798
|
+
pendingResultFailureCause = ABORTED_FAILURE_CAUSE;
|
|
3773
3799
|
}
|
|
3774
3800
|
else {
|
|
3775
|
-
|
|
3801
|
+
pendingResultFailureCause = playerFailureCause({
|
|
3776
3802
|
roleId,
|
|
3777
3803
|
...(playerId === undefined ? {} : { playerId }),
|
|
3778
3804
|
error: result.error ??
|
|
3779
3805
|
`callPlayer status ${JSON.stringify(result.status)}`,
|
|
3780
|
-
})
|
|
3806
|
+
});
|
|
3781
3807
|
}
|
|
3782
3808
|
return result;
|
|
3783
3809
|
};
|
|
@@ -3850,9 +3876,10 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3850
3876
|
}
|
|
3851
3877
|
},
|
|
3852
3878
|
markPlayerResultFailure(error) {
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3879
|
+
if (pendingResultFailureCause === undefined)
|
|
3880
|
+
return error;
|
|
3881
|
+
failureCauses.retain(error, pendingResultFailureCause);
|
|
3882
|
+
return attachPlaybookFailureCause(error, pendingResultFailureCause);
|
|
3856
3883
|
},
|
|
3857
3884
|
takeGovernedPlayerOutput(result) {
|
|
3858
3885
|
const settlement = governedPlayerSettlements.get(result);
|
|
@@ -4381,7 +4408,8 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
4381
4408
|
}
|
|
4382
4409
|
return {
|
|
4383
4410
|
...normalized,
|
|
4384
|
-
cause:
|
|
4411
|
+
cause: failureCauses.causeOf(context.lastError) ??
|
|
4412
|
+
runtimeDefectCause(normalized.message),
|
|
4385
4413
|
};
|
|
4386
4414
|
}
|
|
4387
4415
|
// DR-063 §2: a rejected player port or a result the runtime cannot read is
|
|
@@ -4394,7 +4422,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
4394
4422
|
...(playerId === undefined ? {} : { playerId }),
|
|
4395
4423
|
error,
|
|
4396
4424
|
});
|
|
4397
|
-
|
|
4425
|
+
failureCauses.retain(error, cause);
|
|
4398
4426
|
return attachPlaybookFailureCause(error, cause);
|
|
4399
4427
|
}
|
|
4400
4428
|
function completeFailedStatuses(statuses, state, context) {
|
|
@@ -5691,9 +5719,15 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
5691
5719
|
const state = currentState();
|
|
5692
5720
|
if (state.status !== 'active' || !state.quiescent)
|
|
5693
5721
|
return undefined;
|
|
5694
|
-
const machineSnapshot = detachPersistedMachineSnapshot(actor.getPersistedSnapshot());
|
|
5695
5722
|
const context = actor.getSnapshot()
|
|
5696
5723
|
.context;
|
|
5724
|
+
// DR-063 §2: the failed state's `lastError` is persisted as every live
|
|
5725
|
+
// surface publishes it, completed from the runtime's own cause slot, so
|
|
5726
|
+
// a thrown value that refused the cause still explains itself after a
|
|
5727
|
+
// restart.
|
|
5728
|
+
const machineSnapshot = detachPersistedMachineSnapshot(actor.getPersistedSnapshot(), state.stateId === 'failed'
|
|
5729
|
+
? { lastError: failedStateError(context ?? {}) }
|
|
5730
|
+
: {});
|
|
5697
5731
|
effectLedgerMirror = currentEffectLedger();
|
|
5698
5732
|
refreshRetainedEffectReconciliation(effectLedgerMirror);
|
|
5699
5733
|
syncDeferredReconciliationOverlay();
|
|
@@ -6128,9 +6162,10 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
6128
6162
|
activeAborts = abortReasonClassifier(signal);
|
|
6129
6163
|
activeAbortEmission = undefined;
|
|
6130
6164
|
controlPlaneError = undefined;
|
|
6131
|
-
// DR-063 §2: each turn decides its own failures, so
|
|
6132
|
-
// carries
|
|
6133
|
-
|
|
6165
|
+
// DR-063 §2: each turn decides its own failures, so no pending result
|
|
6166
|
+
// cause carries into this one; a cause decided for a parked failure
|
|
6167
|
+
// stays bound to that failure.
|
|
6168
|
+
pendingResultFailureCause = undefined;
|
|
6134
6169
|
let result;
|
|
6135
6170
|
let operationError;
|
|
6136
6171
|
// The boundary sentinel releases on every exit: a settlement defect
|
|
@@ -284,12 +284,12 @@ const GOVERNED_JUDGE_FAILURE_REASONS: readonly string[] = [
|
|
|
284
284
|
'semantic correction budget is unavailable',
|
|
285
285
|
];
|
|
286
286
|
|
|
287
|
-
const ABORTED_FAILURE_CAUSE: PlaybookFailureCause = assertPlaybookFailureCause({
|
|
287
|
+
export const ABORTED_FAILURE_CAUSE: PlaybookFailureCause = assertPlaybookFailureCause({
|
|
288
288
|
code: 'aborted',
|
|
289
289
|
evidence: {},
|
|
290
290
|
});
|
|
291
291
|
|
|
292
|
-
function runtimeDefectCause(reason: string): PlaybookFailureCause {
|
|
292
|
+
export function runtimeDefectCause(reason: string): PlaybookFailureCause {
|
|
293
293
|
return assertPlaybookFailureCause({
|
|
294
294
|
code: 'runtime-defect',
|
|
295
295
|
evidence: { reason: reason.length === 0 ? 'unknown runtime defect' : reason },
|
|
@@ -315,7 +315,7 @@ function failureErrorCode(error: unknown): string | undefined {
|
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
function playerFailureCause(input: {
|
|
318
|
+
export function playerFailureCause(input: {
|
|
319
319
|
readonly roleId: string;
|
|
320
320
|
readonly playerId?: string;
|
|
321
321
|
readonly error: unknown;
|
|
@@ -336,6 +336,66 @@ function playerFailureCause(input: {
|
|
|
336
336
|
});
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* DR-063 §1: the cause of an unresolved governed settlement. A reconciled
|
|
341
|
+
* mismatch supplies its own receipt-read cause; the reasons a runtime owns map
|
|
342
|
+
* to adjudication, abort, and runtime-defect codes. Shared with DECIDE's
|
|
343
|
+
* bespoke runtime, which decides the same reasons.
|
|
344
|
+
*/
|
|
345
|
+
export function governedSettlementCause(
|
|
346
|
+
reason: string,
|
|
347
|
+
error: unknown,
|
|
348
|
+
aborted: boolean,
|
|
349
|
+
supplied: PlaybookFailureCause | undefined,
|
|
350
|
+
): PlaybookFailureCause {
|
|
351
|
+
if (supplied !== undefined) return supplied;
|
|
352
|
+
if (aborted || reason.includes('aborted')) return ABORTED_FAILURE_CAUSE;
|
|
353
|
+
if (GOVERNED_JUDGE_FAILURE_REASONS.includes(reason)) {
|
|
354
|
+
const transport = failureErrorEvidence(error);
|
|
355
|
+
return assertPlaybookFailureCause({
|
|
356
|
+
code: 'judge-failed',
|
|
357
|
+
evidence: {
|
|
358
|
+
reason,
|
|
359
|
+
...(transport === undefined ? {} : { error: transport }),
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return runtimeDefectCause(reason);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* DR-063 §2: the causes decided for thrown values that cannot carry the
|
|
368
|
+
* marker — a string, a frozen error — kept by identity for as long as the
|
|
369
|
+
* failure stands, so every surface that reads the FSM's own `lastError`
|
|
370
|
+
* publishes the cause decided for exactly that value, whatever turn reads it.
|
|
371
|
+
* A value that carries the marker answers from it; the map is bounded, since
|
|
372
|
+
* only values that refuse the marker need an entry.
|
|
373
|
+
*/
|
|
374
|
+
export interface PlaybookFailureCauseRetention {
|
|
375
|
+
retain(error: unknown, cause: PlaybookFailureCause): void;
|
|
376
|
+
causeOf(error: unknown): PlaybookFailureCause | undefined;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const FAILURE_CAUSE_RETENTION_LIMIT = 8;
|
|
380
|
+
|
|
381
|
+
export function createFailureCauseRetention(): PlaybookFailureCauseRetention {
|
|
382
|
+
const causes = new Map<unknown, PlaybookFailureCause>();
|
|
383
|
+
return {
|
|
384
|
+
retain(error, cause) {
|
|
385
|
+
if (error === undefined || error === null) return;
|
|
386
|
+
causes.delete(error);
|
|
387
|
+
causes.set(error, cause);
|
|
388
|
+
while (causes.size > FAILURE_CAUSE_RETENTION_LIMIT) {
|
|
389
|
+
causes.delete(causes.keys().next().value);
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
causeOf(error) {
|
|
393
|
+
if (error === undefined || error === null) return undefined;
|
|
394
|
+
return normalizeError(error).cause ?? causes.get(error);
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
339
399
|
|
|
340
400
|
// ---------------------------------------------------------------------------
|
|
341
401
|
// DR-028: both call boundaries treat an `ok` result whose `finalText` is
|
|
@@ -1269,8 +1329,8 @@ function preExistingChangesBlock(
|
|
|
1269
1329
|
* `one-descendant-commit` disposition may commit nothing, so it is told
|
|
1270
1330
|
* nothing.
|
|
1271
1331
|
*/
|
|
1272
|
-
function effectAuthorizedPreExistingBlock(
|
|
1273
|
-
effectBoundary: Pick<
|
|
1332
|
+
export function effectAuthorizedPreExistingBlock(
|
|
1333
|
+
effectBoundary: Pick<PlaybookEffectBoundaryStart, 'dispositions'>,
|
|
1274
1334
|
baseline: PlaybookRepositoryReceipt['baseline'] | undefined,
|
|
1275
1335
|
): string | undefined {
|
|
1276
1336
|
return effectBoundary.dispositions.includes('one-descendant-commit')
|
|
@@ -3577,15 +3637,17 @@ export function createXStatePlaybookRuntime<
|
|
|
3577
3637
|
let deferInspectionEmissions = false;
|
|
3578
3638
|
let deferredInspectionEmissions: Array<() => void> = [];
|
|
3579
3639
|
let controlPlaneError: unknown;
|
|
3580
|
-
// DR-063 §2: the cause decided
|
|
3581
|
-
//
|
|
3582
|
-
//
|
|
3583
|
-
//
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3640
|
+
// DR-063 §2: the cause decided for a non-`ok` player result before the
|
|
3641
|
+
// compiled artifact builds the Error that carries it and hands it to
|
|
3642
|
+
// `markPlayerResultFailure`. It is cleared at each Boss turn start and at
|
|
3643
|
+
// each successful result, so it is never stale.
|
|
3644
|
+
let pendingResultFailureCause: PlaybookFailureCause | undefined;
|
|
3645
|
+
// DR-063 §2: the causes decided for thrown values that cannot carry the
|
|
3646
|
+
// marker — a string, a frozen error — kept by identity for as long as the
|
|
3647
|
+
// failure stands, so every surface that reads the FSM's own `lastError`
|
|
3648
|
+
// publishes the cause decided for exactly that value, whatever turn reads
|
|
3649
|
+
// it.
|
|
3650
|
+
const failureCauses = createFailureCauseRetention();
|
|
3589
3651
|
// Previous root-machine state for the inspect-driven telemetry /
|
|
3590
3652
|
// status emitter. undefined before the first inspect firing.
|
|
3591
3653
|
let priorState: PlaybookState | undefined;
|
|
@@ -4865,30 +4927,6 @@ export function createXStatePlaybookRuntime<
|
|
|
4865
4927
|
});
|
|
4866
4928
|
}
|
|
4867
4929
|
|
|
4868
|
-
// DR-063 §1: the cause of an unresolved governed settlement. A reconciled
|
|
4869
|
-
// mismatch supplies its own receipt-read cause; the reasons this function
|
|
4870
|
-
// owns map to adjudication, abort, and runtime-defect codes.
|
|
4871
|
-
function governedSettlementCause(
|
|
4872
|
-
reason: string,
|
|
4873
|
-
error: unknown,
|
|
4874
|
-
aborted: boolean,
|
|
4875
|
-
supplied: PlaybookFailureCause | undefined,
|
|
4876
|
-
): PlaybookFailureCause {
|
|
4877
|
-
if (supplied !== undefined) return supplied;
|
|
4878
|
-
if (aborted || reason.includes('aborted')) return ABORTED_FAILURE_CAUSE;
|
|
4879
|
-
if (GOVERNED_JUDGE_FAILURE_REASONS.includes(reason)) {
|
|
4880
|
-
const transport = failureErrorEvidence(error);
|
|
4881
|
-
return assertPlaybookFailureCause({
|
|
4882
|
-
code: 'judge-failed',
|
|
4883
|
-
evidence: {
|
|
4884
|
-
reason,
|
|
4885
|
-
...(transport === undefined ? {} : { error: transport }),
|
|
4886
|
-
},
|
|
4887
|
-
});
|
|
4888
|
-
}
|
|
4889
|
-
return runtimeDefectCause(reason);
|
|
4890
|
-
}
|
|
4891
|
-
|
|
4892
4930
|
function unresolvedGovernedSettlement(
|
|
4893
4931
|
reason: string,
|
|
4894
4932
|
error?: unknown,
|
|
@@ -4900,8 +4938,8 @@ export function createXStatePlaybookRuntime<
|
|
|
4900
4938
|
? Object.is(error, signal.reason)
|
|
4901
4939
|
: false;
|
|
4902
4940
|
const decided = governedSettlementCause(reason, error, aborted, cause);
|
|
4903
|
-
rememberFailureCause(decided);
|
|
4904
4941
|
if (aborted) {
|
|
4942
|
+
failureCauses.retain(error, decided);
|
|
4905
4943
|
return {
|
|
4906
4944
|
status: 'unresolved',
|
|
4907
4945
|
error: attachPlaybookFailureCause(error, decided),
|
|
@@ -4911,6 +4949,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4911
4949
|
error instanceof Error
|
|
4912
4950
|
? error
|
|
4913
4951
|
: new Error(`${label} governed outcome remains unresolved: ${reason}`);
|
|
4952
|
+
failureCauses.retain(failure, decided);
|
|
4914
4953
|
return {
|
|
4915
4954
|
status: 'unresolved',
|
|
4916
4955
|
error: attachPlaybookFailureCause(
|
|
@@ -5835,19 +5874,17 @@ export function createXStatePlaybookRuntime<
|
|
|
5835
5874
|
// the player, and the reported error are known, and a successful
|
|
5836
5875
|
// result clears it so no later failure inherits it.
|
|
5837
5876
|
if (result.status === 'ok') {
|
|
5838
|
-
|
|
5877
|
+
pendingResultFailureCause = undefined;
|
|
5839
5878
|
} else if (result.status === 'aborted') {
|
|
5840
|
-
|
|
5879
|
+
pendingResultFailureCause = ABORTED_FAILURE_CAUSE;
|
|
5841
5880
|
} else {
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
error
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
}),
|
|
5850
|
-
);
|
|
5881
|
+
pendingResultFailureCause = playerFailureCause({
|
|
5882
|
+
roleId,
|
|
5883
|
+
...(playerId === undefined ? {} : { playerId }),
|
|
5884
|
+
error:
|
|
5885
|
+
result.error ??
|
|
5886
|
+
`callPlayer status ${JSON.stringify(result.status)}`,
|
|
5887
|
+
});
|
|
5851
5888
|
}
|
|
5852
5889
|
return result;
|
|
5853
5890
|
};
|
|
@@ -5957,9 +5994,9 @@ export function createXStatePlaybookRuntime<
|
|
|
5957
5994
|
},
|
|
5958
5995
|
|
|
5959
5996
|
markPlayerResultFailure(error): Error {
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5997
|
+
if (pendingResultFailureCause === undefined) return error;
|
|
5998
|
+
failureCauses.retain(error, pendingResultFailureCause);
|
|
5999
|
+
return attachPlaybookFailureCause(error, pendingResultFailureCause);
|
|
5963
6000
|
},
|
|
5964
6001
|
|
|
5965
6002
|
takeGovernedPlayerOutput(result): GovernedPlayerSettlement | undefined {
|
|
@@ -6619,7 +6656,9 @@ export function createXStatePlaybookRuntime<
|
|
|
6619
6656
|
}
|
|
6620
6657
|
return {
|
|
6621
6658
|
...normalized,
|
|
6622
|
-
cause:
|
|
6659
|
+
cause:
|
|
6660
|
+
failureCauses.causeOf(context.lastError) ??
|
|
6661
|
+
runtimeDefectCause(normalized.message),
|
|
6623
6662
|
};
|
|
6624
6663
|
}
|
|
6625
6664
|
|
|
@@ -6638,7 +6677,7 @@ export function createXStatePlaybookRuntime<
|
|
|
6638
6677
|
...(playerId === undefined ? {} : { playerId }),
|
|
6639
6678
|
error,
|
|
6640
6679
|
});
|
|
6641
|
-
|
|
6680
|
+
failureCauses.retain(error, cause);
|
|
6642
6681
|
return attachPlaybookFailureCause(error, cause);
|
|
6643
6682
|
}
|
|
6644
6683
|
|
|
@@ -8243,11 +8282,18 @@ export function createXStatePlaybookRuntime<
|
|
|
8243
8282
|
}
|
|
8244
8283
|
const state = currentState();
|
|
8245
8284
|
if (state.status !== 'active' || !state.quiescent) return undefined;
|
|
8285
|
+
const context = (actor.getSnapshot() as { context?: unknown })
|
|
8286
|
+
.context as Record<string, unknown>;
|
|
8287
|
+
// DR-063 §2: the failed state's `lastError` is persisted as every live
|
|
8288
|
+
// surface publishes it, completed from the runtime's own cause slot, so
|
|
8289
|
+
// a thrown value that refused the cause still explains itself after a
|
|
8290
|
+
// restart.
|
|
8246
8291
|
const machineSnapshot = detachPersistedMachineSnapshot(
|
|
8247
8292
|
actor.getPersistedSnapshot(),
|
|
8293
|
+
state.stateId === 'failed'
|
|
8294
|
+
? { lastError: failedStateError(context ?? {}) }
|
|
8295
|
+
: {},
|
|
8248
8296
|
);
|
|
8249
|
-
const context = (actor.getSnapshot() as { context?: unknown })
|
|
8250
|
-
.context as Record<string, unknown>;
|
|
8251
8297
|
effectLedgerMirror = currentEffectLedger();
|
|
8252
8298
|
refreshRetainedEffectReconciliation(effectLedgerMirror);
|
|
8253
8299
|
syncDeferredReconciliationOverlay();
|
|
@@ -8763,9 +8809,10 @@ export function createXStatePlaybookRuntime<
|
|
|
8763
8809
|
activeAborts = abortReasonClassifier(signal);
|
|
8764
8810
|
activeAbortEmission = undefined;
|
|
8765
8811
|
controlPlaneError = undefined;
|
|
8766
|
-
// DR-063 §2: each turn decides its own failures, so
|
|
8767
|
-
// carries
|
|
8768
|
-
|
|
8812
|
+
// DR-063 §2: each turn decides its own failures, so no pending result
|
|
8813
|
+
// cause carries into this one; a cause decided for a parked failure
|
|
8814
|
+
// stays bound to that failure.
|
|
8815
|
+
pendingResultFailureCause = undefined;
|
|
8769
8816
|
let result: PlaybookRunResult | undefined;
|
|
8770
8817
|
let operationError: unknown;
|
|
8771
8818
|
// The boundary sentinel releases on every exit: a settlement defect
|
package/src/xstate-runtime.d.ts
CHANGED
|
@@ -44,7 +44,9 @@ export interface SnapshotNormalizationOptions {
|
|
|
44
44
|
pendingCall?: PlaybookPendingCall;
|
|
45
45
|
}
|
|
46
46
|
export declare function normalizePlaybookSnapshot(snapshot: unknown, options?: SnapshotNormalizationOptions): PlaybookState;
|
|
47
|
-
export declare function detachPersistedMachineSnapshot(persisted: unknown
|
|
47
|
+
export declare function detachPersistedMachineSnapshot(persisted: unknown, completion?: {
|
|
48
|
+
readonly lastError?: NormalizedError;
|
|
49
|
+
}): JsonValue;
|
|
48
50
|
/** Authority for one governed delegated-player output field (DR-040 §1). */
|
|
49
51
|
export type PlaybookSemanticFieldAuthority = 'presentation' | 'semantic' | 'effect' | 'runtime';
|
|
50
52
|
/** One already-validated state-local governed outcome declaration. */
|
package/src/xstate-runtime.js
CHANGED
|
@@ -607,8 +607,15 @@ export function normalizePlaybookSnapshot(snapshot, options = {}) {
|
|
|
607
607
|
// DR-014 §1: deep-detach an XState persisted actor snapshot into strict
|
|
608
608
|
// JSON for a PlaybookRuntimeSnapshot, normalizing any raw Error value
|
|
609
609
|
// (for example FSM context `lastError`) instead of rejecting it.
|
|
610
|
-
|
|
611
|
-
|
|
610
|
+
// DR-063 §2: a runtime parked in its failure state supplies the `lastError`
|
|
611
|
+
// it publishes live — completed with the cause it decided — and that record
|
|
612
|
+
// replaces the context's own, which a thrown value may have refused to carry.
|
|
613
|
+
export function detachPersistedMachineSnapshot(persisted, completion = {}) {
|
|
614
|
+
const detached = withErrorsNormalized(persisted, new Set());
|
|
615
|
+
const lastError = completion.lastError;
|
|
616
|
+
return snapshotJsonValue(lastError !== undefined && isRecord(detached) && isRecord(detached.context)
|
|
617
|
+
? { ...detached, context: { ...detached.context, lastError } }
|
|
618
|
+
: detached, 'persisted machine snapshot');
|
|
612
619
|
}
|
|
613
620
|
function withErrorsNormalized(value, ancestors) {
|
|
614
621
|
if (value instanceof Error)
|
package/src/xstate-runtime.ts
CHANGED
|
@@ -902,9 +902,19 @@ export function normalizePlaybookSnapshot(
|
|
|
902
902
|
// DR-014 §1: deep-detach an XState persisted actor snapshot into strict
|
|
903
903
|
// JSON for a PlaybookRuntimeSnapshot, normalizing any raw Error value
|
|
904
904
|
// (for example FSM context `lastError`) instead of rejecting it.
|
|
905
|
-
|
|
905
|
+
// DR-063 §2: a runtime parked in its failure state supplies the `lastError`
|
|
906
|
+
// it publishes live — completed with the cause it decided — and that record
|
|
907
|
+
// replaces the context's own, which a thrown value may have refused to carry.
|
|
908
|
+
export function detachPersistedMachineSnapshot(
|
|
909
|
+
persisted: unknown,
|
|
910
|
+
completion: { readonly lastError?: NormalizedError } = {},
|
|
911
|
+
): JsonValue {
|
|
912
|
+
const detached = withErrorsNormalized(persisted, new Set());
|
|
913
|
+
const lastError = completion.lastError;
|
|
906
914
|
return snapshotJsonValue(
|
|
907
|
-
|
|
915
|
+
lastError !== undefined && isRecord(detached) && isRecord(detached.context)
|
|
916
|
+
? { ...detached, context: { ...detached.context, lastError } }
|
|
917
|
+
: detached,
|
|
908
918
|
'persisted machine snapshot',
|
|
909
919
|
);
|
|
910
920
|
}
|