@sublang/playbook 16.0.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 +26 -5
- 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/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/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
|
@@ -48,7 +48,7 @@ export interface OpenSessionHostOptions {
|
|
|
48
48
|
readonly store?: SharedSessionStore;
|
|
49
49
|
readonly sessionsDir?: string;
|
|
50
50
|
readonly sessionId?: string;
|
|
51
|
-
readonly mode?: 'new' | 'continue' | 'retry';
|
|
51
|
+
readonly mode?: 'new' | 'continue' | 'retry' | 'recover';
|
|
52
52
|
readonly cwd?: string;
|
|
53
53
|
readonly config?: SessionExecutionProjection;
|
|
54
54
|
readonly plan?: any;
|
|
@@ -98,6 +98,9 @@ export interface SessionHostController {
|
|
|
98
98
|
*/
|
|
99
99
|
submitShellAction(actionId: string): Promise<SessionRecovery>;
|
|
100
100
|
retry(): Promise<SessionRecovery>;
|
|
101
|
+
/** Restore and report recorded uncertainty without running work; settled pauses require Boss input. Never discards work.
|
|
102
|
+
* After this live controller becomes uncertain, dispose and reopen with mode:'recover' first. */
|
|
103
|
+
recover(input?: string): Promise<SessionRecovery>;
|
|
101
104
|
dispose(): Promise<void>;
|
|
102
105
|
}
|
|
103
106
|
export declare function openSessionHost(options: OpenSessionHostOptions): Promise<SessionHostController>;
|
|
@@ -113,6 +113,18 @@ export interface SessionStructuralProjection {
|
|
|
113
113
|
readonly players: readonly Readonly<Record<string, any>>[];
|
|
114
114
|
readonly catalog: Readonly<Record<string, any>>;
|
|
115
115
|
}
|
|
116
|
+
export interface SessionStep {
|
|
117
|
+
readonly id: string;
|
|
118
|
+
readonly kind: 'player' | 'captain' | 'script' | 'preparation' | 'completion' | 'answer';
|
|
119
|
+
readonly stateId: string;
|
|
120
|
+
readonly runtimeSessionId: string;
|
|
121
|
+
readonly playbookId: string;
|
|
122
|
+
readonly result?: unknown;
|
|
123
|
+
}
|
|
124
|
+
export interface SessionProgressChange {
|
|
125
|
+
readonly snapshot?: SessionSnapshot | null;
|
|
126
|
+
readonly step?: SessionStep;
|
|
127
|
+
}
|
|
116
128
|
export interface SessionRecovery {
|
|
117
129
|
readonly schemaVersion: 6;
|
|
118
130
|
readonly kind: 'captain-session';
|
|
@@ -135,6 +147,7 @@ export interface SessionRecovery {
|
|
|
135
147
|
readonly markedAt: string;
|
|
136
148
|
readonly attemptedExecutionProjection: SessionExecutionProjection;
|
|
137
149
|
readonly abandonment?: Readonly<Record<string, any>>;
|
|
150
|
+
readonly progress?: { readonly snapshot: SessionSnapshot | null; readonly steps: readonly SessionStep[]; readonly positionStepId?: string | null };
|
|
138
151
|
};
|
|
139
152
|
}
|
|
140
153
|
export interface SessionReplayCheckpoint {
|
|
@@ -215,6 +228,7 @@ export interface PlaybookSessionLifecycle extends PlaybookSessionLease {
|
|
|
215
228
|
abandonFreshSettled(options: { expected: SessionRecovery }): Promise<boolean>;
|
|
216
229
|
beginTurn(options: { input: string; attemptId: string; attemptedExecutionProjection: SessionExecutionProjection }): Promise<SessionRecovery>;
|
|
217
230
|
beginRetry(options: { expectedAttemptId: string; nextAttemptId: string }): Promise<SessionRecovery>;
|
|
231
|
+
recordProgress(change: SessionProgressChange): Promise<void>;
|
|
218
232
|
settle(options: { attemptId: string; snapshot: SessionSnapshot; unresolvedEffects: readonly SessionUnresolvedEffect[]; retentionUpdates?: readonly SessionRetentionUpdate[] }): Promise<SessionRecovery>;
|
|
219
233
|
discard(options: { attemptId: string }): Promise<SessionRecovery | undefined>;
|
|
220
234
|
beginUnresolvedEffectAbandonment(options: any): Promise<any>;
|
|
@@ -255,3 +269,6 @@ export declare function validateCaptainSessionExecutionProjection(value: unknown
|
|
|
255
269
|
export declare function validateCaptainSessionStructuralProjection(value: unknown): SessionStructuralProjection;
|
|
256
270
|
export declare function assertCaptainSessionExecutionCompatible(structural: SessionStructuralProjection, execution: SessionExecutionProjection): SessionExecutionProjection;
|
|
257
271
|
export declare function attachSessionHints(snapshot: SessionSnapshot, hints: SessionHints): SessionSnapshot;
|
|
272
|
+
|
|
273
|
+
/** Whether discarding an uncertain turn restores its untouched baseline. */
|
|
274
|
+
export declare function isUncertainTurnDiscardable(record: SessionRecovery): boolean;
|
|
@@ -117,6 +117,7 @@ function projectStatus(status) {
|
|
|
117
117
|
|
|
118
118
|
export {
|
|
119
119
|
createCaptainSessionStore as createSessionStore,
|
|
120
|
+
isUncertainTurnDiscardable,
|
|
120
121
|
projectCaptainSessionStructure,
|
|
121
122
|
validateCaptainSessionExecutionProjection,
|
|
122
123
|
validateCaptainSessionStructuralProjection,
|
|
@@ -203,15 +203,15 @@ export type DecideOutput = {
|
|
|
203
203
|
*/
|
|
204
204
|
export declare function authoredChildResult(error: unknown, expectedPlaybookId: string): PlaybookCallResult | undefined;
|
|
205
205
|
export declare const decideMachine: import("xstate").StateMachine<DecideContext, import("xstate").AnyEventObject, {
|
|
206
|
-
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<
|
|
206
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>> | undefined;
|
|
207
207
|
}, {
|
|
208
|
-
src: "playbook";
|
|
209
|
-
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
210
|
-
id: string | undefined;
|
|
211
|
-
} | {
|
|
212
208
|
src: "player";
|
|
213
209
|
logic: import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>;
|
|
214
210
|
id: string | undefined;
|
|
211
|
+
} | {
|
|
212
|
+
src: "playbook";
|
|
213
|
+
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
214
|
+
id: string | undefined;
|
|
215
215
|
}, {
|
|
216
216
|
type: "playbook.acceptedOutcome";
|
|
217
217
|
params: {
|
|
@@ -152,15 +152,15 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
|
|
|
152
152
|
readonly answer: string;
|
|
153
153
|
readonly questionId?: string;
|
|
154
154
|
}, {
|
|
155
|
-
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<
|
|
155
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>> | undefined;
|
|
156
156
|
}, {
|
|
157
|
-
src: "playbook";
|
|
158
|
-
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
159
|
-
id: string | undefined;
|
|
160
|
-
} | {
|
|
161
157
|
src: "player";
|
|
162
158
|
logic: import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>;
|
|
163
159
|
id: string | undefined;
|
|
160
|
+
} | {
|
|
161
|
+
src: "playbook";
|
|
162
|
+
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
163
|
+
id: string | undefined;
|
|
164
164
|
}, {
|
|
165
165
|
type: "playbook.acceptedOutcome";
|
|
166
166
|
params: {
|
|
@@ -162,12 +162,8 @@ export declare const prMachine: import("xstate").StateMachine<PrContext, {
|
|
|
162
162
|
readonly answer: string;
|
|
163
163
|
readonly questionId?: "openPullRequest";
|
|
164
164
|
}, {
|
|
165
|
-
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<
|
|
165
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<ScriptOutput, ScriptInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>> | undefined;
|
|
166
166
|
}, {
|
|
167
|
-
src: "playbook";
|
|
168
|
-
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
169
|
-
id: string | undefined;
|
|
170
|
-
} | {
|
|
171
167
|
src: "player";
|
|
172
168
|
logic: import("xstate").PromiseActorLogic<PlayerOutput, PlayerInput, import("xstate").EventObject>;
|
|
173
169
|
id: string | undefined;
|
|
@@ -175,6 +171,10 @@ export declare const prMachine: import("xstate").StateMachine<PrContext, {
|
|
|
175
171
|
src: "script";
|
|
176
172
|
logic: import("xstate").PromiseActorLogic<ScriptOutput, ScriptInput, import("xstate").EventObject>;
|
|
177
173
|
id: string | undefined;
|
|
174
|
+
} | {
|
|
175
|
+
src: "playbook";
|
|
176
|
+
logic: import("xstate").PromiseActorLogic<PlaybookOutput, PlaybookInput, import("xstate").EventObject>;
|
|
177
|
+
id: string | undefined;
|
|
178
178
|
}, {
|
|
179
179
|
type: "playbook.acceptedOutcome";
|
|
180
180
|
params: {
|
package/slc/gears2fsm.md
CHANGED
|
@@ -243,15 +243,16 @@ teardown event. The completion rule of
|
|
|
243
243
|
output clause binds only where Source declares a terminal result, which a
|
|
244
244
|
controller Source does not.
|
|
245
245
|
The controller decision state's direct-Captain result contract discriminates
|
|
246
|
-
the closed action set of DR-029 as extended by DR-038. Its guard discriminants
|
|
246
|
+
the closed action set of DR-029 as extended by DR-038 and DR-069. Its guard discriminants
|
|
247
247
|
are a stable compiler contract, not names the compiler may invent — `respond`, `resume`,
|
|
248
|
-
`start`, `switch`, `dismiss`, `deliver`, and `runtime` — with each guard's
|
|
248
|
+
`start`, `switch`, `dismiss`, `deliver`, and `runtime`, plus `recover` when Source declares recovery preparation — with each guard's
|
|
249
249
|
required payload fields:
|
|
250
250
|
|
|
251
251
|
- `respond` requires `text`;
|
|
252
252
|
- `resume` requires `playbookId` and carries no `input`;
|
|
253
253
|
- `start` and `switch` each require `playbookId` and `input`;
|
|
254
254
|
- `runtime` requires `actionId`;
|
|
255
|
+
- `recover`, when declared, requires no payload and selects host-owned preparation followed by runtime-validated continuation;
|
|
255
256
|
- `dismiss` and `deliver` require none — a `deliver` result in particular
|
|
256
257
|
carries no text payload: the host is authoritative for the delivered text,
|
|
257
258
|
so the contract declares no field for it.
|
|
@@ -779,8 +780,7 @@ current event, not solely from the context that action will populate.
|
|
|
779
780
|
For an omitted event value, use an existing context seed only where Source
|
|
780
781
|
permits it; do not manufacture a required constructor value to satisfy a
|
|
781
782
|
context-only entry guard. The linked runtime sends the entry event normally;
|
|
782
|
-
|
|
783
|
-
copy fresh text into context before the guard.
|
|
783
|
+
entry metadata does not copy fresh text into context before the guard.
|
|
784
784
|
An entry event's copy action shall not clear per-run parameters the event omits: an absent optional field falls back to the existing (input-seeded) context value.
|
|
785
785
|
The two surfaces shall not be collapsed. `BOSS_INTERRUPT` always carries its
|
|
786
786
|
target id and may additionally carry typed Boss-supplied fields such as an
|
|
@@ -803,7 +803,7 @@ Boss-reply suspension, because its hub already receives every Boss turn and a
|
|
|
803
803
|
clarifying question to Boss is a `respond` selection over the closed action
|
|
804
804
|
set. The rule below is therefore universal over workflow states and silent
|
|
805
805
|
about that class; in particular, adding `needsBossReply` to the controller
|
|
806
|
-
decision state would add an
|
|
806
|
+
decision state would add an unauthorized outcome to its closed controller contract
|
|
807
807
|
whose guard discriminants [Setup](#setup) fixes, and is nonconformant.
|
|
808
808
|
There is no source-level opt-in annotation and no `needsBossReply` result metadata in GEARS output.
|
|
809
809
|
The FSM compiler shall preserve the GEARS blockquote as the state's domain `prompt` body and shall not inject any Boss-question instruction into `invoke.input.prompt`.
|
package/slc/link.md
CHANGED
|
@@ -65,7 +65,7 @@ node "<definition-directory>/materialize-link.mjs" --fsm "<source.fsm.ts>" --out
|
|
|
65
65
|
"label": "EXAMPLE",
|
|
66
66
|
"options": {},
|
|
67
67
|
"inputMapping": {},
|
|
68
|
-
"entryEvent": { "type": "BOSS_TASK", "textField": "bossIntent"
|
|
68
|
+
"entryEvent": { "type": "BOSS_TASK", "textField": "bossIntent" },
|
|
69
69
|
"bossEvents": [],
|
|
70
70
|
"outcomeAuthority": {
|
|
71
71
|
"governedPlayerStates": {
|
|
@@ -196,6 +196,8 @@ interface PlaybookRuntime {
|
|
|
196
196
|
handleBossInput(turn: {
|
|
197
197
|
text: string;
|
|
198
198
|
signal: AbortSignal;
|
|
199
|
+
/** Called synchronously just before accepting the event, at most once. */
|
|
200
|
+
onAccepted?: () => void;
|
|
199
201
|
}): Promise<PlaybookRunResult>;
|
|
200
202
|
resumePlaybookCall(input: {
|
|
201
203
|
callId: string;
|
|
@@ -366,6 +368,7 @@ interface PlaybookEffectBoundary {
|
|
|
366
368
|
readonly baseline: PlaybookRepositoryObservation;
|
|
367
369
|
readonly after?: PlaybookRepositoryObservation;
|
|
368
370
|
readonly physicalReceipt?: PlaybookRepositoryReceipt;
|
|
371
|
+
readonly restored?: PlaybookRepositoryObservation;
|
|
369
372
|
readonly finalText?: string;
|
|
370
373
|
readonly semanticCandidate?: JsonValue;
|
|
371
374
|
readonly initialSemanticCandidate?: JsonValue;
|
|
@@ -405,6 +408,7 @@ type PlaybookEffectBoundaryStart = Omit<
|
|
|
405
408
|
| 'finalText'
|
|
406
409
|
| 'semanticCandidate'
|
|
407
410
|
| 'initialSemanticCandidate'
|
|
411
|
+
| 'restored'
|
|
408
412
|
>;
|
|
409
413
|
|
|
410
414
|
type PlaybookEffectLogicalOperationStart = Omit<
|
|
@@ -466,6 +470,23 @@ interface PlaybookEffectLedgerCapability {
|
|
|
466
470
|
): Promise<PlaybookEffectLedger>;
|
|
467
471
|
}
|
|
468
472
|
|
|
473
|
+
interface PlaybookRecoveryCheckpoint {
|
|
474
|
+
readonly id?: string;
|
|
475
|
+
readonly stateId: string;
|
|
476
|
+
readonly prompt: string;
|
|
477
|
+
readonly machine: JsonValue;
|
|
478
|
+
readonly boundaryPrefix: number;
|
|
479
|
+
readonly result?: JsonValue;
|
|
480
|
+
readonly delivered?: true;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
interface PlaybookStepRecord {
|
|
484
|
+
readonly id: string;
|
|
485
|
+
readonly kind: 'player' | 'captain' | 'script';
|
|
486
|
+
readonly stateId: string;
|
|
487
|
+
readonly result?: JsonValue;
|
|
488
|
+
}
|
|
489
|
+
|
|
469
490
|
interface PlaybookRuntimeSnapshot {
|
|
470
491
|
schemaVersion: 4;
|
|
471
492
|
playbookId: string;
|
|
@@ -496,6 +517,8 @@ interface PlaybookRuntimeSnapshot {
|
|
|
496
517
|
readonly boundaryPrefix: number;
|
|
497
518
|
readonly attemptId: string | null;
|
|
498
519
|
};
|
|
520
|
+
/** Interrupted invocation, captured before its external call (DR-069). */
|
|
521
|
+
recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
|
|
499
522
|
suspendedCall?: PlaybookSuspendedCall;
|
|
500
523
|
}
|
|
501
524
|
|
|
@@ -580,7 +603,7 @@ interface XStatePlaybookRuntimeConstruction<
|
|
|
580
603
|
|
|
581
604
|
The shared type-only contract module shall export `PlaybookRepositoryDisposition`, `PlaybookRepositoryObservation`, `PlaybookRepositoryReceipt`, `PlaybookEffectBoundary`, `PlaybookEffectBoundaryStart`, `PlaybookEffectLogicalOperation`, `PlaybookEffectLedger`, `PlaybookEffectLedgerCommand`, `PlaybookEffectLedgerCommandBatch`, and `PlaybookEffectLedgerCapability`; the executable `@sublang/playbook/xstate-runtime` module shall export `assertPlaybookEffectLedger`, `emptyPlaybookEffectLedger`, and `isPlaybookEffectLedgerMonotonicExtension` over those types.
|
|
582
605
|
That executable module shall also export the centralized schema-3 semantic surface: `PlaybookSemanticFieldAuthority`, `PlaybookSemanticOutcomeSpec`, `PlaybookSemanticEvidenceInput`, `PlaybookReconciledSemanticOutput`, `PlaybookRetainedSemanticEvidence`, `PlaybookSemanticReconciliationReason`, `PlaybookSemanticReconciliation`, `PlaybookSemanticCandidateStructureError`, and `reconcilePlaybookSemanticEvidence`.
|
|
583
|
-
The pure reconciler shall accept the declared state-local outcomes, an unknown semantic candidate, and optional unknown `finalText`, repository receipt, and runtime-field evidence; shall return a detached frozen `resolved` or `deferred` decision with exact output and retained evidence, or an `unresolved` decision with retained evidence and one closed reason from `missing-presentation-evidence`, `missing-repository-receipt`, `invalid-repository-receipt`, `repository-disposition-mismatch`, `missing-effect-evidence`, `missing-runtime-evidence`, and `inconsistent-runtime-evidence`; and shall reserve `PlaybookSemanticCandidateStructureError` for candidate defects eligible for the bounded correction path rather than effect-evidence disagreement.
|
|
606
|
+
The pure reconciler shall accept the declared state-local outcomes, an unknown semantic candidate, and optional unknown `finalText`, repository receipt, and runtime-field evidence; shall return a detached frozen `resolved` or `deferred` decision with exact output and retained evidence, or an `unresolved` decision with retained evidence and one closed reason from `no-matching-outcome`, `missing-presentation-evidence`, `missing-repository-receipt`, `invalid-repository-receipt`, `repository-disposition-mismatch`, `missing-effect-evidence`, `missing-runtime-evidence`, and `inconsistent-runtime-evidence`; and shall reserve `PlaybookSemanticCandidateStructureError` for candidate defects eligible for the bounded correction path rather than effect-evidence disagreement.
|
|
584
607
|
The empty ledger shall be exactly `{ schemaVersion: 1, revision: 0, boundaries: [], logicalOperations: [] }`, and revision shall be zero if and only if both ordered ledgers are empty.
|
|
585
608
|
The validator shall capture the complete supplied ledger once as detached frozen JSON and enforce every closed member, identity, ordering, receipt, cross-reference, correction-budget, and logical-operation invariant represented above.
|
|
586
609
|
One optional host-owned UUID `cohortId` shall identify every member of exactly one contiguous, distinct-role, all-`unchanged` physical cohort in declared role order; every member shall share attempt, playbook, runtime-session, turn, canonical-worktree, and baseline identity and shall be uniformly started or uniformly complete, complete members shall carry the identical after observation and receipt, and the id shall never be reused by another group.
|
|
@@ -642,8 +665,8 @@ Boss text supplied by the entry event is not a required startup option unless So
|
|
|
642
665
|
A generated entry guard that requires the text already in context is likewise
|
|
643
666
|
not independent Source evidence; it is a producer defect when the entry action
|
|
644
667
|
has yet to copy the event's text. Do not compensate with a required option or
|
|
645
|
-
invented startup task.
|
|
646
|
-
|
|
668
|
+
invented startup task. Entry metadata does not populate fresh context before
|
|
669
|
+
FSM guards execute. Interrupted-step checkpoints retain accepted input.
|
|
647
670
|
A source-appropriate optional seed may remain, and genuine required bootstrap catalogs or other options shall not be erased or filled with invented defaults.
|
|
648
671
|
|
|
649
672
|
Concrete player binding and prompt identity are host policy and shall not enter `PlaybookRuntimeOptions`, machine input, or the emitted artifact.
|
|
@@ -654,6 +677,7 @@ The host shall derive `promptIdentity` from the current effective model when pre
|
|
|
654
677
|
|
|
655
678
|
```typescript
|
|
656
679
|
interface PlaybookPorts {
|
|
680
|
+
recordStep?(step: PlaybookStepRecord, position?: PlaybookRuntimeSnapshot): Promise<void>;
|
|
657
681
|
callPlayer(
|
|
658
682
|
roleId: string,
|
|
659
683
|
prompt: string,
|
|
@@ -1283,6 +1307,13 @@ field.
|
|
|
1283
1307
|
It shall reject an absent required field, an undeclared or extra field, a
|
|
1284
1308
|
field supplied by the wrong authority, an invalid value, or any mutually
|
|
1285
1309
|
inconsistent candidate before FSM delivery.
|
|
1310
|
+
When no outcome matches, a governed judge may instead return exactly
|
|
1311
|
+
`{ blocked: <nonempty explanation> }`.
|
|
1312
|
+
The shared reconciler retains this candidate and returns `no-matching-outcome`
|
|
1313
|
+
with its explanation as a `runtime-defect` cause; it authorizes neither a
|
|
1314
|
+
corrective judge nor an FSM outcome.
|
|
1315
|
+
Both shared and bespoke judge prompts shall state this alternative explicitly
|
|
1316
|
+
instead of forcing a match when the declared results are incomplete.
|
|
1286
1317
|
|
|
1287
1318
|
For a non-deferred candidate, reconciliation shall require a complete durable
|
|
1288
1319
|
physical receipt, or the complete cumulative logical receipt of a deferred
|
|
@@ -1356,7 +1387,8 @@ Two default adjudication strategies, in selection order:
|
|
|
1356
1387
|
answer keyed to exactly one of the declared guards: a nongoverned player uses
|
|
1357
1388
|
`{ guard, …structuralPayloadFields }`, while a governed schema-3 player uses
|
|
1358
1389
|
`{ guard, …semanticOwnedPayloadFields }` and explicitly forbids every other
|
|
1359
|
-
payload field.
|
|
1390
|
+
payload field. The governed form also permits the blocked report above when no outcome matches.
|
|
1391
|
+
Both forms exclude the runtime-injected direct-Captain
|
|
1360
1392
|
`question` and `response` fields above. The prompt shall identify hidden control work,
|
|
1361
1393
|
prohibit tool use, file inspection, and external evidence, direct the judge
|
|
1362
1394
|
to decide only from the supplied actor output and declared outcomes, and
|
|
@@ -1887,7 +1919,8 @@ rehydrate it in a later process (DR-014). A runtime that implements either
|
|
|
1887
1919
|
member shall implement both. When generated for a runtime whose host needs
|
|
1888
1920
|
durability, the pair shall behave as follows.
|
|
1889
1921
|
|
|
1890
|
-
`exportSnapshot()`
|
|
1922
|
+
A shared runtime may also receive the internal step-start snapshot for its current invocation or `exportSnapshot({child})` for a bridge-owned child during startup; these capture stopped positions without execution.
|
|
1923
|
+
Ordinary `exportSnapshot()` shall return `undefined` unless the runtime is at a safe
|
|
1891
1924
|
capture point: initialized, not disposing or disposed, no active
|
|
1892
1925
|
`handleBossInput`/`resumePlaybookCall` boundary, and the root actor at a
|
|
1893
1926
|
quiescent state with actor status `active`.
|
|
@@ -2096,6 +2129,14 @@ interface PlaybookControlAction {
|
|
|
2096
2129
|
reason?: PlaybookControlActionReason; // why it is not `ready`
|
|
2097
2130
|
}
|
|
2098
2131
|
|
|
2132
|
+
interface PlaybookRecoveryOffer {
|
|
2133
|
+
prompt: string;
|
|
2134
|
+
description?: string;
|
|
2135
|
+
preparation?: string;
|
|
2136
|
+
evidence?: JsonValue;
|
|
2137
|
+
continuation: { kind: 'reply' } | { kind: 'runtime'; actionId: string };
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2099
2140
|
interface PlaybookControlView {
|
|
2100
2141
|
state: PlaybookState;
|
|
2101
2142
|
stateDescription?: string; // runtime-published meaning of the current state
|
|
@@ -2103,12 +2144,13 @@ interface PlaybookControlView {
|
|
|
2103
2144
|
pendingQuestions: readonly PlaybookPendingBossQuestion[];
|
|
2104
2145
|
lastError?: NormalizedError;
|
|
2105
2146
|
actions: readonly PlaybookControlAction[];
|
|
2147
|
+
recovery?: PlaybookRecoveryOffer;
|
|
2106
2148
|
}
|
|
2107
2149
|
|
|
2108
2150
|
type PlaybookControlReceipt =
|
|
2109
2151
|
| { disposition: 'rejected'; reason: string } // before any effect
|
|
2110
2152
|
| { disposition: 'executed'; run: PlaybookRunResult }
|
|
2111
|
-
| { disposition: 'failed'; error: NormalizedError }; // effects may exist
|
|
2153
|
+
| { disposition: 'failed'; error: NormalizedError; run?: PlaybookRunResult }; // effects may exist
|
|
2112
2154
|
|
|
2113
2155
|
// Optional PlaybookRuntime members — both or neither:
|
|
2114
2156
|
describe?(): PlaybookControlView;
|
|
@@ -2129,6 +2171,7 @@ throw. The view carries the current normalized state descriptor, the state
|
|
|
2129
2171
|
description defined below, the authored context projection defined below, the
|
|
2130
2172
|
pending Boss questions with their stable ids, the last recorded error in
|
|
2131
2173
|
normalized form, and the currently valid actions.
|
|
2174
|
+
The shared runtime owns the optional recovery context and invocation checkpoint; linked artifacts need no recovery-specific source, event, or actor.
|
|
2132
2175
|
|
|
2133
2176
|
`stateDescription` is the runtime's own Boss-facing statement of what its
|
|
2134
2177
|
current state means, taken from the same source state descriptions the action
|
|
@@ -2181,25 +2224,17 @@ parked-session snapshot uses (actor status `active`, quiescent, no pending
|
|
|
2181
2224
|
nested call); anywhere else `actions` is empty while the rest of the view
|
|
2182
2225
|
still describes the state.
|
|
2183
2226
|
While effect-possible outcome evidence remains unresolved, the view shall omit its pending Boss questions and state description and shall replace every ordinary action with exactly `reconcile:unresolved-effect` labeled `Retry unresolved effect reconciliation` and `abandon:unresolved-effect` labeled `Abandon unresolved workflow attempt`.
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
context member the machine's entry action copies the exact Boss text into
|
|
2191
|
-
(DR-034), the retry event is that deterministic entry event built from the
|
|
2192
|
-
live snapshot's member — excluded when the member is absent, not a string,
|
|
2193
|
-
or blank, and never falling back to the record. Where it names no member,
|
|
2194
|
-
the retry event is the recorded last classified event (the event a public
|
|
2195
|
-
Boss boundary sent that drove the run into `failed`, kept with its recorded
|
|
2196
|
-
payload), and there is none while the runtime holds none. The member is
|
|
2197
|
-
declared, never inferred from a context member that happens to match the
|
|
2198
|
-
entry event's text field.
|
|
2227
|
+
A valid invocation checkpoint is the only source of a retry: it retries only the stopped invocation after its effect checks, using `retry:<EVENT_TYPE>` for the same entry target or `retry:step` otherwise.
|
|
2228
|
+
Saved-result assessment (`retry:adjudication`) never repeats the player and is not offered for an output the machine has already received (checkpoint `delivered: true`), and verified read-only restoration (`retry:restored-step`) records an exact baseline check before replay.
|
|
2229
|
+
These retries have standing `ready`; an unsafe checkpoint grants no replay. Saved-result assessment withholds ordinary state jumps. Reconciliation and abandonment remain available whenever effects are unresolved, even alongside an eligible assessment or restoration.
|
|
2230
|
+
Ordinary retry and jump actions use source state descriptions and require assessment and unresolved-effect fences to be absent:
|
|
2231
|
+
|
|
2232
|
+
- **Failure-state retry** — a valid invocation checkpoint retries that step only; a completed saved result is consumed without executing the operation again. No checkpoint means no retry. Legacy `entryEvent.contextField` metadata is ignored.
|
|
2199
2233
|
- **Jump entries** — for each registered resumable state id whose
|
|
2200
2234
|
explicit-state-jump event (`BOSS_INTERRUPT` with that `targetId`, optional
|
|
2201
2235
|
textual fields omitted) the live snapshot accepts, guards included, the
|
|
2202
|
-
runtime shall advertise `jump:<stateId
|
|
2236
|
+
runtime shall advertise `jump:<stateId>`, except that a jump out of a governed
|
|
2237
|
+
failure must also pass the full failed-attempt effect check above.
|
|
2203
2238
|
|
|
2204
2239
|
A candidate whose event requires a payload the runtime cannot source from
|
|
2205
2240
|
recorded state shall be excluded from `actions` — `apply` never invents free
|
|
@@ -2209,9 +2244,8 @@ back to a target id or to the replayed event type, because a controller host
|
|
|
2209
2244
|
names an executed or refused action by its label and never by its id, so an
|
|
2210
2245
|
identifier used as a label defeats that substitution. A jump whose target
|
|
2211
2246
|
publishes no description is therefore not advertised — borrowing another
|
|
2212
|
-
state's description would name the wrong state — and a retry
|
|
2213
|
-
|
|
2214
|
-
neither exists.
|
|
2247
|
+
state's description would name the wrong state — and a retry requires the
|
|
2248
|
+
interrupted invocation's source description.
|
|
2215
2249
|
|
|
2216
2250
|
`apply({ actionId, key, signal })` shall revalidate the action against the
|
|
2217
2251
|
live state and settle `{ disposition: 'rejected', reason }` with no effect
|
|
@@ -2227,7 +2261,7 @@ pair, and may execute once the action is advertised — and a key whose call
|
|
|
2227
2261
|
threw before reaching acceptance (lifecycle misuse, invalid input, a
|
|
2228
2262
|
pre-acceptance abort, a rejected start-boundary sink) likewise records
|
|
2229
2263
|
nothing, so a later call with that key may execute.
|
|
2230
|
-
Executing
|
|
2264
|
+
Executing a jump sends the validated event through the same actor drive as `handleBossInput` — state
|
|
2231
2265
|
transitions, player/judge boundaries, statuses, and traces flow unchanged —
|
|
2232
2266
|
and settles `executed` with the projected run result, or `failed` with the
|
|
2233
2267
|
normalized error when the run settles in the failure state, aborts, or a
|
|
@@ -2260,12 +2294,18 @@ signal's own abort reason, in which case it evidences the cancellation and is
|
|
|
2260
2294
|
dropped, not latched
|
|
2261
2295
|
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
|
|
2262
2296
|
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2297
|
+
Recorded control receipts are process-local. Invocation checkpoints retain the original input and accepted output across restoration; a restored runtime offers only the currently valid step controls.
|
|
2298
|
+
|
|
2299
|
+
The optional `PlaybookPorts.recordStep(step, position?)` lets a durable host save ordinary work without understanding machine internals.
|
|
2300
|
+
The shared runtime records player, direct-Captain and script starts before execution and their actor results before advancing.
|
|
2301
|
+
The runtime owns the complete stopped position, including accepted Boss input; the host joins nested frames and atomically stores starts and results; retention changes wait for settlement.
|
|
2302
|
+
An invocation inside a parallel region records its start and result without a single-invocation recovery position, including when only its last region is resuming.
|
|
2303
|
+
Sequential invocations after the join use ordinary checkpoints.
|
|
2304
|
+
A custom runtime that cannot provide a position remains runnable, but a crash without a supported position returns to Captain with recorded work preserved.
|
|
2305
|
+
Automatic recovery requires a failed or quiescent outcome produced by an operation in the same turn; resuming or adopting an older stop starts no preparation.
|
|
2306
|
+
The store identifies the step that owns a saved position, so a consumed result cannot be attached to a later stopped snapshot.
|
|
2307
|
+
Only internal step-start capture creates a synthetic failed position; public snapshot capture returns the actual stopped state.
|
|
2308
|
+
Opening or reporting an interrupted run executes no work; Boss chooses the next action after checking outside effects and stopping any surviving worker.
|
|
2269
2309
|
|
|
2270
2310
|
## Abort
|
|
2271
2311
|
|
|
@@ -2374,6 +2414,10 @@ The runtime shall emit, at minimum:
|
|
|
2374
2414
|
semantics matter to Boss — e.g., `respondToReview`, `failed`). The
|
|
2375
2415
|
default is to emit on every transition and let the host filter; hosts
|
|
2376
2416
|
may bind a stricter rule.
|
|
2417
|
+
Mark both a player-question status and its waiting marker with status data
|
|
2418
|
+
`{ kind: "boss-question" }`; the session Captain replaces those statuses with
|
|
2419
|
+
its own clear reply, using the complete pending question from the control view.
|
|
2420
|
+
Keep the original question in state and telemetry, and deliver Boss input unchanged.
|
|
2377
2421
|
- One `emitTelemetry` per state transition under a namespaced topic
|
|
2378
2422
|
(recommended `playbook.fsm.state`), with structured `from`, `to`, `event`,
|
|
2379
2423
|
`previousState`, and `state` fields. Descriptors carry the JSON-safe XState
|
package/src/runtime.d.ts
CHANGED
|
@@ -173,10 +173,13 @@ export type PlaybookRunResult = {
|
|
|
173
173
|
pendingCall: PlaybookPendingCall;
|
|
174
174
|
};
|
|
175
175
|
export interface PlaybookPorts {
|
|
176
|
+
/** Acknowledge the durable start before work and its result before transition. */
|
|
177
|
+
recordStep?(step: PlaybookStepRecord, position?: PlaybookRuntimeSnapshot): Promise<void>;
|
|
176
178
|
callPlayer(roleId: string, prompt: string, signal: AbortSignal, options: PlayerCallOptions): Promise<PlayerResult>;
|
|
177
179
|
callCaptain(prompt: string, signal: AbortSignal, options: CaptainCallOptions): Promise<CaptainResult>;
|
|
178
180
|
callJudge(prompt: string, signal: AbortSignal): Promise<string>;
|
|
179
181
|
callPlaybook(request: PlaybookCallRequest, signal: AbortSignal): Promise<PlaybookCallStart>;
|
|
182
|
+
/** Mark question presentation with data.kind = "boss-question"; Captain relays it from pendingQuestions. */
|
|
180
183
|
emitStatus(message: string, data?: unknown): Promise<void>;
|
|
181
184
|
emitTelemetry(event: {
|
|
182
185
|
topic: string;
|
|
@@ -275,6 +278,8 @@ export interface PlaybookEffectBoundary {
|
|
|
275
278
|
readonly baseline: PlaybookRepositoryObservation;
|
|
276
279
|
readonly after?: PlaybookRepositoryObservation;
|
|
277
280
|
readonly physicalReceipt?: PlaybookRepositoryReceipt;
|
|
281
|
+
/** A later exact restoration permits replay of this standalone read-only call. */
|
|
282
|
+
readonly restored?: PlaybookRepositoryObservation;
|
|
278
283
|
readonly finalText?: string;
|
|
279
284
|
readonly semanticCandidate?: JsonValue;
|
|
280
285
|
readonly initialSemanticCandidate?: JsonValue;
|
|
@@ -286,7 +291,7 @@ export interface PlaybookEffectBoundary {
|
|
|
286
291
|
readonly logicalOperationId?: string;
|
|
287
292
|
}
|
|
288
293
|
/** One physical boundary before the host assigns attempt and sequence data. */
|
|
289
|
-
export type PlaybookEffectBoundaryStart = Omit<PlaybookEffectBoundary, 'sequence' | 'attemptId' | 'attemptNumber' | 'after' | 'physicalReceipt' | 'finalText' | 'semanticCandidate' | 'initialSemanticCandidate'>;
|
|
294
|
+
export type PlaybookEffectBoundaryStart = Omit<PlaybookEffectBoundary, 'sequence' | 'attemptId' | 'attemptNumber' | 'after' | 'physicalReceipt' | 'restored' | 'finalText' | 'semanticCandidate' | 'initialSemanticCandidate'>;
|
|
290
295
|
/** One deferred logical operation spanning its ordered physical boundaries. */
|
|
291
296
|
export interface PlaybookEffectLogicalOperation {
|
|
292
297
|
readonly sequence: number;
|
|
@@ -388,8 +393,26 @@ export interface PlaybookRuntimeSnapshot {
|
|
|
388
393
|
readonly boundaryPrefix: number;
|
|
389
394
|
readonly attemptId: string | null;
|
|
390
395
|
};
|
|
396
|
+
/** Interrupted invocation, captured before its external call (DR-069). */
|
|
397
|
+
recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
|
|
391
398
|
suspendedCall?: PlaybookSuspendedCall;
|
|
392
399
|
}
|
|
400
|
+
export interface PlaybookRecoveryCheckpoint {
|
|
401
|
+
readonly id?: string;
|
|
402
|
+
readonly stateId: string;
|
|
403
|
+
readonly prompt: string;
|
|
404
|
+
readonly machine: JsonValue;
|
|
405
|
+
readonly boundaryPrefix: number;
|
|
406
|
+
/** Completed actor output supplied by the execution journal on restore. */
|
|
407
|
+
readonly result?: JsonValue;
|
|
408
|
+
readonly delivered?: true;
|
|
409
|
+
}
|
|
410
|
+
export interface PlaybookStepRecord {
|
|
411
|
+
readonly id: string;
|
|
412
|
+
readonly kind: 'player' | 'captain' | 'script';
|
|
413
|
+
readonly stateId: string;
|
|
414
|
+
readonly result?: JsonValue;
|
|
415
|
+
}
|
|
393
416
|
export type PlaybookControlStanding = 'ready' | 'no-op' | 'blocked';
|
|
394
417
|
export type PlaybookControlActionReason = 'receipt-complete';
|
|
395
418
|
export interface PlaybookControlAction {
|
|
@@ -398,6 +421,20 @@ export interface PlaybookControlAction {
|
|
|
398
421
|
standing?: PlaybookControlStanding;
|
|
399
422
|
reason?: PlaybookControlActionReason;
|
|
400
423
|
}
|
|
424
|
+
export interface PlaybookRecoveryOffer {
|
|
425
|
+
prompt: string;
|
|
426
|
+
description?: string;
|
|
427
|
+
/** Runtime-owned conditions to satisfy; these never authorize task completion. */
|
|
428
|
+
preparation?: string;
|
|
429
|
+
/** Saved observations and result choices for diagnosing the stopped step. */
|
|
430
|
+
evidence?: JsonValue;
|
|
431
|
+
continuation: {
|
|
432
|
+
kind: 'reply';
|
|
433
|
+
} | {
|
|
434
|
+
kind: 'runtime';
|
|
435
|
+
actionId: string;
|
|
436
|
+
};
|
|
437
|
+
}
|
|
401
438
|
export interface PlaybookControlView {
|
|
402
439
|
state: PlaybookState;
|
|
403
440
|
stateDescription?: string;
|
|
@@ -405,6 +442,8 @@ export interface PlaybookControlView {
|
|
|
405
442
|
pendingQuestions: readonly PlaybookPendingBossQuestion[];
|
|
406
443
|
lastError?: NormalizedError;
|
|
407
444
|
actions: readonly PlaybookControlAction[];
|
|
445
|
+
/** Context only for an explicitly authorized Captain preparation call. */
|
|
446
|
+
recovery?: PlaybookRecoveryOffer;
|
|
408
447
|
}
|
|
409
448
|
export type PlaybookControlReceipt = {
|
|
410
449
|
disposition: 'rejected';
|
|
@@ -415,13 +454,16 @@ export type PlaybookControlReceipt = {
|
|
|
415
454
|
} | {
|
|
416
455
|
disposition: 'failed';
|
|
417
456
|
error: NormalizedError;
|
|
457
|
+
run?: PlaybookRunResult;
|
|
418
458
|
};
|
|
419
459
|
export interface PlaybookRetainedGenerationMetadata {
|
|
420
460
|
readonly unfinishedFinalStateIds: readonly string[];
|
|
421
461
|
}
|
|
422
462
|
export interface PlaybookRuntime {
|
|
423
463
|
init(session: PlaybookSession): Promise<void>;
|
|
424
|
-
exportSnapshot?(
|
|
464
|
+
exportSnapshot?(checkpoint?: {
|
|
465
|
+
child?: PlaybookPendingCall;
|
|
466
|
+
}): PlaybookRuntimeSnapshot | undefined;
|
|
425
467
|
restore?(session: PlaybookSession, snapshot: PlaybookRuntimeSnapshot): Promise<void>;
|
|
426
468
|
adopt?(session: PlaybookSession, snapshot: PlaybookRuntimeSnapshot, context: PlaybookAdoptionContext): Promise<void>;
|
|
427
469
|
readonly retainedGenerationMetadata?: PlaybookRetainedGenerationMetadata;
|
|
@@ -447,6 +489,8 @@ export interface PlaybookRuntime {
|
|
|
447
489
|
handleBossInput(turn: {
|
|
448
490
|
text: string;
|
|
449
491
|
signal: AbortSignal;
|
|
492
|
+
/** Called synchronously just before accepting the event, at most once. */
|
|
493
|
+
onAccepted?: () => void;
|
|
450
494
|
}): Promise<PlaybookRunResult>;
|
|
451
495
|
resumePlaybookCall(input: {
|
|
452
496
|
callId: string;
|