@sublang/playbook 9.0.0 → 10.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.
Files changed (64) hide show
  1. package/docs/cli.md +51 -8
  2. package/docs/embedding.md +38 -12
  3. package/package.json +7 -3
  4. package/reference/sdlc/captain.md +14 -10
  5. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +33 -13
  6. package/reference/sdlc/captain.playbook/captain.fsm.js +80 -9
  7. package/reference/sdlc/captain.playbook/captain.fsm.ts +137 -18
  8. package/reference/sdlc/captain.playbook/captain.gears.md +10 -6
  9. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +5 -1
  10. package/reference/sdlc/captain.playbook/captain.playbook.js +140 -10
  11. package/reference/sdlc/captain.playbook/captain.playbook.ts +188 -16
  12. package/reference/sdlc/code.md +0 -1
  13. package/reference/sdlc/code.playbook/bin/interactive-session.js +170 -17
  14. package/reference/sdlc/code.playbook/bin/launch-config.js +136 -4
  15. package/reference/sdlc/code.playbook/bin/playbook.js +81 -4
  16. package/reference/sdlc/code.playbook/bin/repository-effects.js +2930 -0
  17. package/reference/sdlc/code.playbook/bin/run.js +365 -63
  18. package/reference/sdlc/code.playbook/bin/session-store.js +2877 -209
  19. package/reference/sdlc/code.playbook/code.fsm.d.ts +7 -0
  20. package/reference/sdlc/code.playbook/code.fsm.js +74 -25
  21. package/reference/sdlc/code.playbook/code.fsm.ts +83 -29
  22. package/reference/sdlc/code.playbook/code.gears.md +0 -2
  23. package/reference/sdlc/code.playbook/code.playbook.d.ts +5 -2
  24. package/reference/sdlc/code.playbook/code.playbook.js +54 -2
  25. package/reference/sdlc/code.playbook/code.playbook.ts +75 -6
  26. package/reference/sdlc/code.playbook/code.registry.d.ts +10 -3
  27. package/reference/sdlc/code.playbook/code.registry.js +10 -3
  28. package/reference/sdlc/code.playbook/code.registry.ts +23 -5
  29. package/reference/sdlc/code.playbook/playbook-captain.d.ts +99 -7
  30. package/reference/sdlc/code.playbook/playbook-captain.js +1850 -72
  31. package/reference/sdlc/code.playbook/playbook-captain.ts +2759 -96
  32. package/reference/sdlc/decide.md +0 -1
  33. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +7 -0
  34. package/reference/sdlc/decide.playbook/decide.fsm.js +80 -29
  35. package/reference/sdlc/decide.playbook/decide.fsm.ts +89 -31
  36. package/reference/sdlc/decide.playbook/decide.gears.md +0 -1
  37. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +13 -5
  38. package/reference/sdlc/decide.playbook/decide.playbook.js +1712 -91
  39. package/reference/sdlc/decide.playbook/decide.playbook.ts +2677 -136
  40. package/reference/sdlc/decide.playbook/decide.registry.d.ts +7 -3
  41. package/reference/sdlc/decide.playbook/decide.registry.js +10 -3
  42. package/reference/sdlc/decide.playbook/decide.registry.ts +20 -5
  43. package/reference/sdlc/review.playbook/review.fsm.d.ts +7 -0
  44. package/reference/sdlc/review.playbook/review.fsm.js +133 -12
  45. package/reference/sdlc/review.playbook/review.fsm.ts +140 -12
  46. package/reference/sdlc/review.playbook/review.playbook.d.ts +5 -2
  47. package/reference/sdlc/review.playbook/review.playbook.js +65 -2
  48. package/reference/sdlc/review.playbook/review.playbook.ts +83 -6
  49. package/reference/sdlc/review.playbook/review.registry.d.ts +10 -3
  50. package/reference/sdlc/review.playbook/review.registry.js +10 -3
  51. package/reference/sdlc/review.playbook/review.registry.ts +23 -5
  52. package/slc/gears2fsm.md +6 -5
  53. package/slc/link.md +544 -41
  54. package/src/accepted-outcome.d.ts +18 -0
  55. package/src/accepted-outcome.js +94 -0
  56. package/src/accepted-outcome.ts +140 -0
  57. package/src/runtime.d.ts +164 -3
  58. package/src/runtime.ts +213 -2
  59. package/src/xstate-playbook-runtime.d.ts +149 -10
  60. package/src/xstate-playbook-runtime.js +2569 -270
  61. package/src/xstate-playbook-runtime.ts +4133 -490
  62. package/src/xstate-runtime.d.ts +59 -1
  63. package/src/xstate-runtime.js +866 -7
  64. package/src/xstate-runtime.ts +1397 -7
@@ -0,0 +1,18 @@
1
+ import type { PlaybookState } from './runtime.js';
2
+ export declare const ACCEPTED_OUTCOME_ACTION_TYPE = "playbook.acceptedOutcome";
3
+ export interface AcceptedOutcomeReceipt {
4
+ readonly source: string;
5
+ readonly target: string;
6
+ readonly acceptedOutcome: string;
7
+ }
8
+ interface InspectedAction {
9
+ readonly type: string;
10
+ readonly params: unknown;
11
+ }
12
+ export interface AcceptedOutcomeConsumer {
13
+ capture(action: InspectedAction): void;
14
+ confirm(previousState: PlaybookState | undefined, state: PlaybookState): readonly AcceptedOutcomeReceipt[];
15
+ reset(): void;
16
+ }
17
+ export declare function createAcceptedOutcomeConsumer(isDeclared: (source: string, acceptedOutcome: string) => boolean): AcceptedOutcomeConsumer;
18
+ export {};
@@ -0,0 +1,94 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ export const ACCEPTED_OUTCOME_ACTION_TYPE = 'playbook.acceptedOutcome';
4
+ function exactMarkerParams(value) {
5
+ if (value === null ||
6
+ typeof value !== 'object' ||
7
+ Array.isArray(value) ||
8
+ (Object.getPrototypeOf(value) !== Object.prototype &&
9
+ Object.getPrototypeOf(value) !== null)) {
10
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} params must be a plain object`);
11
+ }
12
+ const descriptors = Object.getOwnPropertyDescriptors(value);
13
+ const keys = Reflect.ownKeys(descriptors);
14
+ const expected = ['acceptedOutcome', 'source', 'target'];
15
+ if (keys.length !== expected.length ||
16
+ keys.some((key) => typeof key !== 'string' || !expected.includes(key))) {
17
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} params must contain exactly source, target, and acceptedOutcome`);
18
+ }
19
+ const stringValue = (key) => {
20
+ const descriptor = descriptors[key];
21
+ if (descriptor === undefined ||
22
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value') ||
23
+ descriptor.enumerable !== true ||
24
+ typeof descriptor.value !== 'string' ||
25
+ descriptor.value.trim().length === 0) {
26
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} params.${key} must be a nonempty enumerable string data property`);
27
+ }
28
+ return descriptor.value;
29
+ };
30
+ return Object.freeze({
31
+ source: stringValue('source'),
32
+ target: stringValue('target'),
33
+ acceptedOutcome: stringValue('acceptedOutcome'),
34
+ });
35
+ }
36
+ export function createAcceptedOutcomeConsumer(isDeclared) {
37
+ let pending = [];
38
+ let invalidBatch = false;
39
+ return Object.freeze({
40
+ capture(action) {
41
+ if (action.type !== ACCEPTED_OUTCOME_ACTION_TYPE) {
42
+ return;
43
+ }
44
+ if (invalidBatch)
45
+ return;
46
+ try {
47
+ const marker = exactMarkerParams(action.params);
48
+ if (!isDeclared(marker.source, marker.acceptedOutcome)) {
49
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} names undeclared outcome ` +
50
+ `${marker.source}.${marker.acceptedOutcome}`);
51
+ }
52
+ pending.push(marker);
53
+ }
54
+ catch (error) {
55
+ pending = [];
56
+ invalidBatch = true;
57
+ throw error;
58
+ }
59
+ },
60
+ confirm(previousState, state) {
61
+ if (invalidBatch) {
62
+ pending = [];
63
+ invalidBatch = false;
64
+ return Object.freeze([]);
65
+ }
66
+ if (pending.length === 0)
67
+ return Object.freeze([]);
68
+ const captured = pending;
69
+ pending = [];
70
+ if (previousState === undefined) {
71
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} marker has no prior public root snapshot`);
72
+ }
73
+ const seen = new Set();
74
+ for (const marker of captured) {
75
+ const identity = marker.source;
76
+ if (seen.has(identity)) {
77
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} source ${marker.source} was instrumented more than once in one action batch`);
78
+ }
79
+ seen.add(identity);
80
+ if (!previousState.activeStateIds.includes(marker.source)) {
81
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} source ${marker.source} was not confirmed by the prior public root snapshot`);
82
+ }
83
+ if (!state.activeStateIds.includes(marker.target)) {
84
+ throw new TypeError(`${ACCEPTED_OUTCOME_ACTION_TYPE} target ${marker.target} was not confirmed by the public root snapshot`);
85
+ }
86
+ }
87
+ return Object.freeze(captured);
88
+ },
89
+ reset() {
90
+ pending = [];
91
+ invalidBatch = false;
92
+ },
93
+ });
94
+ }
@@ -0,0 +1,140 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ import type { PlaybookState } from './runtime.js';
5
+
6
+ export const ACCEPTED_OUTCOME_ACTION_TYPE = 'playbook.acceptedOutcome';
7
+
8
+ export interface AcceptedOutcomeReceipt {
9
+ readonly source: string;
10
+ readonly target: string;
11
+ readonly acceptedOutcome: string;
12
+ }
13
+
14
+ interface InspectedAction {
15
+ readonly type: string;
16
+ readonly params: unknown;
17
+ }
18
+
19
+ export interface AcceptedOutcomeConsumer {
20
+ capture(action: InspectedAction): void;
21
+ confirm(
22
+ previousState: PlaybookState | undefined,
23
+ state: PlaybookState,
24
+ ): readonly AcceptedOutcomeReceipt[];
25
+ reset(): void;
26
+ }
27
+
28
+ function exactMarkerParams(value: unknown): AcceptedOutcomeReceipt {
29
+ if (
30
+ value === null ||
31
+ typeof value !== 'object' ||
32
+ Array.isArray(value) ||
33
+ (Object.getPrototypeOf(value) !== Object.prototype &&
34
+ Object.getPrototypeOf(value) !== null)
35
+ ) {
36
+ throw new TypeError(
37
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} params must be a plain object`,
38
+ );
39
+ }
40
+ const descriptors = Object.getOwnPropertyDescriptors(value);
41
+ const keys = Reflect.ownKeys(descriptors);
42
+ const expected = ['acceptedOutcome', 'source', 'target'];
43
+ if (
44
+ keys.length !== expected.length ||
45
+ keys.some((key) => typeof key !== 'string' || !expected.includes(key))
46
+ ) {
47
+ throw new TypeError(
48
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} params must contain exactly source, target, and acceptedOutcome`,
49
+ );
50
+ }
51
+ const stringValue = (key: string): string => {
52
+ const descriptor = descriptors[key];
53
+ if (
54
+ descriptor === undefined ||
55
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value') ||
56
+ descriptor.enumerable !== true ||
57
+ typeof descriptor.value !== 'string' ||
58
+ descriptor.value.trim().length === 0
59
+ ) {
60
+ throw new TypeError(
61
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} params.${key} must be a nonempty enumerable string data property`,
62
+ );
63
+ }
64
+ return descriptor.value;
65
+ };
66
+ return Object.freeze({
67
+ source: stringValue('source'),
68
+ target: stringValue('target'),
69
+ acceptedOutcome: stringValue('acceptedOutcome'),
70
+ });
71
+ }
72
+
73
+ export function createAcceptedOutcomeConsumer(
74
+ isDeclared: (source: string, acceptedOutcome: string) => boolean,
75
+ ): AcceptedOutcomeConsumer {
76
+ let pending: AcceptedOutcomeReceipt[] = [];
77
+ let invalidBatch = false;
78
+ return Object.freeze({
79
+ capture(action: InspectedAction) {
80
+ if (action.type !== ACCEPTED_OUTCOME_ACTION_TYPE) {
81
+ return;
82
+ }
83
+ if (invalidBatch) return;
84
+ try {
85
+ const marker = exactMarkerParams(action.params);
86
+ if (!isDeclared(marker.source, marker.acceptedOutcome)) {
87
+ throw new TypeError(
88
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} names undeclared outcome ` +
89
+ `${marker.source}.${marker.acceptedOutcome}`,
90
+ );
91
+ }
92
+ pending.push(marker);
93
+ } catch (error) {
94
+ pending = [];
95
+ invalidBatch = true;
96
+ throw error;
97
+ }
98
+ },
99
+ confirm(previousState: PlaybookState | undefined, state: PlaybookState) {
100
+ if (invalidBatch) {
101
+ pending = [];
102
+ invalidBatch = false;
103
+ return Object.freeze([]);
104
+ }
105
+ if (pending.length === 0) return Object.freeze([]);
106
+ const captured = pending;
107
+ pending = [];
108
+ if (previousState === undefined) {
109
+ throw new TypeError(
110
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} marker has no prior public root snapshot`,
111
+ );
112
+ }
113
+ const seen = new Set<string>();
114
+ for (const marker of captured) {
115
+ const identity = marker.source;
116
+ if (seen.has(identity)) {
117
+ throw new TypeError(
118
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} source ${marker.source} was instrumented more than once in one action batch`,
119
+ );
120
+ }
121
+ seen.add(identity);
122
+ if (!previousState.activeStateIds.includes(marker.source)) {
123
+ throw new TypeError(
124
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} source ${marker.source} was not confirmed by the prior public root snapshot`,
125
+ );
126
+ }
127
+ if (!state.activeStateIds.includes(marker.target)) {
128
+ throw new TypeError(
129
+ `${ACCEPTED_OUTCOME_ACTION_TYPE} target ${marker.target} was not confirmed by the public root snapshot`,
130
+ );
131
+ }
132
+ }
133
+ return Object.freeze(captured);
134
+ },
135
+ reset() {
136
+ pending = [];
137
+ invalidBatch = false;
138
+ },
139
+ });
140
+ }
package/src/runtime.d.ts CHANGED
@@ -55,6 +55,7 @@ export interface PlaybookSuspendedCall extends PlaybookPendingCall {
55
55
  stateId: string;
56
56
  text: string;
57
57
  turnId?: number;
58
+ effectBoundaryPrefixSequence?: number | null;
58
59
  }
59
60
  export interface PlaybookCallRequest {
60
61
  callId: string;
@@ -90,6 +91,9 @@ export type PlaybookCallStart = {
90
91
  export type PlaybookRunResult = {
91
92
  outcome: 'quiescent' | 'no-action';
92
93
  state: PlaybookState;
94
+ } | {
95
+ outcome: 'unresolved-effect';
96
+ state: PlaybookState;
93
97
  } | {
94
98
  outcome: 'failed' | 'aborted';
95
99
  state: PlaybookState;
@@ -126,9 +130,14 @@ export interface PlaybookSession {
126
130
  playerSessions?: PlayerSessionStore;
127
131
  ports: PlaybookPorts;
128
132
  }
129
- export type PlaybookTraceType = 'session.started' | 'boss.input.received' | 'judge.call.started' | 'judge.call.finished' | 'player.call.started' | 'player.call.finished' | 'captain.call.started' | 'captain.call.finished' | 'playbook.call.started' | 'playbook.call.finished' | 'apply.started' | 'apply.finished' | 'fsm.transition' | 'status.emitted' | 'boss.input.settled' | 'session.disposed';
133
+ export interface PlaybookAdoptionContext {
134
+ readonly sourceSessionId: string;
135
+ readonly sourceGenerationId: string;
136
+ readonly targetChildSessionId?: string;
137
+ }
138
+ export type PlaybookTraceType = 'session.started' | 'boss.input.received' | 'judge.call.started' | 'judge.call.finished' | 'player.call.started' | 'player.call.finished' | 'captain.call.started' | 'captain.call.finished' | 'playbook.call.started' | 'playbook.call.finished' | 'apply.started' | 'apply.finished' | 'fsm.transition' | 'outcome.accepted' | 'status.emitted' | 'boss.input.settled' | 'session.disposed';
130
139
  export interface PlaybookTraceEvent {
131
- schemaVersion: 3;
140
+ schemaVersion: 4;
132
141
  sessionId: string;
133
142
  playbookId: string;
134
143
  rootSessionId: string;
@@ -153,8 +162,127 @@ export interface PlaybookPendingBossQuestion {
153
162
  question: string;
154
163
  sourceItem?: string;
155
164
  }
165
+ /** One repository disposition declared by a governed outcome arm (DR-040). */
166
+ export type PlaybookRepositoryDisposition = 'unchanged' | 'one-descendant-commit' | 'deferred';
167
+ /** A detached Git-visible repository observation owned by the effect ledger. */
168
+ export interface PlaybookRepositoryObservation {
169
+ readonly worktree: string;
170
+ readonly gitDir: string;
171
+ readonly head: string;
172
+ readonly projection: Readonly<Record<string, JsonValue>>;
173
+ readonly projectionDigest: string;
174
+ }
175
+ /** The fail-closed classification of one complete physical or logical receipt. */
176
+ export interface PlaybookRepositoryReceipt {
177
+ readonly classification: 'unchanged' | 'one-descendant-commit' | 'multiple-commits' | 'rewritten-or-non-descendant' | 'worktree-only-change' | 'concurrent-or-foreign-change' | 'observation-ambiguous';
178
+ readonly baseline: PlaybookRepositoryObservation;
179
+ readonly after?: PlaybookRepositoryObservation;
180
+ readonly commitOid?: string;
181
+ }
182
+ /** One durably ordered physical governed-player boundary (DR-040). */
183
+ export interface PlaybookEffectBoundary {
184
+ readonly sequence: number;
185
+ readonly boundaryId: string;
186
+ readonly attemptId: string;
187
+ readonly attemptNumber: number;
188
+ readonly playbookId: string;
189
+ readonly runtimeSessionId: string;
190
+ readonly turnId: number;
191
+ readonly callId: string;
192
+ readonly roleId: string;
193
+ readonly sourceStateId: string;
194
+ readonly sourceOutcomeSchema: JsonValue;
195
+ readonly dispositions: readonly PlaybookRepositoryDisposition[];
196
+ readonly canonicalWorktree: {
197
+ readonly worktree: string;
198
+ readonly gitDir: string;
199
+ };
200
+ readonly baseline: PlaybookRepositoryObservation;
201
+ readonly after?: PlaybookRepositoryObservation;
202
+ readonly physicalReceipt?: PlaybookRepositoryReceipt;
203
+ readonly finalText?: string;
204
+ readonly semanticCandidate?: JsonValue;
205
+ readonly initialSemanticCandidate?: JsonValue;
206
+ readonly correctionBudget: {
207
+ readonly limit: 1;
208
+ readonly spent: boolean;
209
+ };
210
+ readonly cohortId?: string;
211
+ readonly logicalOperationId?: string;
212
+ }
213
+ /** One physical boundary before the host assigns attempt and sequence data. */
214
+ export type PlaybookEffectBoundaryStart = Omit<PlaybookEffectBoundary, 'sequence' | 'attemptId' | 'attemptNumber' | 'after' | 'physicalReceipt' | 'finalText' | 'semanticCandidate' | 'initialSemanticCandidate'>;
215
+ /** One deferred logical operation spanning its ordered physical boundaries. */
216
+ export interface PlaybookEffectLogicalOperation {
217
+ readonly sequence: number;
218
+ readonly operationId: string;
219
+ readonly playbookId: string;
220
+ readonly runtimeSessionId: string;
221
+ readonly boundaryIds: readonly string[];
222
+ readonly originalBaseline: PlaybookRepositoryObservation;
223
+ readonly checkpoint?: PlaybookRepositoryObservation;
224
+ readonly pendingQuestion?: PlaybookPendingBossQuestion;
225
+ readonly playerContinuation?: JsonValue;
226
+ readonly checkpointRestorationEligible: boolean;
227
+ readonly logicalReceipt?: PlaybookRepositoryReceipt;
228
+ }
229
+ /** Complete detached mirror of one host-owned reconciliation ledger. */
230
+ export interface PlaybookEffectLedger {
231
+ readonly schemaVersion: 1;
232
+ readonly revision: number;
233
+ readonly boundaries: readonly PlaybookEffectBoundary[];
234
+ readonly logicalOperations: readonly PlaybookEffectLogicalOperation[];
235
+ }
236
+ /** One mutation accepted by the host-owned effect-ledger write-ahead boundary. */
237
+ export type PlaybookEffectLedgerCommand = {
238
+ readonly kind: 'start-boundaries';
239
+ readonly boundaries: readonly [
240
+ PlaybookEffectBoundaryStart,
241
+ ...PlaybookEffectBoundaryStart[]
242
+ ];
243
+ } | {
244
+ readonly kind: 'replace-boundaries';
245
+ readonly replacements: readonly [
246
+ {
247
+ readonly expected: PlaybookEffectBoundary;
248
+ readonly next: PlaybookEffectBoundary;
249
+ },
250
+ ...{
251
+ readonly expected: PlaybookEffectBoundary;
252
+ readonly next: PlaybookEffectBoundary;
253
+ }[]
254
+ ];
255
+ } | {
256
+ readonly kind: 'append-logical-operations';
257
+ readonly operations: readonly [
258
+ Omit<PlaybookEffectLogicalOperation, 'sequence'>,
259
+ ...Omit<PlaybookEffectLogicalOperation, 'sequence'>[]
260
+ ];
261
+ } | {
262
+ readonly kind: 'replace-logical-operations';
263
+ readonly replacements: readonly [
264
+ {
265
+ readonly expected: PlaybookEffectLogicalOperation;
266
+ readonly next: PlaybookEffectLogicalOperation;
267
+ },
268
+ ...{
269
+ readonly expected: PlaybookEffectLogicalOperation;
270
+ readonly next: PlaybookEffectLogicalOperation;
271
+ }[]
272
+ ];
273
+ };
274
+ /** A nonempty command batch persisted as one ledger revision. */
275
+ export type PlaybookEffectLedgerCommandBatch = readonly [
276
+ PlaybookEffectLedgerCommand,
277
+ ...PlaybookEffectLedgerCommand[]
278
+ ];
279
+ /** Live current-host seam for atomic effect-ledger observation and mutation. */
280
+ export interface PlaybookEffectLedgerCapability {
281
+ snapshot(): PlaybookEffectLedger;
282
+ writeAhead(commands: PlaybookEffectLedgerCommandBatch): Promise<PlaybookEffectLedger>;
283
+ }
156
284
  export interface PlaybookRuntimeSnapshot {
157
- schemaVersion: 3;
285
+ schemaVersion: 4;
158
286
  playbookId: string;
159
287
  machine: JsonValue;
160
288
  roleResumeTokens: {
@@ -170,6 +298,21 @@ export interface PlaybookRuntimeSnapshot {
170
298
  };
171
299
  state: PlaybookState;
172
300
  pendingBossQuestions: readonly PlaybookPendingBossQuestion[];
301
+ effectLedger: PlaybookEffectLedger;
302
+ /** Original runtime identity retained across schema-3 adoption lineage. */
303
+ retainedEffectSourceSessionId?: string;
304
+ /**
305
+ * Unsafe retained-adoption checkpoint. The marker remains durable until
306
+ * authoritative reconciliation proves its complete suffix replay-safe.
307
+ */
308
+ retainedEffectReconciliation?: {
309
+ readonly sourceSessionId: string;
310
+ readonly checkpoint: PlaybookEffectLedger;
311
+ };
312
+ failedEffectAttempt?: {
313
+ readonly boundaryPrefix: number;
314
+ readonly attemptId: string | null;
315
+ };
173
316
  suspendedCall?: PlaybookSuspendedCall;
174
317
  }
175
318
  export interface PlaybookControlAction {
@@ -194,11 +337,29 @@ export type PlaybookControlReceipt = {
194
337
  disposition: 'failed';
195
338
  error: NormalizedError;
196
339
  };
340
+ export interface PlaybookRetainedGenerationMetadata {
341
+ readonly unfinishedFinalStateIds: readonly string[];
342
+ }
197
343
  export interface PlaybookRuntime {
198
344
  init(session: PlaybookSession): Promise<void>;
199
345
  exportSnapshot?(): PlaybookRuntimeSnapshot | undefined;
200
346
  restore?(session: PlaybookSession, snapshot: PlaybookRuntimeSnapshot): Promise<void>;
347
+ adopt?(session: PlaybookSession, snapshot: PlaybookRuntimeSnapshot, context: PlaybookAdoptionContext): Promise<void>;
348
+ readonly retainedGenerationMetadata?: PlaybookRetainedGenerationMetadata;
201
349
  describe?(): PlaybookControlView;
350
+ /**
351
+ * Host-only identities of the durable envelopes that still require
352
+ * unresolved-effect settlement. The host owns their bounded projection
353
+ * from its authoritative effect ledger; no repository evidence enters a
354
+ * runtime-owned run result.
355
+ */
356
+ unresolvedEffectEnvelopes?(): readonly ({
357
+ readonly kind: 'boundary';
358
+ readonly boundaryId: string;
359
+ } | {
360
+ readonly kind: 'logical-operation';
361
+ readonly operationId: string;
362
+ })[];
202
363
  apply?(input: {
203
364
  actionId: string;
204
365
  key: string;