@sublang/playbook 8.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 (66) hide show
  1. package/README.md +3 -3
  2. package/docs/cli.md +66 -23
  3. package/docs/configuration.md +13 -8
  4. package/docs/embedding.md +45 -14
  5. package/package.json +7 -3
  6. package/reference/sdlc/captain.md +14 -10
  7. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +33 -13
  8. package/reference/sdlc/captain.playbook/captain.fsm.js +80 -9
  9. package/reference/sdlc/captain.playbook/captain.fsm.ts +137 -18
  10. package/reference/sdlc/captain.playbook/captain.gears.md +10 -6
  11. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +5 -1
  12. package/reference/sdlc/captain.playbook/captain.playbook.js +151 -10
  13. package/reference/sdlc/captain.playbook/captain.playbook.ts +200 -14
  14. package/reference/sdlc/code.md +0 -1
  15. package/reference/sdlc/code.playbook/bin/interactive-session.js +170 -17
  16. package/reference/sdlc/code.playbook/bin/launch-config.js +136 -4
  17. package/reference/sdlc/code.playbook/bin/playbook.js +81 -4
  18. package/reference/sdlc/code.playbook/bin/repository-effects.js +2930 -0
  19. package/reference/sdlc/code.playbook/bin/run.js +365 -63
  20. package/reference/sdlc/code.playbook/bin/session-store.js +2877 -209
  21. package/reference/sdlc/code.playbook/code.fsm.d.ts +11 -1
  22. package/reference/sdlc/code.playbook/code.fsm.js +85 -29
  23. package/reference/sdlc/code.playbook/code.fsm.ts +95 -33
  24. package/reference/sdlc/code.playbook/code.gears.md +0 -2
  25. package/reference/sdlc/code.playbook/code.playbook.d.ts +5 -2
  26. package/reference/sdlc/code.playbook/code.playbook.js +67 -4
  27. package/reference/sdlc/code.playbook/code.playbook.ts +87 -8
  28. package/reference/sdlc/code.playbook/code.registry.d.ts +10 -3
  29. package/reference/sdlc/code.playbook/code.registry.js +10 -3
  30. package/reference/sdlc/code.playbook/code.registry.ts +23 -5
  31. package/reference/sdlc/code.playbook/playbook-captain.d.ts +99 -7
  32. package/reference/sdlc/code.playbook/playbook-captain.js +1894 -82
  33. package/reference/sdlc/code.playbook/playbook-captain.ts +2809 -109
  34. package/reference/sdlc/decide.md +0 -1
  35. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +8 -1
  36. package/reference/sdlc/decide.playbook/decide.fsm.js +80 -29
  37. package/reference/sdlc/decide.playbook/decide.fsm.ts +89 -31
  38. package/reference/sdlc/decide.playbook/decide.gears.md +0 -1
  39. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +15 -5
  40. package/reference/sdlc/decide.playbook/decide.playbook.js +1994 -191
  41. package/reference/sdlc/decide.playbook/decide.playbook.ts +3209 -404
  42. package/reference/sdlc/decide.playbook/decide.registry.d.ts +7 -3
  43. package/reference/sdlc/decide.playbook/decide.registry.js +10 -3
  44. package/reference/sdlc/decide.playbook/decide.registry.ts +20 -5
  45. package/reference/sdlc/review.playbook/review.fsm.d.ts +7 -0
  46. package/reference/sdlc/review.playbook/review.fsm.js +133 -12
  47. package/reference/sdlc/review.playbook/review.fsm.ts +140 -12
  48. package/reference/sdlc/review.playbook/review.playbook.d.ts +5 -2
  49. package/reference/sdlc/review.playbook/review.playbook.js +78 -4
  50. package/reference/sdlc/review.playbook/review.playbook.ts +95 -8
  51. package/reference/sdlc/review.playbook/review.registry.d.ts +10 -3
  52. package/reference/sdlc/review.playbook/review.registry.js +10 -3
  53. package/reference/sdlc/review.playbook/review.registry.ts +23 -5
  54. package/slc/gears2fsm.md +25 -7
  55. package/slc/link.md +727 -82
  56. package/src/accepted-outcome.d.ts +18 -0
  57. package/src/accepted-outcome.js +94 -0
  58. package/src/accepted-outcome.ts +140 -0
  59. package/src/runtime.d.ts +165 -3
  60. package/src/runtime.ts +214 -2
  61. package/src/xstate-playbook-runtime.d.ts +162 -13
  62. package/src/xstate-playbook-runtime.js +3344 -564
  63. package/src/xstate-playbook-runtime.ts +4873 -637
  64. package/src/xstate-runtime.d.ts +76 -8
  65. package/src/xstate-runtime.js +1001 -64
  66. package/src/xstate-runtime.ts +1640 -91
package/slc/link.md CHANGED
@@ -39,7 +39,13 @@ The emitted module shall default-export a factory of the following shape:
39
39
 
40
40
  ```typescript
41
41
  interface PlaybookRuntime {
42
+ readonly retainedGenerationMetadata?: PlaybookRetainedGenerationMetadata;
42
43
  init(session: PlaybookSession): Promise<void>;
44
+ adopt?(
45
+ session: PlaybookSession,
46
+ snapshot: PlaybookRuntimeSnapshot,
47
+ context: PlaybookAdoptionContext,
48
+ ): Promise<void>;
43
49
  handleBossInput(turn: {
44
50
  text: string;
45
51
  signal: AbortSignal;
@@ -49,9 +55,23 @@ interface PlaybookRuntime {
49
55
  result: PlaybookCallResult;
50
56
  signal: AbortSignal;
51
57
  }): Promise<PlaybookRunResult>;
58
+ unresolvedEffectEnvelopes?(): readonly (
59
+ | { readonly kind: 'boundary'; readonly boundaryId: string }
60
+ | { readonly kind: 'logical-operation'; readonly operationId: string }
61
+ )[];
52
62
  dispose(): Promise<void>;
53
63
  }
54
64
 
65
+ interface PlaybookRetainedGenerationMetadata {
66
+ readonly unfinishedFinalStateIds: readonly string[];
67
+ }
68
+
69
+ interface PlaybookAdoptionContext {
70
+ readonly sourceSessionId: string;
71
+ readonly sourceGenerationId: string;
72
+ readonly targetChildSessionId?: string;
73
+ }
74
+
55
75
  interface PlaybookSession {
56
76
  sessionId: string;
57
77
  playbookId: string;
@@ -121,10 +141,160 @@ interface PlaybookSuspendedCall extends PlaybookPendingCall {
121
141
  stateId: string;
122
142
  text: string;
123
143
  turnId?: number;
144
+ effectBoundaryPrefixSequence?: number | null;
145
+ }
146
+
147
+ type PlaybookRepositoryReceiptClassification =
148
+ | 'unchanged'
149
+ | 'one-descendant-commit'
150
+ | 'multiple-commits'
151
+ | 'rewritten-or-non-descendant'
152
+ | 'worktree-only-change'
153
+ | 'concurrent-or-foreign-change'
154
+ | 'observation-ambiguous';
155
+
156
+ interface PlaybookRepositoryObservation {
157
+ readonly worktree: string;
158
+ readonly gitDir: string;
159
+ readonly head: string;
160
+ readonly projection: Readonly<Record<string, JsonValue>>;
161
+ readonly projectionDigest: string;
162
+ }
163
+
164
+ interface PlaybookRepositoryReceipt {
165
+ readonly classification: PlaybookRepositoryReceiptClassification;
166
+ readonly baseline: PlaybookRepositoryObservation;
167
+ readonly after?: PlaybookRepositoryObservation;
168
+ readonly commitOid?: string;
169
+ }
170
+
171
+ type PlaybookRepositoryDisposition =
172
+ | 'unchanged'
173
+ | 'one-descendant-commit'
174
+ | 'deferred';
175
+
176
+ interface PlaybookEffectBoundary {
177
+ readonly sequence: number;
178
+ readonly boundaryId: string;
179
+ readonly attemptId: string;
180
+ readonly attemptNumber: number;
181
+ readonly playbookId: string;
182
+ readonly runtimeSessionId: string;
183
+ readonly turnId: number;
184
+ readonly callId: string;
185
+ readonly roleId: string;
186
+ readonly sourceStateId: string;
187
+ readonly sourceOutcomeSchema: JsonValue;
188
+ readonly dispositions: readonly PlaybookRepositoryDisposition[];
189
+ readonly canonicalWorktree: {
190
+ readonly worktree: string;
191
+ readonly gitDir: string;
192
+ };
193
+ readonly baseline: PlaybookRepositoryObservation;
194
+ readonly after?: PlaybookRepositoryObservation;
195
+ readonly physicalReceipt?: PlaybookRepositoryReceipt;
196
+ readonly finalText?: string;
197
+ readonly semanticCandidate?: JsonValue;
198
+ readonly initialSemanticCandidate?: JsonValue;
199
+ readonly correctionBudget: { readonly limit: 1; readonly spent: boolean };
200
+ readonly cohortId?: string;
201
+ readonly logicalOperationId?: string;
202
+ }
203
+
204
+ interface PlaybookEffectLogicalOperation {
205
+ readonly sequence: number;
206
+ readonly operationId: string;
207
+ readonly playbookId: string;
208
+ readonly runtimeSessionId: string;
209
+ readonly boundaryIds: readonly string[];
210
+ readonly originalBaseline: PlaybookRepositoryObservation;
211
+ readonly checkpoint?: PlaybookRepositoryObservation;
212
+ readonly pendingQuestion?: PlaybookPendingBossQuestion;
213
+ readonly playerContinuation?: JsonValue;
214
+ readonly checkpointRestorationEligible: boolean;
215
+ readonly logicalReceipt?: PlaybookRepositoryReceipt;
216
+ }
217
+
218
+ interface PlaybookEffectLedger {
219
+ readonly schemaVersion: 1;
220
+ readonly revision: number;
221
+ readonly boundaries: readonly PlaybookEffectBoundary[];
222
+ readonly logicalOperations: readonly PlaybookEffectLogicalOperation[];
223
+ }
224
+
225
+ type PlaybookEffectBoundaryStart = Omit<
226
+ PlaybookEffectBoundary,
227
+ | 'sequence'
228
+ | 'attemptId'
229
+ | 'attemptNumber'
230
+ | 'after'
231
+ | 'physicalReceipt'
232
+ | 'finalText'
233
+ | 'semanticCandidate'
234
+ | 'initialSemanticCandidate'
235
+ >;
236
+
237
+ type PlaybookEffectLogicalOperationStart = Omit<
238
+ PlaybookEffectLogicalOperation,
239
+ 'sequence'
240
+ >;
241
+
242
+ type PlaybookEffectLedgerCommand =
243
+ | {
244
+ readonly kind: 'start-boundaries';
245
+ readonly boundaries: readonly [
246
+ PlaybookEffectBoundaryStart,
247
+ ...PlaybookEffectBoundaryStart[],
248
+ ];
249
+ }
250
+ | {
251
+ readonly kind: 'replace-boundaries';
252
+ readonly replacements: readonly [
253
+ {
254
+ readonly expected: PlaybookEffectBoundary;
255
+ readonly next: PlaybookEffectBoundary;
256
+ },
257
+ ...{
258
+ readonly expected: PlaybookEffectBoundary;
259
+ readonly next: PlaybookEffectBoundary;
260
+ }[],
261
+ ];
262
+ }
263
+ | {
264
+ readonly kind: 'append-logical-operations';
265
+ readonly operations: readonly [
266
+ PlaybookEffectLogicalOperationStart,
267
+ ...PlaybookEffectLogicalOperationStart[],
268
+ ];
269
+ }
270
+ | {
271
+ readonly kind: 'replace-logical-operations';
272
+ readonly replacements: readonly [
273
+ {
274
+ readonly expected: PlaybookEffectLogicalOperation;
275
+ readonly next: PlaybookEffectLogicalOperation;
276
+ },
277
+ ...{
278
+ readonly expected: PlaybookEffectLogicalOperation;
279
+ readonly next: PlaybookEffectLogicalOperation;
280
+ }[],
281
+ ];
282
+ };
283
+
284
+ type PlaybookEffectLedgerCommandBatch = readonly [
285
+ PlaybookEffectLedgerCommand,
286
+ ...PlaybookEffectLedgerCommand[],
287
+ ];
288
+
289
+ interface PlaybookEffectLedgerCapability {
290
+ snapshot(): PlaybookEffectLedger;
291
+ writeAhead(
292
+ commands: PlaybookEffectLedgerCommandBatch,
293
+ ): Promise<PlaybookEffectLedger>;
124
294
  }
125
295
 
126
296
  interface PlaybookRuntimeSnapshot {
127
- schemaVersion: 3;
297
+ schemaVersion: 4;
128
298
  playbookId: string;
129
299
  machine: JsonValue;
130
300
  roleResumeTokens: { readonly [roleId: string]: string };
@@ -138,11 +308,27 @@ interface PlaybookRuntimeSnapshot {
138
308
  };
139
309
  state: PlaybookState;
140
310
  pendingBossQuestions: readonly PlaybookPendingBossQuestion[];
311
+ effectLedger: PlaybookEffectLedger;
312
+ /** Original runtime identity retained across schema-3 adoption lineage. */
313
+ retainedEffectSourceSessionId?: string;
314
+ /**
315
+ * Unsafe retained-adoption checkpoint. The marker remains durable until
316
+ * authoritative reconciliation proves its complete suffix replay-safe.
317
+ */
318
+ retainedEffectReconciliation?: {
319
+ readonly sourceSessionId: string;
320
+ readonly checkpoint: PlaybookEffectLedger;
321
+ };
322
+ failedEffectAttempt?: {
323
+ readonly boundaryPrefix: number;
324
+ readonly attemptId: string | null;
325
+ };
141
326
  suspendedCall?: PlaybookSuspendedCall;
142
327
  }
143
328
 
144
329
  type PlaybookRunResult =
145
330
  | { outcome: 'quiescent' | 'no-action'; state: PlaybookState }
331
+ | { outcome: 'unresolved-effect'; state: PlaybookState }
146
332
  | {
147
333
  outcome: 'failed' | 'aborted';
148
334
  state: PlaybookState;
@@ -151,6 +337,7 @@ type PlaybookRunResult =
151
337
  | {
152
338
  outcome: 'terminal';
153
339
  state: PlaybookState;
340
+ stateDescription?: string;
154
341
  output?: JsonValue;
155
342
  }
156
343
  | {
@@ -164,11 +351,80 @@ type PlaybookRuntimeFactory<Options = unknown> = (
164
351
  ) => PlaybookRuntime;
165
352
 
166
353
  export default function createPlaybookRuntime(
167
- options: PlaybookRuntimeOptions,
354
+ construction: XStatePlaybookRuntimeConstruction<
355
+ PlaybookRuntimeOptions,
356
+ HostCapabilities
357
+ >,
168
358
  ): PlaybookRuntime;
169
359
  ```
170
360
 
171
- The default export conforms to `PlaybookRuntimeFactory<PlaybookRuntimeOptions>`, the generic factory type the shared contract module exposes (§Output).
361
+ For a Captain-hosted linked workflow, the default export conforms to `PlaybookRuntimeFactory<XStatePlaybookRuntimeConstruction<PlaybookRuntimeOptions, HostCapabilities>>`, where `HostCapabilities` is the artifact's exact live schema-3 capability type and `PlaybookRuntimeFactory` is the generic factory type the shared contract module exposes (§Output).
362
+ The roleless session-Captain is the sole signature exception: its public options-only `PlaybookRuntimeFactory<PlaybookRuntimeOptions>` wrapper supplies its fixed empty-ledger, fail-closed schema-3 capabilities internally because no Captain host exists above it.
363
+
364
+ Artifact schema `3` shall require `outcomeAuthority` as an own plain-JSON data property and shall instantiate the shared factory with exactly `{ configuredOptions, hostCapabilities }`, where `configuredOptions` is the registry-validated plain-JSON workflow slice and `hostCapabilities` is a non-null live current-host object.
365
+ For schema `3`, the `Options` argument of the one-argument shared `PlaybookRuntimeFactory<Options>` shall be `XStatePlaybookRuntimeConstruction<ConfiguredOptions, HostCapabilities>`; the registry's public entry receives the two members separately and composes that one internal argument only at the artifact boundary.
366
+ For a Captain-hosted schema-3 artifact, `hostCapabilities` shall contain exactly `authority`, `repository`, and `effectLedger`: authority binds that artifact's id, schema, detached role and cohort declarations, current configured working directory, logical session and lease-owner identities, and canonical worktree; repository exposes that same canonical identity plus host-bound observation, acquisition, exclusive-call, and cohort operations, whose optional live completion mapper may return only detached `finalText`, `semanticCandidate`, `logicalOperationId`, and additional typed ledger commands for the same atomic completion; and the ledger exposes its synchronous detached `snapshot(): PlaybookEffectLedger` mirror plus `writeAhead(commands: PlaybookEffectLedgerCommandBatch): Promise<PlaybookEffectLedger>` against the current host's atomic writer.
367
+ Only `configuredOptions` may reach option snapshotting and FSM input.
368
+ The capability object, its callbacks, lease token, and live claim or store handles shall enter neither `PlaybookPorts`, machine input or context, runtime snapshots, launch or durable projections, retained generations, nor continuation identity; the detached ledger data and canonical identities returned by its ledger channel shall instead persist only through the versioned effect-ledger members defined below.
369
+
370
+ ```typescript
371
+ type XStateOutcomeFieldAuthority =
372
+ | 'presentation'
373
+ | 'semantic'
374
+ | 'effect'
375
+ | 'runtime';
376
+
377
+ type XStateRepositoryDisposition =
378
+ | 'unchanged'
379
+ | 'one-descendant-commit'
380
+ | 'deferred';
381
+
382
+ interface XStateGovernedOutcomeSpec {
383
+ readonly fields: Readonly<Record<string, XStateOutcomeFieldAuthority>>;
384
+ readonly repositoryDisposition: XStateRepositoryDisposition;
385
+ }
386
+
387
+ interface XStateOutcomeAuthoritySpec {
388
+ readonly governedPlayerStates: Readonly<
389
+ Record<
390
+ string,
391
+ Readonly<Record<string, XStateGovernedOutcomeSpec>>
392
+ >
393
+ >;
394
+ }
395
+
396
+ interface XStatePlaybookRuntimeConstruction<
397
+ ConfiguredOptions,
398
+ HostCapabilities extends object,
399
+ > {
400
+ readonly configuredOptions: ConfiguredOptions;
401
+ readonly hostCapabilities: HostCapabilities & {
402
+ readonly effectLedger: PlaybookEffectLedgerCapability;
403
+ };
404
+ }
405
+ ```
406
+
407
+ 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.
408
+ That executable module shall also export the centralized schema-3 semantic surface: `PlaybookSemanticFieldAuthority`, `PlaybookSemanticOutcomeSpec`, `PlaybookSemanticEvidenceInput`, `PlaybookReconciledSemanticOutput`, `PlaybookRetainedSemanticEvidence`, `PlaybookSemanticReconciliationReason`, `PlaybookSemanticReconciliation`, `PlaybookSemanticCandidateStructureError`, and `reconcilePlaybookSemanticEvidence`.
409
+ 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.
410
+ 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.
411
+ 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.
412
+ 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.
413
+ Within a logical operation, `checkpoint`, `pendingQuestion`, and `playerContinuation` shall be all present or all absent; the pending question shall preserve its exact nonempty authored identity and nonblank content, and `checkpointRestorationEligible: true` shall require that complete bound group.
414
+ Each logical operation shall reciprocally name every and only boundary carrying its operation id, share those boundaries' playbook and runtime-session identity, and use its first boundary's exact baseline as `originalBaseline`; every linked boundary shall use that baseline's canonical worktree and, after the first, start from the preceding boundary's complete after checkpoint, while a logical receipt shall require every linked physical receipt.
415
+ `isPlaybookEffectLedgerMonotonicExtension(checkpoint, current)` shall accept exact equality and only a ledger reachable through the typed append-or-replace transitions without boundary or operation deletion, identity or original-baseline reassignment, correction-budget replenishment, completed-receipt or evidence loss, or removal or reordering of an earlier boundary-id prefix. A spent correction may replace `semanticCandidate` exactly once only while adding `initialSemanticCandidate` equal to the prior candidate; that initial candidate then remains immutable. A replacement may append boundary ids and replace or clear the complete current checkpoint, pending-question, and player-continuation group together with its eligibility, while an existing logical receipt remains immutable ([DR-040](../specs/decisions/040-outcome-authority-effect-reconciliation.md)).
416
+ Every accepted non-idempotent command batch shall increment revision once; an exact start or append replay under the same boundary or operation identities and payload shall return the same acknowledged ledger, while conflicting identity reuse shall reject without mutation.
417
+ The host shall assign each started boundary's sequence, current uncertain-attempt UUID, and positive attempt number, and shall assign each appended logical operation's sequence.
418
+ Every command batch and every command's entry list shall be nonempty; the host shall apply its commands in order as one ledger transition, perform final cross-reference validation after the complete batch, and acknowledge only one atomic persistence and revision increment.
419
+ Every replace command's exact `{ expected, next }` pair shall compare-and-swap one present boundary or operation, preserve its identity and immutable fields, reject a stale expected value, and move optional evidence, the one-way correction budget, or the current logical binding and eligibility only as permitted by DR-040.
420
+ After a governed operation settles, the host shall retain any exact proposed completion batch only in live memory until it is acknowledged and the cooperative claim retires; an indeterminate same-process write shall retry or recognize that batch under the still-owned claim, while recovery after process death shall reconstruct only evidence provable from the durable baseline and current repository observation before source restoration.
421
+
422
+ `governedPlayerStates` shall name every delegated-player state declared by `roleStates`, or shall be exactly empty for an artifact with no delegated-player state; it shall name no other state.
423
+ Each state shall name exactly the outcomes in that state's `invoke.input.result`, and each outcome shall contain exactly `fields` and `repositoryDisposition`.
424
+ The outcome key owns the semantic discriminator, so `guard` shall not appear in `fields`; the `fields` keys shall equal every additional payload field named by that outcome's result description.
425
+ Each field shall have exactly one authority from `presentation`, `semantic`, `effect`, or `runtime`; every linker-declared verbatim payload field and `question` shall be `presentation`, `latestCommit` shall be `effect`, and the payload fields `irNumber` and `irTask` shall be `semantic`, while outcome keys such as `moreTasks` and `finalTask` remain semantic discriminators.
426
+ Each repository disposition shall be exactly `unchanged`, `one-descendant-commit`, or `deferred`; an effect-owned field is valid only on `one-descendant-commit`, and `deferred` is valid only on `needsBossReply` with presentation-owned `question` and another outcome in that state declaring `one-descendant-commit`.
427
+ The shared factory shall reject every legacy artifact schema and reject schema-3 missing, extra, unknown, wrongly owned, or inconsistent metadata before the affected player call.
172
428
 
173
429
  `init` receives the host-owned playbook session identity and ports, constructs the XState actor with FSM `input` derived from `options`, and starts the actor.
174
430
  The runtime owns the actor for its lifetime; `handleBossInput` runs one turn, and `dispose` stops the actor and drains pending port emissions.
@@ -184,12 +440,14 @@ differ from both its `rootSessionId` and `parentSessionId`.
184
440
  Run outcomes are exact: `no-action` means no FSM event was sent;
185
441
  `quiescent` means a non-failure parked/idle state; `failed` means the FSM is in
186
442
  a recoverable failure state; `terminal` means top-level final with optional
187
- JSON output; `aborted` means the turn signal ended work; and `suspended` means
443
+ JSON output and the exact authored `stateDescription` of the reached final
444
+ state when one is declared; `aborted` means the turn signal ended work; and `suspended` means
188
445
  exactly one `pendingCall` is active.
446
+ Only the terminal variant may carry `stateDescription`; the runtime shall omit it when the final state declares none and shall never substitute a state id or derive it from opaque output ([DR-037](../specs/decisions/037-terminal-result-meaning.md)).
189
447
  Control-plane exceptions reject the runtime method rather than masquerade as a
190
448
  recoverable workflow `failed` result.
191
449
 
192
- `PlaybookRuntimeOptions` is host-agnostic and carries only _per-run_ workflow knobs, strategy overrides the linker exposes, and where the compiled playbook's policy needs a host seam host-supplied port-shaped callbacks the linker exposes as option members whose types the artifact itself declares, so the six-member `PlaybookPorts` contract and the shared contract module stay free of host types.
450
+ Configured options shall be plain JSON and live host seams shall enter only through `hostCapabilities` in the disjoint schema-3 construction input above.
193
451
  The link compiler emits a typed options interface per playbook based on the FSM's `CodingInput` (or equivalent).
194
452
  The CLI's absence of `--link-option` values does not mean that
195
453
  `PlaybookRuntimeOptions` is empty. CLI link options are compile-time inputs;
@@ -285,6 +543,8 @@ type PlaybookCallStart =
285
543
  | { state: 'suspended'; childSessionId: string };
286
544
  ```
287
545
 
546
+ A runtime's `{ outcome: 'unresolved-effect', state }` abandonment result is not a `PlaybookCallResult`: a host shall not translate it into child output or error, resume a parent FSM with it, or treat it as authored completion.
547
+
288
548
  `PlayerResult` mirrors the status, resume token, final text, and error fields of cligent's `PlayerRunResult` ([TMUX-033](https://github.com/sublang-ai/cligent/blob/main/specs/user/tmux-play.md#tmux-033)).
289
549
  The runtime treats `status !== 'ok'` as a player failure and routes it through the FSM's error path (§Abort).
290
550
  An `ok` player result whose `finalText` is missing, empty, or whitespace-only
@@ -386,7 +646,7 @@ The runtime never speaks to LLMs directly and never touches host types beyond `P
386
646
  ## Playbook trace
387
647
 
388
648
  Every linked runtime shall emit a boundary-complete, ordered trace through `emitTelemetry` topic `playbook.trace`.
389
- Each payload shall carry `schemaVersion: 3`, the immutable session identity and
649
+ Each payload shall carry `schemaVersion: 4`, the immutable session identity and
390
650
  causality, a contiguous one-based `sequence`, a Unix-millisecond `timestamp`, a
391
651
  trace `type`, event `payload`, and the runtime-local `turnId` / paired `callId`
392
652
  where applicable.
@@ -406,12 +666,13 @@ type PlaybookTraceType =
406
666
  | 'apply.started'
407
667
  | 'apply.finished'
408
668
  | 'fsm.transition'
669
+ | 'outcome.accepted'
409
670
  | 'status.emitted'
410
671
  | 'boss.input.settled'
411
672
  | 'session.disposed';
412
673
 
413
674
  interface PlaybookTraceEvent {
414
- schemaVersion: 3;
675
+ schemaVersion: 4;
415
676
  sessionId: string;
416
677
  playbookId: string;
417
678
  rootSessionId: string;
@@ -437,8 +698,9 @@ The trace types are `session.started`, `boss.input.received`,
437
698
  `player.call.finished`, `captain.call.started`, `captain.call.finished`,
438
699
  `playbook.call.started`,
439
700
  `playbook.call.finished`, `apply.started`, `apply.finished`,
440
- `fsm.transition`, `status.emitted`,
701
+ `fsm.transition`, `outcome.accepted`, `status.emitted`,
441
702
  `boss.input.settled`, and `session.disposed`.
703
+ No trace event whose `schemaVersion` is below `4` is authority-bearing accepted-outcome evidence.
442
704
  Call pairs carry exact prompts and replies, normalized failures, actor and state
443
705
  identity, and their boundary-specific options.
444
706
  `apply.started` and `apply.finished` are the paired apply boundary of a
@@ -453,8 +715,13 @@ Direct-Captain start and finish payloads shall carry `allowedTools` exactly when
453
715
  the originating `CaptainCallOptions` selects it and shall omit the member when
454
716
  the call preserves the host Captain's configured tools.
455
717
  `session.started` and `session.disposed` carry their descriptor as top-level
456
- `state` and its singular `stateId` when present. Every judge start and finish
457
- carries the working snapshot's singular `stateId` when one exists;
718
+ `state` and its singular `stateId` when present. An adopted runtime begins its
719
+ fresh target trace with `session.started` at sequence `1`; that event also
720
+ carries an exact nested `adoption` object with `sourceSessionId` and
721
+ `sourceGenerationId`, plus the source/target call and child-session identities
722
+ when a suspended edge was rebased. The suspended form carries the fresh target
723
+ call id as top-level `callId` and carries no source `turnId`. Every judge start
724
+ and finish carries the working snapshot's singular `stateId` when one exists;
458
725
  classification uses the current descriptor and adjudication uses the invoking
459
726
  actor input. The default Captain always has such a singular id, while a
460
727
  parallel snapshot may omit it. Every judge finish also carries
@@ -494,6 +761,15 @@ If a started-boundary sink records the event and then rejects, the runtime
494
761
  shall make one best-effort normalized error-finish attempt with the same call
495
762
  id and then reject the original start error. It shall not retry either event or
496
763
  let a failure of that finish attempt replace the start error.
764
+ A start-sink rejection causally identical to the applicable signal reason is
765
+ the cancellation itself, not a control error: no host call begins, the
766
+ best-effort paired finish carries the boundary's canonical aborted evidence —
767
+ `status: 'aborted'` for a host call, or the rejected-before-effect disposition
768
+ and reason for apply — and nothing is latched. An ordinary run boundary settles
769
+ as §Abort prescribes. At the apply boundary the same event remains
770
+ pre-acceptance: `apply` rejects with that exact reason, records no receipt, and
771
+ leaves the key reusable
772
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
497
773
  When a call boundary carries `callId`, that id shall be unique within the
498
774
  runtime session. A stable FSM `stateId` is identity metadata in the payload,
499
775
  not a call id and shall not be reused as one across repeated invocations.
@@ -506,6 +782,7 @@ result: its outcome must be one of the `PlaybookRunResult` discriminants (never
506
782
  an invented `error` outcome), and it shall include `state`, singular
507
783
  `stateId`, `pendingCall`, `output`, and normalized `error` whenever the matching
508
784
  result arm carries them.
785
+ The `unresolved-effect` arm shall carry only `state`; bounded repository-effect evidence remains host-owned and shall enter neither that result nor its trace projection.
509
786
  One runtime-owned concurrency-one emission queue shall serialize every trace,
510
787
  human status, and state telemetry call. Sequence allocation and enqueueing
511
788
  shall occur atomically, and every public method shall drain that queue before
@@ -658,10 +935,14 @@ nonconformant.
658
935
  The FSM's `events` union enumerates every Boss-originated event.
659
936
  The runtime receives Boss input as a free-form string
660
937
  (`handleBossInput.text`).
661
- Where the current ready or reconstructed terminal machine accepts exactly one
938
+ Where the current ready, recoverable-failure (`failed`), or reconstructed
939
+ terminal machine accepts exactly one
662
940
  ordinary textual entry event and no Boss question is pending, the runtime
663
941
  shall send that event deterministically and attach the exact original text to
664
- its declared textual payload field without invoking `callJudge`.
942
+ its declared textual payload field without invoking `callJudge`: each of the
943
+ three is an entry awaiting a fresh intent, so delivered text has exactly one
944
+ meaning there and a judge call could only spend budget or settle the restart
945
+ as no action.
665
946
  The default Captain — the controller playbook of
666
947
  [gears2fsm "Setup"](gears2fsm.md#setup) — is deterministic at every parked
667
948
  entry: the runtime maps each Boss turn from the exact text and the host's
@@ -771,7 +1052,8 @@ clause (or equivalent typed output metadata). Backticked prose before that
771
1052
  clause can name statuses, guards, or concepts such as `ok`, `aborted`, and
772
1053
  `error`; those names are not output properties and shall never become required
773
1054
  judge fields.
774
- For a delegated-player field annotated exactly `` `<field>: <verbatim final text>` ``, the judge shall select the guard but the runtime shall replace any judge-supplied value with the player's canonical non-empty final text before returning the actor output.
1055
+ For a nongoverned delegated-player field annotated exactly `` `<field>: <verbatim final text>` ``, the judge shall select the guard but the runtime shall replace any judge-supplied value with the player's canonical non-empty final text before returning the actor output.
1056
+ For an artifact-schema-3 governed field, that annotation instead declares presentation authority and any judge-supplied value is a structural error under the authority rule below.
775
1057
  The linker shall derive the complete `verbatimPayloadFields` set from those annotations across the FSM result maps.
776
1058
  A field name that is annotated in one result map and unannotated in another is a link error because the shared adjudication strategy cannot give one property both ownership policies.
777
1059
  For a direct Captain result, `question` and `response` are human-presentation
@@ -784,13 +1066,87 @@ After validating that selection, the runtime shall inject the exact non-empty
784
1066
  It shall reject a judge reply that supplies either presentation field as an
785
1067
  undeclared extra key, so hidden adjudication cannot replace, paraphrase, or
786
1068
  decorate prose Boss already saw.
787
- Delegated-player adjudication retains extraction of every required field from
788
- the judge reply, including a player-authored Boss question.
1069
+ For an artifact-schema-3 governed delegated-player call, the shared engine
1070
+ shall instead use one semantic reconciler for both the default linked runtime
1071
+ and any bespoke linked runtime that adopts schema `3`.
1072
+ That reconciler shall retain the validated player's exact non-empty
1073
+ `finalText` as opaque presentation evidence, let the hidden adjudicator read
1074
+ it only as semantic evidence, and require the adjudicator's detached
1075
+ plain-JSON candidate to contain exactly `guard` plus every and only
1076
+ semantic-owned payload field declared for that guard.
1077
+ The candidate shall therefore contain no presentation-, effect-, or
1078
+ runtime-owned payload field; `guard` shall name exactly one outcome declared
1079
+ by both the live result map and `outcomeAuthority`; and every semantic-owned
1080
+ field shall satisfy the result map's required-field type before any actor
1081
+ output is delivered.
1082
+ The reconciler shall construct the complete actor output rather than accept a
1083
+ cross-authority object from the judge: every presentation-owned payload field
1084
+ shall receive the canonical `finalText.trim()` value, effect-owned
1085
+ `latestCommit` shall receive only the qualifying receipt's exact commit OID,
1086
+ and no authority may supply, overwrite, or contradict another authority's
1087
+ field.
1088
+ It shall reject an absent required field, an undeclared or extra field, a
1089
+ field supplied by the wrong authority, an invalid value, or any mutually
1090
+ inconsistent candidate before FSM delivery.
1091
+
1092
+ For a non-deferred candidate, reconciliation shall require a complete durable
1093
+ physical receipt, or the complete cumulative logical receipt of a deferred
1094
+ operation, whose classification is exactly the outcome's declared
1095
+ `unchanged` or `one-descendant-commit` disposition; the latter shall carry
1096
+ exactly the after-HEAD OID used for `latestCommit`.
1097
+ A `deferred` candidate shall be admissible only for its already-validated
1098
+ effect-authorized `needsBossReply` outcome and only from a complete after
1099
+ observation whose HEAD equals the logical operation's original baseline HEAD
1100
+ and whose classification is exactly `unchanged` or `worktree-only-change`.
1101
+ It shall become deliverable only after the current host durably acknowledges
1102
+ the exact checkpoint, question, continuation, and logical-operation binding
1103
+ defined above; a missing checkpoint, changed HEAD, multiple or rewritten
1104
+ history, detected concurrent or foreign change, or ambiguous observation
1105
+ shall leave it unresolved.
1106
+
1107
+ The first structurally invalid schema-3 semantic reply shall make at most one
1108
+ corrective hidden adjudication eligible over the identical retained
1109
+ presentation evidence and declared outcome schema, with the validation error
1110
+ restated.
1111
+ Before starting that corrective judge, the runtime shall compare-and-swap the
1112
+ boundary's `correctionBudget` from `{ limit: 1, spent: false }` to
1113
+ `{ limit: 1, spent: true }` while atomically retaining its receipt, opaque
1114
+ presentation, and first recoverable invalid `semanticCandidate` through
1115
+ `effectLedger.writeAhead`, await the durable acknowledgement, replace its
1116
+ mirror with that acknowledged ledger, and check the applicable abort signal
1117
+ again.
1118
+ A failed or indeterminate spend, an acknowledgement that does not contain the
1119
+ exact one-way update, a previously spent budget, or an abort before the call
1120
+ begins shall start no corrective judge; the spent value shall remain spent
1121
+ across export, restore, adoption, and process restart.
1122
+ A second structurally invalid reply shall receive no further correction.
1123
+ A player abort, error, non-`ok` result, or missing non-empty `finalText` shall
1124
+ start no adjudication, while an initial or corrective judge transport failure
1125
+ or invalid host result shall start no corrective or third judge respectively.
1126
+
1127
+ Only a complete, authority-consistent semantic-and-effect envelope shall be
1128
+ delivered once to the FSM, and only after its evidence and applicable
1129
+ correction-budget or deferred-operation updates are durably acknowledged.
1130
+ The completion path shall retain the opaque `finalText`, the latest recoverable
1131
+ detached plain-JSON semantic candidate even when it is structurally invalid,
1132
+ and the receipt and correction budget without parsing the presentation for a
1133
+ repository fact; where a correction replaces that candidate, immutable
1134
+ `initialSemanticCandidate` shall preserve the candidate that consumed the
1135
+ budget, while a malformed reply from which no JSON value can be recovered may
1136
+ omit both candidates.
1137
+ An effect-possible envelope whose presentation, semantic, effect, or deferred
1138
+ checkpoint evidence is absent, invalid, incomplete, or inconsistent shall
1139
+ deliver no actor output and shall remain parked for later reconciliation;
1140
+ once its matching source state is restored, reconstruction from a durable
1141
+ complete envelope may perform that same reconciliation once without another
1142
+ player or judge call.
789
1143
  The adjudicator shall use the same document-order tolerant JSON recovery as
790
1144
  the Boss classifier. Unlike invalid classification, a reply from which no
791
1145
  object can be recovered, an undeclared guard, or a missing required field is a
792
- control-plane error and shall throw after the invocation reaches its FSM error
793
- path and ordered emissions drain.
1146
+ control-plane error for nongoverned delegated-player and direct-Captain adjudication and
1147
+ shall throw after the invocation reaches its FSM error path and ordered
1148
+ emissions drain; schema-3 governed adjudication follows the bounded
1149
+ reconciliation contract above instead.
794
1150
 
795
1151
  Two default adjudication strategies, in selection order:
796
1152
 
@@ -798,9 +1154,11 @@ Two default adjudication strategies, in selection order:
798
1154
  names the source item's actor (and delegated player where applicable),
799
1155
  includes the actor's verbatim output,
800
1156
  lists the `result` keys with their descriptions, and demands a JSON
801
- `{ guard, …structuralPayloadFields }` answer keyed to exactly one of the
802
- declared guards, excluding the runtime-owned direct-Captain `question` and
803
- `response` fields above. The prompt shall identify hidden control work,
1157
+ answer keyed to exactly one of the declared guards: a nongoverned player uses
1158
+ `{ guard, …structuralPayloadFields }`, while a governed schema-3 player uses
1159
+ `{ guard, …semanticOwnedPayloadFields }` and explicitly forbids every other
1160
+ payload field. Both forms exclude the runtime-injected direct-Captain
1161
+ `question` and `response` fields above. The prompt shall identify hidden control work,
804
1162
  prohibit tool use, file inspection, and external evidence, direct the judge
805
1163
  to decide only from the supplied actor output and declared outcomes, and
806
1164
  require exactly one JSON object with no prose. The judge prompt shall not
@@ -873,7 +1231,7 @@ the `{ visibility: 'visible', resume: false }` workflow-call selection
873
1231
  (§PlaybookPorts contract, §Captain prompt composition) stay the
874
1232
  visible-presentation shape for non-controller playbooks.
875
1233
 
876
- The adjudicator shall fail loudly on:
1234
+ The nongoverned player adjudicator and every direct-Captain adjudicator shall fail loudly on:
877
1235
 
878
1236
  - A guard the state does not declare,
879
1237
  - A missing payload field the state's `result` description requires,
@@ -885,7 +1243,7 @@ identify an undeclared guard, and an incomplete selection shall identify the
885
1243
  missing required field. A generic “no declared guard selected” error for all
886
1244
  three cases is nonconformant.
887
1245
 
888
- Adjudicator failures are control-plane errors.
1246
+ Those adjudicator failures are control-plane errors.
889
1247
  The runtime shall propagate them by throwing out of `handleBossInput` after attempting cleanup.
890
1248
  The host adapter surfaces the throw on its control-plane channel (cligent surfaces such throws as `runtime_error` per [TMUX-025](https://github.com/sublang-ai/cligent/blob/main/specs/user/tmux-play.md#tmux-025)).
891
1249
  The host's player-result channels (`player_finished` and equivalents) are reserved for failures the player itself produced; the host emits them when `callPlayer` resolves with `status !== 'ok'`.
@@ -895,11 +1253,12 @@ failure path specified above. Captain transport, result-shape, trace-sink, and
895
1253
  adjudication failures remain control-plane errors unless the transport failure
896
1254
  is causally identical to the active abort signal.
897
1255
  Because XState still needs the invoked promise to settle, the linked runtime
898
- shall latch an adjudicator, actor-output JSON-validation, or nested-boundary
1256
+ shall latch a nongoverned delegated-player or direct-Captain adjudicator failure, actor-output JSON-validation, or nested-boundary
899
1257
  control error outside machine context, allow the invocation's `onError` path to
900
1258
  reach quiescence, drain all emissions, and then reject the public runtime
901
1259
  method with that original error. It shall not return such a failure as a
902
1260
  recoverable `{ outcome: 'failed' }` workflow result.
1261
+ An artifact-schema-3 governed-player adjudicator shall instead use the bounded structural correction, authority reconciliation, and unresolved parking contract above.
903
1262
  The first latched non-abort control error takes precedence over a coincident
904
1263
  boundary-signal abort. Read and clear the latch only in the public boundary's
905
1264
  `finally` cleanup after XState and emissions have settled, so it cannot leak
@@ -937,10 +1296,34 @@ The provided actor shall:
937
1296
  `{ guard: <first declared guard>, exitStatus: 0 }`; any nonzero status
938
1297
  resolves the second declared guard with that status. Guard selection is
939
1298
  mechanical; the runtime shall not route script output through the judge.
940
- - Reject only when the command cannot be spawned at all, routing through the
941
- state's ordinary `onError` path.
942
- - Honor the active turn's abort signal by terminating the child process and
943
- rejecting per §Abort.
1299
+ - Reject when the command cannot be spawned at all, routing through the
1300
+ state's ordinary `onError` path. Beyond spawn failure, the invocation
1301
+ rejects only per the abort bullet below or when one of its own script
1302
+ emissions rejects; a completed command's exit status itself never rejects.
1303
+ - Honor the active turn's abort signal per §Abort: the actor shall reject
1304
+ without spawning when the combined signal is already aborted; shall run the
1305
+ shell detached as its own process-group leader; and on abort — whenever it
1306
+ lands before the invocation settles, including only after the shell's own
1307
+ exit — shall deliver
1308
+ `SIGTERM` to the entire group, escalate to `SIGKILL` after a bounded grace,
1309
+ and settle only after the shell process itself has exited and the group has
1310
+ stopped being signalable, confirmed by an `ESRCH` liveness probe, rejecting
1311
+ with the signal's reason. The same
1312
+ bounded grace caps the post-`SIGKILL` wait for kernel teardown, so an
1313
+ unreaped member outside the runtime's control cannot stall settlement. If
1314
+ the group remains signalable through that bound, or confirmation fails
1315
+ without `ESRCH`, the boundary rejects with a distinct teardown control error
1316
+ rather than reporting a clean abort over unconfirmed cleanup. The kill is
1317
+ always posted before the actor settles. Abort ownership — the
1318
+ listener and its escalation — spans the whole invocation, not the
1319
+ spawn-to-exit window
1320
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)). An abort
1321
+ observed only after the shell's exit shall additionally reject before guard
1322
+ resolution and before starting any script emission not already in flight; an
1323
+ emission already started when the abort lands completes through the
1324
+ ordinary serialized channel and the rejection follows it. A
1325
+ descendant that leaves the process group is beyond the runtime's kill
1326
+ scope.
944
1327
  - Emit, after the child settles and before the invocation resolves, one status
945
1328
  line `Executed script for <stateId> (exit <status>).` and one telemetry
946
1329
  event under topic `playbook.script` with payload
@@ -965,14 +1348,18 @@ XState `.provide(...)` receives the exact declared actor input rather than a
965
1348
  structurally similar local type.
966
1349
  Construct one bridge per runtime and wire every integration hook: allocate ids
967
1350
  with `nextCallId`; return the currently active public-boundary signal from
968
- `getBoundarySignal`; bind `resumePlaybookCall.signal` before settling the
969
- deferred actor through `bindResumeSignal`; enqueue the exact start/finish trace
970
- through `emitStarted` / `emitFinished`; drain the global emission queue through
971
- `drain`; latch the original control error through `onControlPlaneError`; and
972
- retain any cleanup/observer failure through `onBackgroundError` for the next
973
- public boundary or disposal rejection. The runtime shall not leave these
974
- optional API hooks unwired merely because their TypeScript properties are
975
- optional for simpler bridge consumers.
1351
+ `getBoundarySignal`; capture an immutable cancellation classifier for the
1352
+ invocation's signal identities; compose `resumePlaybookCall.signal` into that
1353
+ classifier through `bindResumeSignal`; pass the applicable classifier through
1354
+ `emitStarted`, `emitFinished`, and `drain`; bind it to the root transition
1355
+ caused by child settlement through `bindActorSettlement`; and pass it through
1356
+ `onControlPlaneError` and `onBackgroundError`. Each receiving latch shall drop
1357
+ only a failure the supplied classifier identifies as exact cancellation and
1358
+ shall retain every distinct cleanup or observer failure for the owning public
1359
+ boundary, the next drain, or disposal rejection as applicable. A stored
1360
+ distinct failure shall never be reclassified against a later boundary. The
1361
+ runtime shall not leave these optional API hooks unwired merely because their
1362
+ TypeScript properties are optional for simpler bridge consumers.
976
1363
  On invocation the bridge allocates a runtime-local call id, traces the start,
977
1364
  and calls `PlaybookPorts.callPlaybook` with the composed target/text and the
978
1365
  bridge signal combined from the XState invocation lifetime, the active public
@@ -1057,12 +1444,15 @@ registry; linker-time metadata is not authorization to call a target.
1057
1444
 
1058
1445
  Disposal shall settle an outstanding call as aborted and drain its finish
1059
1446
  trace before `session.disposed`.
1060
- If registered child abort cleanup rejects, the bridge shall emit the paired
1061
- finish with an error result and reject `abortPending` or disposal with that
1062
- original cleanup error; it shall not swallow the failure merely because the
1063
- promise actor also observes a `NestedPlaybookCallError`. Parent disposal shall
1064
- still drain, emit its one `session.disposed` boundary, and clear the bound
1065
- session before rejecting with that preserved cleanup error.
1447
+ If registered child abort cleanup rejects with a failure distinct from every
1448
+ applicable abort reason, the bridge shall emit the paired finish with an error
1449
+ result and reject `abortPending` or disposal with that original cleanup error,
1450
+ or with an aggregate containing every distinct failure when more than one
1451
+ remains;
1452
+ an exact abort-reason rejection is cancellation evidence and shall not be
1453
+ retained as a control failure. Parent disposal shall still drain, emit its one
1454
+ `session.disposed` boundary, and clear the bound session before rejecting with
1455
+ any preserved distinct cleanup error.
1066
1456
  Child output and errors must be JSON-safe; a non-JSON-safe result is a
1067
1457
  control-plane error.
1068
1458
 
@@ -1140,6 +1530,12 @@ The `PlaybookRuntime` shall:
1140
1530
  transition-trace or telemetry sink failure is part of `init`: initialization
1141
1531
  shall reject, stop the actor, and perform the failed-start cleanup below
1142
1532
  rather than swallowing it as a later background error.
1533
+ A root-actor error observed during startup — an initial entry action or a
1534
+ synchronously failing initial invocation — is equally part of `init` and
1535
+ `restore`: the boundary shall reject with that original error after the
1536
+ failed-start cleanup, and shall never resolve leaving the errored actor as
1537
+ later background state
1538
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
1143
1539
  Where the FSM input declares `selfPlaybookId`, seed it from the immutable
1144
1540
  `session.playbookId`; do not expose a caller option or reuse a working leaf's
1145
1541
  `stateId` as the self-call identity.
@@ -1193,7 +1589,14 @@ The `PlaybookRuntime` shall:
1193
1589
  transition/status/telemetry queue before returning, just as
1194
1590
  `handleBossInput` does. A resume shall not allocate a new Boss-input
1195
1591
  `turnId`; retain the original call-start turn id for its matching finish and
1196
- for the parent continuation caused by that return. Every success and
1592
+ for the parent continuation caused by that return.
1593
+ A resume whose signal is already aborted after identity and result
1594
+ validation shall deliver nothing: bind no resume signal, settle no deferred,
1595
+ emit no call finish, and preserve the pending call — the boundary settles
1596
+ `{ outcome: 'aborted' }` with the signal's reason while the suspended state
1597
+ and pending identity survive, so a later resume with the same call id and a
1598
+ fresh signal still delivers
1599
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)). Every success and
1197
1600
  exceptional path shall drain ordered emissions, select the first latched
1198
1601
  non-abort control error before considering abort, and clear its boundary
1199
1602
  latches in `finally`, so a failed resume cannot leak an emission error into a
@@ -1219,18 +1622,22 @@ The `PlaybookRuntime` shall:
1219
1622
  The actor's `lastError` field shall be surfaced via `emitStatus` when the machine enters its `failed` state.
1220
1623
  Presence of linker-emitted `roleStates` selects the canonical factory-backed status profile.
1221
1624
  That profile shall emit the selected Boss event type
1222
- before sending that event, exactly `→ <guard>` (with no payload-count or tally
1223
- rider) when a settling actor output carries a guard, and
1625
+ before sending that event; exactly `→ <acceptedOutcome>` (with no payload-count or tally
1626
+ rider) only from a confirmed accepted-outcome marker; and
1224
1627
  `⤷ <Role>: <label>` only when the entered state appears in the linked module's `roleStates` metadata.
1225
1628
  It shall emit no raw state-id fallback for any other state.
1226
1629
  `roleStates` shall be a complete map of the FSM states
1227
- that invoke the typed `player` actor; each schema-2 value carries the exact
1630
+ that invoke the typed `player` actor; each value carries the exact
1228
1631
  local role from that state's source-derived `meta.playbook.role` and the state's exact FSM description as `{ role, label }`.
1229
1632
  The factory shall reject an
1230
1633
  incomplete entry, a non-player state, or a role or label that differs from the FSM metadata.
1231
1634
  Artifact schema `1` and a missing compatibility declaration
1232
1635
  shall reject before interpretation because their legacy `player` values may
1233
1636
  encode bindings or aliases rather than canonical local roles.
1637
+ For artifact schema `3`, an accepted-outcome marker is a root-machine XState action with exact type `playbook.acceptedOutcome` and exact plain-data params `{ source, target, acceptedOutcome }` naming a declared governed outcome.
1638
+ The runtime shall observe that action only through the public root `@xstate.action` inspection event, retain it privately until the corresponding next public root `@xstate.snapshot` confirms `source` active in the prior snapshot and `target` active in the new snapshot, then emit one trace-schema-4 `outcome.accepted` event with those exact params before its canonical status and before public settlement; markers confirmed together shall retain their XState execution order.
1639
+ A valid unmarked transition, including an unexecuted guarded arm or rejected-guard fallback, shall settle normally with neither accepted-outcome evidence nor claimed-outcome status.
1640
+ An executed marker that is malformed, undeclared, or unconfirmed by those adjacent snapshots, or a batch that instruments one governed source more than once regardless of target or outcome, shall clear the entire pending marker batch and fail the current public boundary after retaining the ordinary transitioned state but before settlement, accepted-outcome evidence, or claimed-outcome status.
1234
1641
  For the default Captain runtime, an initial `ready` state and a terminal `done`
1235
1642
  state shall not emit human status. The terminal response is already visible
1236
1643
  Captain prose; a synthetic “entered done” message would present it twice.
@@ -1246,9 +1653,14 @@ transition first.
1246
1653
 
1247
1654
  If a `*.call.started` trace records and then its sink rejects, no host call may
1248
1655
  begin. The runtime shall still enqueue exactly one synthetic paired
1249
- `*.call.finished` trace with `status: 'error'`, preserving the original call
1656
+ `*.call.finished` trace with `status: 'error'`, or `status: 'aborted'` when
1657
+ the sink rejection is causally identical to the applicable signal reason, in
1658
+ which case nothing is latched and the turn follows abort settlement
1659
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)) — preserving
1660
+ the original call
1250
1661
  id, turn id, actor visibility, state/source identity, and prompt or request
1251
- metadata from the start boundary. It shall then follow the same latched
1662
+ metadata from the start boundary. A distinct rejection shall then follow the
1663
+ same latched
1252
1664
  control-error, FSM settlement, and ordered-drain path as any other call-start
1253
1665
  failure; the synthetic finish must not replace the original sink error.
1254
1666
 
@@ -1268,18 +1680,22 @@ quiescent state with actor status `active`.
1268
1680
  At a safe capture point it shall return a JSON-safe
1269
1681
  `PlaybookRuntimeSnapshot` carrying:
1270
1682
 
1271
- - `schemaVersion`: literal `3`.
1683
+ - `schemaVersion`: literal `4`.
1272
1684
  - `playbookId`: the bound session's playbook id.
1273
1685
  - `machine`: the root actor's `getPersistedSnapshot()` result, passed
1274
1686
  through the shared JSON detachment with any raw `Error` context value
1275
1687
  (for example FSM `lastError`) normalized to `{ name, message, stack? }`
1276
1688
  first. The value is opaque to hosts.
1689
+ - `effectLedger`: the detached immutable schema-version-1 mirror most recently
1690
+ acknowledged by the current host's atomic ledger channel; a linked workflow
1691
+ runtime carries the complete current-host mirror, while the internal compiled
1692
+ Captain runtime carries the exact empty ledger.
1277
1693
  - `roleResumeTokens`: the local-role resume-token projection as a plain object
1278
1694
  (§PlaybookPorts contract).
1279
1695
  - `sequences`: the live `trace`, `turn`, `judgeCall`, `playerCall`, and
1280
1696
  `playbookCall` counters, plus `captainCall` when the runtime supports direct
1281
1697
  Captain calls.
1282
- A direct-Captain-capable runtime shall persist it in every schema-version-3 export.
1698
+ A direct-Captain-capable runtime shall persist it in every schema-version-4 export.
1283
1699
  - `state`: the current normalized state descriptor.
1284
1700
  - `pendingBossQuestions`: the pending Boss question(s) from FSM context as
1285
1701
  a list of `{ questionId, asker, question, sourceItem? }`, where `asker` is
@@ -1301,14 +1717,18 @@ unsafe and returns `undefined`.
1301
1717
  `restore(session, snapshot)` is an alternative to `init` under the same
1302
1718
  lifecycle guards (§Session lifecycle): it shall reject when already
1303
1719
  initialized, disposing, or disposed, and shall validate
1304
- schema version `3` and that `snapshot.playbookId` equals `session.playbookId` before touching state.
1305
- Runtime snapshot schemas `1` and `2` shall reject before state binding because their token and pending-question fields conflate local roles, concrete players, and Captain identity.
1720
+ schema version `4`, the complete effect-ledger mirror, and that `snapshot.playbookId` equals `session.playbookId` before touching state.
1721
+ Runtime snapshot schemas `1` and `2` shall reject before state binding because their token and pending-question fields conflate local roles, concrete players, and Captain identity; schema `3` shall reject because it cannot prove an effect ledger.
1306
1722
  The host supplies the same immutable `PlaybookSession` identity the
1307
1723
  snapshot was exported under and recreates the runtime through the same
1308
1724
  factory with equivalent options; the runtime does not diff options, and
1309
1725
  module identity — that the factory constructing this runtime still
1310
1726
  belongs to the snapshot's playbook — is likewise the host's check to
1311
1727
  make before calling `restore`.
1728
+ Before actor or source-state restoration, a linked workflow runtime shall require
1729
+ the snapshot ledger to equal the detached synchronous mirror exposed by its
1730
+ current-host capability; the internal Captain runtime shall require its mirror
1731
+ to be empty.
1312
1732
  `restore` shall bind the session and its current detached role bindings, restore the local-role token projection, the
1313
1733
  sequence counters, and the
1314
1734
  prior-state descriptor from the snapshot,
@@ -1331,6 +1751,110 @@ ownership without a child-host call or duplicate start/finish boundary.
1331
1751
  A restore failure shall leave the runtime unbound so `dispose` remains
1332
1752
  callable and terminal.
1333
1753
 
1754
+ ## Retained-snapshot adoption (optional)
1755
+
1756
+ A linked runtime may implement the optional adoption capability of
1757
+ `@sublang/playbook/runtime` — `adopt(session, snapshot, context)` — as a third
1758
+ initialization path distinct from `init` and same-engagement `restore`.
1759
+ Adoption may bind a retained generation to a fresh valid `PlaybookSession`
1760
+ identity. Every runtime the shared `createXStatePlaybookRuntime` factory
1761
+ constructs implements `adopt`, regardless of whether the artifact supplies
1762
+ retained-generation classification metadata; a bespoke runtime may omit it,
1763
+ and hosts feature-detect the capability by member presence.
1764
+
1765
+ Before actor construction or any player-session-store, port, trace, status,
1766
+ or telemetry effect, `adopt` shall validate and detach the target session, the
1767
+ snapshot, and an exact closed-schema `PlaybookAdoptionContext` whose nonempty
1768
+ `sourceSessionId` names the retained frame's source runtime session, whose
1769
+ nonempty `sourceGenerationId` names the retained stack root's source
1770
+ `rootSessionId`, and whose optional nonempty `targetChildSessionId` is present
1771
+ exactly when the snapshot carries a suspended call. The source session and
1772
+ generation ids shall coincide exactly for a root frame. The target session and
1773
+ root ids shall each differ from their source counterparts, and a supplied
1774
+ target child id shall differ from every source and target identity visible to
1775
+ that frame. Accessors, unknown or missing members, empty identities, and an
1776
+ inconsistent child mapping shall reject during preflight.
1777
+
1778
+ That preflight shall also validate the part of the exact structural envelope
1779
+ visible to the runtime: snapshot schema version `4`, target playbook id, the
1780
+ factory's already-validated artifact contract, and any supplied local-role
1781
+ binding set against the artifact's declared roles. The adopting host
1782
+ owns the working-directory and complete catalog-entry comparison — registry
1783
+ module identity, manifest command, options, and role set — plus every retained
1784
+ frame's artifact-schema comparison, and shall perform them before calling the
1785
+ runtime capability (DR-038 §3).
1786
+ The preflight shall apply the same exact full-mirror rule as restore: a linked
1787
+ workflow target receives a current-host mirror equal to the retained ledger,
1788
+ while the internal Captain target requires the retained ledger to be empty.
1789
+
1790
+ Adoption shall not restore any source counter. The fresh target trace, turn,
1791
+ judge-call, player-call, supported direct-Captain-call, playbook-call, and
1792
+ apply-call counter spaces shall start at zero. Before its session-start trace,
1793
+ a descriptor-free adoption leaves the playbook-call counter at zero. A
1794
+ suspended adoption instead consumes `playbook-1` as the fresh target call id,
1795
+ replaces the descriptor's source child id with `targetChildSessionId`, omits the
1796
+ source `turnId`, and sets the target playbook-call counter to one; it changes no
1797
+ opaque persisted machine value and makes no child-host call.
1798
+
1799
+ After preflight, adoption shall construct the persisted actor and prepare the
1800
+ nested bridge through the same transaction as restore, using the rebased
1801
+ descriptor or an explicit absence. Before actor startup it shall emit exactly
1802
+ one `session.started` as target trace sequence `1`, carrying the adopted
1803
+ top-level `state` and optional `stateId` plus an exact `adoption` object:
1804
+
1805
+ - without a suspended call, `{ sourceSessionId, sourceGenerationId }`;
1806
+ - with a suspended call, `{ sourceSessionId, sourceGenerationId,
1807
+ sourceCallId, sourceChildSessionId, targetCallId: 'playbook-1',
1808
+ targetChildSessionId }`, while the event also carries top-level
1809
+ `callId: 'playbook-1'` and no `turnId`.
1810
+
1811
+ The runtime shall then start the actor with inspection effects suppressed,
1812
+ claim the rebased descriptor, require the reconstructed active normalized
1813
+ state to equal the retained state under that rebase, drain suppressed work,
1814
+ and confirm the bridge as the final fallible step. A preflight mismatch emits
1815
+ nothing. A later state or bridge mismatch makes no child-host call or
1816
+ playbook-call start/finish boundary, rolls provisional ownership back, and,
1817
+ because the target start was attempted, performs failed-start cleanup with one
1818
+ best-effort target `session.disposed`; successful cleanup leaves the runtime
1819
+ reusable. A successful adoption shall close `init`, `restore`, and `adopt`
1820
+ under the ordinary one-start runtime lifecycle. Its immediate export shall
1821
+ carry trace sequence `1`, zero fresh turn, judge, and player counters, zero
1822
+ direct-Captain counter when supported, and playbook-call sequence zero or one
1823
+ according to the suspended shape. Later target turns and calls allocate from
1824
+ those fresh counters rather than continue any source id or sequence. Ordinary
1825
+ same-engagement restore remains trace-silent and preserves its source
1826
+ identities and counters exactly (DR-038 §5).
1827
+
1828
+ Adoption shall not apply the retained snapshot's `roleResumeTokens` through a
1829
+ supplied player-session store's `restore` operation or seed runtime-private
1830
+ continuation from them. For every later local-role invocation, any target
1831
+ session `roleBindings` are the sole source of supplied player and prompt
1832
+ identities, and any supplied player-session store is the sole conversation
1833
+ authority. The runtime shall resolve the current binding and, when a store is
1834
+ supplied, select it at the invocation boundary and pass the exact selected
1835
+ token or `false`. Where
1836
+ the ordinary continuation rules authorize a store mutation, that mutation
1837
+ shall target the same store. It shall never fall back to the retained token
1838
+ projection. A replacement binding whose current selection is `false` therefore
1839
+ starts fresh under its new identities; without a supplied store, the target
1840
+ runtime's private continuation starts empty (DR-038 §4).
1841
+
1842
+ ## Retained-generation classification (optional)
1843
+
1844
+ A linked runtime may expose the optional read-only
1845
+ `retainedGenerationMetadata` marker of `@sublang/playbook/runtime` together
1846
+ with the parked-session snapshot pair and the independently feature-detected
1847
+ adoption capability so a Captain can retain its safe pre-terminal generations.
1848
+ Its `unfinishedFinalStateIds` array shall preserve the artifact's link-time
1849
+ declaration exactly, including an explicitly empty set, and shall be immutable
1850
+ and detached from that declaration. Absence means the runtime contributes no
1851
+ retained generation; presence supplies only terminal classification metadata
1852
+ and does not itself supply the adoption operation.
1853
+ Every runtime the shared `createXStatePlaybookRuntime` factory constructs from
1854
+ a supplied `unfinishedFinalStateIds` spec member shall expose the marker; a
1855
+ bespoke runtime opts into classification only by implementing the public member
1856
+ itself.
1857
+
1334
1858
  ## Control surface (optional)
1335
1859
 
1336
1860
  A linked runtime may implement the optional control-surface capability of
@@ -1367,6 +1891,12 @@ type PlaybookControlReceipt =
1367
1891
  // Optional PlaybookRuntime members — both or neither:
1368
1892
  describe?(): PlaybookControlView;
1369
1893
  apply?(input: { actionId: string; key: string; signal: AbortSignal }): Promise<PlaybookControlReceipt>;
1894
+
1895
+ // Independent optional host-only unresolved-envelope identity seam:
1896
+ unresolvedEffectEnvelopes?(): readonly (
1897
+ | { readonly kind: 'boundary'; readonly boundaryId: string }
1898
+ | { readonly kind: 'logical-operation'; readonly operationId: string }
1899
+ )[];
1370
1900
  ```
1371
1901
 
1372
1902
  `describe()` shall be side-effect free — it emits no trace, status, or
@@ -1387,6 +1917,16 @@ identifier for it. A state whose source declares no description carries no
1387
1917
  `stateDescription`: an id is never promoted into a description, so a host is
1388
1918
  never handed an identifier dressed as meaning.
1389
1919
 
1920
+ At that same safe control-capture point, a schema-3 runtime that retains
1921
+ effect-possible outcome-unresolved work may expose
1922
+ `unresolvedEffectEnvelopes()` so its host can project the authoritative effect
1923
+ ledger. The method shall return only exact nonblank durable boundary or
1924
+ logical-operation identities in envelope order, shall return an empty list
1925
+ when no unresolved envelope remains, and shall expose no receipt, repository
1926
+ observation, semantic evidence, prose, or live authority. It is side-effect
1927
+ free on the same terms as `describe()`, and no returned identity or bounded
1928
+ repository evidence shall enter `PlaybookRunResult`.
1929
+
1390
1930
  The view's `context` is an explicit projection the linked runtime **authors**,
1391
1931
  never an allow-by-default serialization of the FSM context (PBRT-52).
1392
1932
  Only the runtime knows which of its context members are safe and relevant
@@ -1417,15 +1957,23 @@ default. The rules:
1417
1957
  Actions derive from the live snapshot, only at the same safe point the
1418
1958
  parked-session snapshot uses (actor status `active`, quiescent, no pending
1419
1959
  nested call); anywhere else `actions` is empty while the rest of the view
1420
- still describes the state. Two families exist, labeled from source state
1421
- descriptions:
1960
+ still describes the state.
1961
+ 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`.
1962
+ Otherwise two ordinary families exist, labeled from source state descriptions:
1422
1963
 
1423
1964
  - **Failure-state retry** — while the singular state id is the recoverable
1424
- failure state and the runtime holds a recorded last classified event (the
1425
- event a public Boss boundary sent that drove the run into `failed`, kept
1426
- with its recorded payload), and the live snapshot accepts that event, the
1427
- runtime shall advertise `retry:<EVENT_TYPE>` replaying exactly that
1428
- recorded event.
1965
+ failure state and the live snapshot accepts the retry event sourced below,
1966
+ the runtime shall advertise `retry:<EVENT_TYPE>` replaying exactly that
1967
+ event. Where the emitted module's entry-event declaration names the FSM
1968
+ context member the machine's entry action copies the exact Boss text into
1969
+ (DR-034), the retry event is that deterministic entry event built from the
1970
+ live snapshot's member — excluded when the member is absent, not a string,
1971
+ or blank, and never falling back to the record. Where it names no member,
1972
+ the retry event is the recorded last classified event (the event a public
1973
+ Boss boundary sent that drove the run into `failed`, kept with its recorded
1974
+ payload), and there is none while the runtime holds none. The member is
1975
+ declared, never inferred from a context member that happens to match the
1976
+ entry event's text field.
1429
1977
  - **Jump entries** — for each registered resumable state id whose
1430
1978
  explicit-state-jump event (`BOSS_INTERRUPT` with that `targetId`, optional
1431
1979
  textual fields omitted) the live snapshot accepts, guards included, the
@@ -1456,8 +2004,8 @@ its key — a later call with that key revalidates afresh, traces its own
1456
2004
  pair, and may execute once the action is advertised — and a key whose call
1457
2005
  threw before reaching acceptance (lifecycle misuse, invalid input, a
1458
2006
  pre-acceptance abort, a rejected start-boundary sink) likewise records
1459
- nothing, so a later call with that key may execute. Executing sends the
1460
- validated event through the same actor drive as `handleBossInput` — state
2007
+ nothing, so a later call with that key may execute.
2008
+ Executing an ordinary retry or jump sends the validated event through the same actor drive as `handleBossInput` — state
1461
2009
  transitions, player/judge boundaries, statuses, and traces flow unchanged —
1462
2010
  and settles `executed` with the projected run result, or `failed` with the
1463
2011
  normalized error when the run settles in the failure state, aborts, or a
@@ -1468,6 +2016,11 @@ the paired `apply.started` / `apply.finished` events of §Playbook trace, and
1468
2016
  `apply` shares the single active-boundary sentinel with `handleBossInput`
1469
2017
  and `resumePlaybookCall`.
1470
2018
 
2019
+ Executing `reconcile:unresolved-effect` shall use only the current host's authoritative effect ledger for any reconciliation refresh and shall start no player.
2020
+ When an open deferred logical operation is checkpoint-restoration eligible, that action shall reacquire its exclusive repository claim and compare the current observation with the saved checkpoint; exact equality shall durably consume eligibility and return to the identical bound wait with its stable question through an ordinary nonterminal run result without a player, judge, or semantic-candidate delivery, while inequality or any other still-unresolved evidence shall return `no-action` and remain unresolved.
2021
+ Executing `abandon:unresolved-effect` shall move no FSM state or start any player, judge, Captain, script, or child call and shall settle `executed` with exactly `{ outcome: 'unresolved-effect', state }`, where `state` is the current normalized nonfinal state.
2022
+ That state-only run-result arm shall carry no `stateDescription`, output, pending call, error, repository receipt, effect ledger, semantic evidence, or other bounded effect fact, and shall claim neither an authored outcome nor workflow completion.
2023
+
1471
2024
  Acceptance is also the line past which `apply` does not throw, and
1472
2025
  publication — the `apply.finished` emission — is the line past which its
1473
2026
  receipt no longer changes. A settlement failure after acceptance but before
@@ -1480,12 +2033,17 @@ trace and the return agree, and a receipt states what happened to the effect
1480
2033
  rather than what happened to its telemetry. The published receipt stands, is
1481
2034
  returned and replayed verbatim, and the delivery failure travels on the
1482
2035
  runtime's emission-failure channel to surface from the next public boundary
1483
- that drains.
2036
+ that drains — unless the delivery failure is causally identical to the apply
2037
+ signal's own abort reason, in which case it evidences the cancellation and is
2038
+ dropped, not latched
2039
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
1484
2040
 
1485
2041
  The recorded receipts and the recorded last classified event are
1486
- process-local: the durable runtime snapshot persists neither, and a
1487
- restored runtime advertises a retry again only after its next classified
1488
- event.
2042
+ process-local: the durable runtime snapshot persists neither. A restored
2043
+ runtime therefore advertises the retry of a declared entry-event source
2044
+ immediately — that payload rides the persisted machine snapshot — while a
2045
+ module declaring no source advertises a retry again only after its next
2046
+ classified event.
1489
2047
 
1490
2048
  ## Abort
1491
2049
 
@@ -1499,7 +2057,61 @@ the shared `combineAbortSignals`). Classify a rejection as cancellation by its
1499
2057
  causal identity with the applicable signal reason, not by an `AbortError` name
1500
2058
  or by observing only that the signal is also aborted. Signals may carry an
1501
2059
  ordinary `Error`, while a distinct transport or sink failure that occurs after
1502
- abort remains a non-abort control error and takes precedence. On abort, the
2060
+ abort remains a non-abort control error and takes precedence. Classification
2061
+ lives at each latch or report site, against the boundary signal applicable
2062
+ there — the invocation-lifetime combined signal, and during a resume that
2063
+ boundary's own signal — so a failure causally identical to the applicable
2064
+ reason is the cancellation's own evidence: it is handled there under the phase
2065
+ rules below, never mislabeled as a distinct failure and never carried to an
2066
+ unrelated later boundary
2067
+ ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
2068
+ A failure already latched as distinct retains that ownership; a later drain
2069
+ shall not reinterpret it against another boundary whose abort signal happens
2070
+ to use the same object as its reason.
2071
+ A public boundary settles on the machine's state at its quiescence point,
2072
+ in this precedence: a suspended pending call, then a distinct actor error,
2073
+ then terminal completion, then a coincident abort, then the recoverable
2074
+ failure state — a completed machine settles `terminal` even when the signal
2075
+ also aborted, because an `aborted` settlement over a terminal machine hides
2076
+ work the next turn would silently restart.
2077
+ An abort observed after the outcome is computed does not rewrite it, and a
2078
+ settlement-channel rejection causally identical to the abort reason is
2079
+ forgiven, so the returned result and the settlement trace state one fact.
2080
+ A boundary entered with an already-aborted signal delivers nothing.
2081
+ That entry refusal precedes the ordinary settlement order: a pre-aborted
2082
+ resume reports `aborted` while preserving its suspended pending call rather
2083
+ than reporting `suspended` for work it did not deliver.
2084
+ Cancellation-coupled channel rejections obey this phase matrix:
2085
+
2086
+ - **Before a host call or effect starts (and before apply acceptance):** an
2087
+ identical start-channel rejection starts no host call or effect and latches
2088
+ no control error. A recorded start receives one best-effort `aborted` finish.
2089
+ An ordinary run boundary then settles by the precedence above; a
2090
+ pre-acceptance `apply` instead rejects with that exact reason, records no
2091
+ receipt, and leaves its key reusable.
2092
+ - **After a host call or effect starts but before its finish or outcome is
2093
+ recorded:** an identical host, cleanup, observer, or in-flight-emission
2094
+ rejection is cancellation evidence. Invocation-owned cleanup completes, a
2095
+ started trace pair receives one `aborted` finish, and the ordinary boundary
2096
+ settles by the precedence above. A distinct rejection remains a control
2097
+ failure, produces the applicable error finish, and takes distinct-error
2098
+ precedence.
2099
+ - **After a call finish is recorded but before the enclosing non-apply outcome
2100
+ is computed:** an identical finish-sink or drain rejection leaves the
2101
+ recorded finish unchanged, emits no corrective second finish, latches
2102
+ nothing, and lets the enclosing boundary settle by the precedence above.
2103
+ - **After apply acceptance but before receipt publication:** every settlement
2104
+ failure, the exact apply abort reason included, is folded into the current
2105
+ `failed` receipt. Acceptance forbids throwing; the replacement receipt is
2106
+ published, returned, and replayed, and the failure is not carried as a later
2107
+ delivery error.
2108
+ - **After a non-apply outcome is computed or an apply receipt is published:**
2109
+ an identical rejection is dropped without rewriting the outcome or receipt
2110
+ and without poisoning a later boundary. A distinct non-apply settlement
2111
+ rejection retains current-boundary control-error precedence; a distinct
2112
+ post-publication apply rejection retains the published receipt and travels
2113
+ on the delivery-failure channel to the next boundary that drains.
2114
+ On abort, the
1503
2115
  runtime shall not merely race the imperative
1504
2116
  wait and return while an invocation remains live: it shall let the selected
1505
2117
  rejection path settle and drive the actor to a quiescent state before returning
@@ -1563,16 +2175,23 @@ The `playbook.trace` copies are the host-agnostic runtime-boundary record requir
1563
2175
  ## Output
1564
2176
 
1565
2177
  The link compiler emits one TypeScript module per playbook.
1566
- For an FSM that declares no `type: 'parallel'` state, it shall emit the thin
1567
- shared-factory module defined below.
2178
+ Every linked artifact shall emit an `unfinishedFinalStateIds` set beside its resumable-state registry as mechanical link-time metadata.
2179
+ The set shall contain exactly the stable ids of root `type: 'final'` states whose terminal outcomes leave the procedure unfinished, and shall be explicitly empty when no terminal outcome does.
2180
+ The linker shall not infer the set from a state description, opaque output, or procedure prose.
2181
+ The linker shall reject a declared id that does not name a root final state, and the shared factory shall independently reject it at construction before runtime effects.
2182
+ For a factory-backed artifact the set is a `spec` member; a bespoke artifact shall retain equivalent linked metadata, and the artifact declaration is not itself the public runtime retention marker or an adoption capability.
2183
+ For an FSM that declares no `type: 'parallel'` state — necessarily flat
2184
+ under [gears2fsm.md](gears2fsm.md)'s one-state-per-item mapping — it shall
2185
+ emit the thin shared-factory module defined below.
1568
2186
  For an FSM that declares a parallel state, it shall emit bespoke linked
1569
2187
  machinery satisfying this document's runtime contract and shall not invoke
1570
- `createXStatePlaybookRuntime`, whose supported domain is single-region FSMs
1571
- under [DR-019](../specs/decisions/019-shared-linked-runtime-factory.md).
2188
+ `createXStatePlaybookRuntime`, whose supported domain is flat single-region
2189
+ FSMs under [DR-019](../specs/decisions/019-shared-linked-runtime-factory.md).
1572
2190
  The FSM-interpreter machinery — actor wiring, boundary tracing, Boss-event
1573
2191
  mapping, adjudication, script execution, nested-playbook bridging, session
1574
- lifecycle, abort handling, and the optional parked-session snapshot
1575
- capability — is not regenerated for a factory-backed artifact: it ships once
2192
+ lifecycle, abort handling, and the optional parked-session snapshot and
2193
+ retained-snapshot adoption capabilities — is not regenerated for a
2194
+ factory-backed artifact: it ships once
1576
2195
  as the shared `createXStatePlaybookRuntime(machine, spec)` factory exported by
1577
2196
  `@sublang/playbook/xstate-runtime`, and the emitted module hands its FSM and
1578
2197
  a small per-playbook `spec` to that factory. Every behavioral section of
@@ -1613,7 +2232,10 @@ The thin emitted module:
1613
2232
  actor.
1614
2233
  - Supplies in `spec` only what the factory cannot read from the FSM
1615
2234
  artifact's own data: the deterministic textual entry event where
1616
- §Boss-event mapping prescribes deterministic entry; compact `bossEvents`
2235
+ §Boss-event mapping prescribes deterministic entry, naming with it the FSM
2236
+ context member that event's own transition action copies the exact Boss
2237
+ text into wherever the machine keeps one, so the failure-state retry of
2238
+ §Control surface survives a restore; compact `bossEvents`
1617
2239
  metadata for each additional Boss-union arm whose exact required/optional
1618
2240
  judge fields, runtime-owned text fields, or closed string values disappear
1619
2241
  under TypeScript erasure; `placeholderFields` only for authored token/field
@@ -1623,9 +2245,13 @@ The thin emitted module:
1623
2245
  complete `roleStates` status map derived from every FSM state that invokes
1624
2246
  the typed `player` actor, with each `role` copied from that state's
1625
2247
  source-derived `meta.playbook.role` (an empty map when there is no such
1626
- state); the
2248
+ state); the exact schema-3 `outcomeAuthority` map derived
2249
+ from every such state's `invoke.input.result` contract and its linked field
2250
+ authorities and repository dispositions (an explicit empty governed map
2251
+ when there is no such state); the
1627
2252
  `verbatimPayloadFields` set derived from annotated result fields above; the
1628
- `controlContextFields` projection of §Control surface; and any
2253
+ explicitly empty or populated `unfinishedFinalStateIds` set declared above;
2254
+ the `controlContextFields` projection of §Control surface; and any
1629
2255
  per-playbook strategy override (classifier, prompt composers,
1630
2256
  required-field extraction, status formatting) an earlier section of this
1631
2257
  definition requires for that playbook.
@@ -1682,9 +2308,8 @@ The thin emitted module:
1682
2308
  runtime-owned arm to have lost payload detail under erasure shall report
1683
2309
  that gap rather than emit the entry.
1684
2310
  - Supplies `spec.compat` with the compatibility values current at link time:
1685
- `{ artifactSchema, runtimeAbi }`, where `artifactSchema` is `2` the
1686
- schema number of the local-role thin-module format this §Output defines — and
1687
- `runtimeAbi` is the installed shared engine's `RUNTIME_ABI` self-report.
2311
+ `{ artifactSchema: 3, runtimeAbi }`, where `runtimeAbi` is the installed shared engine's
2312
+ `RUNTIME_ABI` self-report.
1688
2313
  The linker shall verify that the installed engine lists the emitted
1689
2314
  schema in `SUPPORTED_ARTIFACT_SCHEMAS` and treat its absence as a
1690
2315
  link-time error; it shall not stamp a different member (such as the
@@ -1695,9 +2320,29 @@ The thin emitted module:
1695
2320
  module and fails construction on a mismatch, so an artifact linked under
1696
2321
  one engine cannot run silently skewed under another. Modules emitted
1697
2322
  before this contract carry no `compat` member and shall reject before interpretation.
1698
- - Requires the containing public registry manifest to advertise the same `artifactSchema: 2`; the Captain host shall reject a missing or disagreeing registry value before constructing this runtime, and a bespoke runtime profile shall advertise the same schema without claiming this shared factory's `runtimeAbi`.
1699
- - Default-exports the factory call as `createPlaybookRuntime`, typed
1700
- `PlaybookRuntimeFactory<PlaybookRuntimeOptions>`.
2323
+ - Requires the containing public registry manifest to advertise the identical
2324
+ `artifactSchema` and an exact implementation `runtimeProfile`. A shared
2325
+ factory profile is `{ kind: 'shared-factory', compat }`, where `compat` is
2326
+ the immutable compatibility record captured by that actual factory from
2327
+ its validated `spec.compat`; a bespoke profile is
2328
+ `{ kind: 'bespoke', artifactSchema }`, with schema `3` declared directly by
2329
+ that implementation and no `runtimeAbi` claim. A registry factory accepts configured options and current
2330
+ host capabilities separately and composes the linked runtime's exact
2331
+ `{ configuredOptions, hostCapabilities }` input. The Captain host shall
2332
+ capture the imported manifest fields once, require capabilities for every
2333
+ and only enabled artifact id, validate each capability's artifact, role,
2334
+ cohort, and canonical-worktree authority, and reject a missing, extra,
2335
+ malformed, or mismatched capability before runtime construction.
2336
+ - Default-exports the factory call as `createPlaybookRuntime`, typed as
2337
+ `XStatePlaybookRuntimeFactory<XStatePlaybookRuntimeConstruction<PlaybookRuntimeOptions, HostCapabilities>, 3>`
2338
+ with the artifact's declared live capability type. A registry module loads
2339
+ dynamically inside the host's caught boundary, so its eager module-scope
2340
+ factory call fails fast there. The compiled session Captain module is the
2341
+ exception: the shell and both CLI front ends import it statically, so it
2342
+ shall defer its factory call to the first runtime request — an eager call
2343
+ would turn a future `spec.compat` rejection into an uncaught module-load
2344
+ error that takes even `--help` down, instead of the caught
2345
+ host-construction boundary's setup diagnostic.
1701
2346
  - Exposes, under an `_internal` export, the pure helpers verification
1702
2347
  needs — at least the prompt composers its own machine uses, which may
1703
2348
  re-export the shared defaults when the spec does not override composition —
@@ -1801,7 +2446,7 @@ New behavior in any of these areas requires a separate slc spec.
1801
2446
 
1802
2447
  ## References
1803
2448
 
1804
- [1]: [text2gears](text2gears.md) "First phase: text → GEARS spec items."
1805
- [2]: [gears2fsm](gears2fsm.md) "Second phase: GEARS items → FSM artifact."
2449
+ [1]: text2gears.md "First phase: text → GEARS spec items."
2450
+ [2]: gears2fsm.md "Second phase: GEARS items → FSM artifact."
1806
2451
  [3]: https://stately.ai/docs/actors "XState actors — `createActor`, snapshots, abort signal handling."
1807
2452
  [4]: https://github.com/sindresorhus/p-queue#readme "p-queue concurrency and AbortSignal support."