@sublang/playbook 15.1.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +2 -0
  2. package/docs/cli.md +36 -18
  3. package/docs/configuration.md +7 -5
  4. package/docs/embedding.md +28 -7
  5. package/package.json +4 -4
  6. package/reference/sdlc/branch.playbook/branch.fsm.d.ts +63 -33
  7. package/reference/sdlc/branch.playbook/branch.fsm.js +190 -108
  8. package/reference/sdlc/branch.playbook/branch.fsm.ts +269 -147
  9. package/reference/sdlc/branch.playbook/branch.gears.md +7 -17
  10. package/reference/sdlc/branch.playbook/branch.playbook.d.ts +12 -16
  11. package/reference/sdlc/branch.playbook/branch.playbook.js +112 -101
  12. package/reference/sdlc/branch.playbook/branch.playbook.ts +128 -171
  13. package/reference/sdlc/branch.playbook/branch.registry.d.ts +3 -2
  14. package/reference/sdlc/branch.playbook/branch.registry.ts +6 -2
  15. package/reference/sdlc/captain.md +19 -10
  16. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +6 -2
  17. package/reference/sdlc/captain.playbook/captain.fsm.js +19 -7
  18. package/reference/sdlc/captain.playbook/captain.fsm.ts +25 -10
  19. package/reference/sdlc/captain.playbook/captain.gears.md +16 -8
  20. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +1 -1
  21. package/reference/sdlc/captain.playbook/captain.playbook.js +3 -2
  22. package/reference/sdlc/captain.playbook/captain.playbook.ts +4 -3
  23. package/reference/sdlc/code.playbook/bin/interactive-session.js +14 -1
  24. package/reference/sdlc/code.playbook/bin/portable-codec.js +3 -0
  25. package/reference/sdlc/code.playbook/bin/repository-effects.js +1 -0
  26. package/reference/sdlc/code.playbook/bin/run.js +128 -85
  27. package/reference/sdlc/code.playbook/bin/session-host.js +40 -8
  28. package/reference/sdlc/code.playbook/bin/session-store.js +162 -34
  29. package/reference/sdlc/code.playbook/code.fsm.d.ts +146 -93
  30. package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +8 -8
  31. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +13 -13
  32. package/reference/sdlc/code.playbook/code.fsm.js +609 -649
  33. package/reference/sdlc/code.playbook/code.fsm.ts +890 -821
  34. package/reference/sdlc/code.playbook/code.gears.md +39 -29
  35. package/reference/sdlc/code.playbook/code.playbook.d.ts +13 -16
  36. package/reference/sdlc/code.playbook/code.playbook.js +166 -147
  37. package/reference/sdlc/code.playbook/code.playbook.ts +180 -229
  38. package/reference/sdlc/code.playbook/code.registry.d.ts +3 -2
  39. package/reference/sdlc/code.playbook/code.registry.ts +6 -2
  40. package/reference/sdlc/code.playbook/host-capabilities.d.ts +3 -0
  41. package/reference/sdlc/code.playbook/playbook-captain.d.ts +74 -2
  42. package/reference/sdlc/code.playbook/playbook-captain.js +1548 -468
  43. package/reference/sdlc/code.playbook/playbook-captain.ts +1993 -573
  44. package/reference/sdlc/code.playbook/session-host.d.ts +4 -1
  45. package/reference/sdlc/code.playbook/session-store.d.ts +17 -0
  46. package/reference/sdlc/code.playbook/session-store.js +1 -0
  47. package/reference/sdlc/decide.md +1 -0
  48. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +273 -157
  49. package/reference/sdlc/decide.playbook/decide.fsm.js +765 -732
  50. package/reference/sdlc/decide.playbook/decide.fsm.ts +1167 -945
  51. package/reference/sdlc/decide.playbook/decide.gears.md +28 -21
  52. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -59
  53. package/reference/sdlc/decide.playbook/decide.playbook.js +155 -3696
  54. package/reference/sdlc/decide.playbook/decide.playbook.ts +186 -5244
  55. package/reference/sdlc/decide.playbook/decide.registry.d.ts +8 -4
  56. package/reference/sdlc/decide.playbook/decide.registry.js +2 -2
  57. package/reference/sdlc/decide.playbook/decide.registry.ts +13 -6
  58. package/reference/sdlc/dev.playbook/dev.fsm.d.ts +116 -134
  59. package/reference/sdlc/dev.playbook/dev.fsm.js +517 -575
  60. package/reference/sdlc/dev.playbook/dev.fsm.ts +667 -787
  61. package/reference/sdlc/dev.playbook/dev.gears.md +52 -54
  62. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +13 -10
  63. package/reference/sdlc/dev.playbook/dev.playbook.js +128 -120
  64. package/reference/sdlc/dev.playbook/dev.playbook.ts +143 -193
  65. package/reference/sdlc/dev.playbook/dev.registry.d.ts +3 -2
  66. package/reference/sdlc/dev.playbook/dev.registry.ts +6 -2
  67. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +95 -88
  68. package/reference/sdlc/pr.playbook/pr.fsm.js +461 -518
  69. package/reference/sdlc/pr.playbook/pr.fsm.ts +533 -663
  70. package/reference/sdlc/pr.playbook/pr.gears.md +47 -63
  71. package/reference/sdlc/pr.playbook/pr.playbook.d.ts +13 -23
  72. package/reference/sdlc/pr.playbook/pr.playbook.js +118 -112
  73. package/reference/sdlc/pr.playbook/pr.playbook.ts +136 -190
  74. package/reference/sdlc/pr.playbook/pr.registry.d.ts +3 -2
  75. package/reference/sdlc/pr.playbook/pr.registry.ts +4 -2
  76. package/reference/sdlc/review.md +3 -3
  77. package/reference/sdlc/review.playbook/review.fsm.d.ts +150 -116
  78. package/reference/sdlc/review.playbook/review.fsm.js +479 -550
  79. package/reference/sdlc/review.playbook/review.fsm.ts +717 -662
  80. package/reference/sdlc/review.playbook/review.gears.md +32 -29
  81. package/reference/sdlc/review.playbook/review.playbook.d.ts +12 -12
  82. package/reference/sdlc/review.playbook/review.playbook.js +180 -155
  83. package/reference/sdlc/review.playbook/review.playbook.ts +190 -230
  84. package/reference/sdlc/review.playbook/review.registry.d.ts +6 -5
  85. package/reference/sdlc/review.playbook/review.registry.js +3 -3
  86. package/reference/sdlc/review.playbook/review.registry.ts +9 -5
  87. package/slc/gears2fsm.md +77 -8
  88. package/slc/link.md +127 -60
  89. package/slc/materialize-link.mjs +51 -13
  90. package/slc/optimize.md +4 -1
  91. package/slc/prefix-prompts.mjs +29 -28
  92. package/slc/prefix.md +27 -24
  93. package/slc/slc.pin-inputs.json +11 -2
  94. package/slc/text2gears.md +71 -11
  95. package/src/runtime.d.ts +46 -2
  96. package/src/runtime.ts +42 -2
  97. package/src/xstate-playbook-runtime.d.ts +63 -23
  98. package/src/xstate-playbook-runtime.js +1637 -475
  99. package/src/xstate-playbook-runtime.ts +2105 -532
  100. package/src/xstate-runtime.d.ts +3 -4
  101. package/src/xstate-runtime.js +52 -0
  102. package/src/xstate-runtime.ts +59 -4
package/slc/gears2fsm.md CHANGED
@@ -141,9 +141,11 @@ generic Captain forms, wire `<boss-intent>` from `bossIntent`,
141
141
  `remainingPlan`, and `<completed-call-results>` from
142
142
  `completedCallResults`. Other non-identity placeholders shall retain the
143
143
  semantic typed field established by Source (for example `<#>` from
144
- `irNumber`). Leaving an ordinary runtime-value placeholder literal, replacing
145
- it with an empty default because its field was omitted, or making the linker
146
- recover it from untyped context is malformed.
144
+ `irNumber`); a created-commit or labelled-section placeholder binds as
145
+ [Context and prompts](#context-and-prompts) states. Leaving an ordinary
146
+ runtime-value placeholder literal, replacing it with an empty default because
147
+ its field was omitted, or making the linker recover it from untyped context is
148
+ malformed.
147
149
  The corresponding `invoke.input` object shall include that field beside `prompt`; storing it only in machine context does not satisfy the actor-input contract.
148
150
  When Source declares a placeholder as the current identity of a local acting
149
151
  role, preserve the placeholder literal in the FSM prompt and do not add any
@@ -241,15 +243,16 @@ teardown event. The completion rule of
241
243
  output clause binds only where Source declares a terminal result, which a
242
244
  controller Source does not.
243
245
  The controller decision state's direct-Captain result contract discriminates
244
- the closed action set of DR-029 as extended by DR-038. Its guard discriminants
246
+ the closed action set of DR-029 as extended by DR-038 and DR-069. Its guard discriminants
245
247
  are a stable compiler contract, not names the compiler may invent — `respond`, `resume`,
246
- `start`, `switch`, `dismiss`, `deliver`, and `runtime` — with each guard's
248
+ `start`, `switch`, `dismiss`, `deliver`, and `runtime`, plus `recover` when Source declares recovery preparation — with each guard's
247
249
  required payload fields:
248
250
 
249
251
  - `respond` requires `text`;
250
252
  - `resume` requires `playbookId` and carries no `input`;
251
253
  - `start` and `switch` each require `playbookId` and `input`;
252
254
  - `runtime` requires `actionId`;
255
+ - `recover`, when declared, requires no payload and selects host-owned preparation followed by runtime-validated continuation;
253
256
  - `dismiss` and `deliver` require none — a `deliver` result in particular
254
257
  carries no text payload: the host is authoritative for the delivered text,
255
258
  so the contract declares no field for it.
@@ -610,6 +613,37 @@ or remove remaining entries as evidence arrives, but it cannot grow or retain
610
613
  the same-length plan indefinitely; the initial finite array therefore bounds
611
614
  the number of sequential child calls without an arbitrary runtime call limit.
612
615
 
616
+ A placeholder that reads the commit an earlier call created — `<code-commit>`,
617
+ `<decide-commit>` — binds to a typed context field named by its canonical
618
+ mapping, assigned from that call's accepted `latestCommit`, the effect-owned
619
+ property the runtime fills from the repository receipt
620
+ ([text2gears](text2gears.md#result-contracts)); the actor input carries the
621
+ field beside the prompt like any other relayed value.
622
+ A placeholder the GEARS defines as a labelled section of another relayed
623
+ text binds to a typed context field named by its canonical mapping. The
624
+ definition names the label that opens the section and the labels that end it —
625
+ `<original-intent>` as the `Original intent:` section of the caller's request,
626
+ which runs to the `Review scope:` line or to the end of the request. The
627
+ machine derives the field deterministically in the entry action or transition
628
+ that stores the text and again in every action that replaces it, such as a
629
+ fresh entry directive or an interrupt that restarts with new text:
630
+
631
+ - the text as read is the text itself, except that where every non-blank line
632
+ begins with `>`, each line is read without that marker and one optional space
633
+ after it, the literal quote layer a quoted relay adds;
634
+ - the section is the lines of the text as read from the first line that
635
+ begins with the opening label, the label removed, through the line before the
636
+ first later line that begins with an ending label, or through the last line,
637
+ with the result trimmed; a line that merely looks labelled, such as `Note:`
638
+ or `Constraints:`, stays in the section;
639
+ - where no line begins with the opening label, or the section is empty once
640
+ trimmed — its label directly followed by an ending label or by the end of
641
+ the text — the field is the whole text as read, trimmed, so the request is
642
+ never lost.
643
+
644
+ The derived field is ordinary context that actor inputs relay, never a player
645
+ or judge output.
646
+
613
647
  Prompts shall pass only the **specific extracted fields** the player needs.
614
648
  The compiler shall not dump `JSON.stringify(lastResult)` or any opaque blob: it leaks internal `guard` strings, wastes tokens, and confuses the LLM.
615
649
 
@@ -663,6 +697,26 @@ The compiler shall update or reset those source-owned facts only at their actual
663
697
  Transitions shall be self-driving when source items define the next obligation.
664
698
  Routing to an idle hub is for recovery, unrecoverable Boss input, or one-shot entry events — not the happy path.
665
699
 
700
+ Where an artifact-schema-3 delegated-player state's outcomes are governed by the
701
+ linked runtime ([link.md "Captain adjudication"](link.md#captain-adjudication)),
702
+ every accepted `onDone` arm — each arm that routes a declared outcome, not the
703
+ malformed-output fallback — shall carry, first among its actions, the
704
+ root-machine action
705
+ `{ type: 'playbook.acceptedOutcome', params: { source: '<stateId>', target: '<target stateId>', acceptedOutcome: '<guard>' } }`,
706
+ with the setup's `actions` declaring `'playbook.acceptedOutcome'` as a no-op
707
+ that types those three string params.
708
+ `target` names the state the next public snapshot shows for the arm: the arm's
709
+ own target, or the parallel parent's `onDone` target where the arm's target is a
710
+ region's final leaf that completes that parent, because the machine leaves that
711
+ leaf before the snapshot; an arm that completes the join only when every
712
+ sibling region is already final therefore splits into two arms guarded on that
713
+ condition, each naming the state it reaches.
714
+ The linked runtime retains the marker until the next public snapshot confirms
715
+ `source` in the prior snapshot and `target` in the new one, and only then
716
+ publishes the outcome's accepted trace and `→ <acceptedOutcome>` status; an arm
717
+ without the marker accepts its outcome silently, and the host counts none of the
718
+ work it saved.
719
+
666
720
  ### Auto-advance on approval
667
721
 
668
722
  A review/approval state's success outcome shall **target the next workflow step**, not idle back to a hub.
@@ -726,8 +780,7 @@ current event, not solely from the context that action will populate.
726
780
  For an omitted event value, use an existing context seed only where Source
727
781
  permits it; do not manufacture a required constructor value to satisfy a
728
782
  context-only entry guard. The linked runtime sends the entry event normally;
729
- its `entryEvent.contextField` declaration supports failure retry and does not
730
- copy fresh text into context before the guard.
783
+ entry metadata does not copy fresh text into context before the guard.
731
784
  An entry event's copy action shall not clear per-run parameters the event omits: an absent optional field falls back to the existing (input-seeded) context value.
732
785
  The two surfaces shall not be collapsed. `BOSS_INTERRUPT` always carries its
733
786
  target id and may additionally carry typed Boss-supplied fields such as an
@@ -750,7 +803,7 @@ Boss-reply suspension, because its hub already receives every Boss turn and a
750
803
  clarifying question to Boss is a `respond` selection over the closed action
751
804
  set. The rule below is therefore universal over workflow states and silent
752
805
  about that class; in particular, adding `needsBossReply` to the controller
753
- decision state would add an eighth outcome to a closed seven-action contract
806
+ decision state would add an unauthorized outcome to its closed controller contract
754
807
  whose guard discriminants [Setup](#setup) fixes, and is nonconformant.
755
808
  There is no source-level opt-in annotation and no `needsBossReply` result metadata in GEARS output.
756
809
  The FSM compiler shall preserve the GEARS blockquote as the state's domain `prompt` body and shall not inject any Boss-question instruction into `invoke.input.prompt`.
@@ -811,6 +864,12 @@ Where several questions are pending, the classifier prompt and event shall
811
864
  require `questionId` and shall reject an omitted or unknown id without moving
812
865
  the FSM.
813
866
 
867
+ Every exit from a Captain- or player-invoking state other than into its own
868
+ Boss-reply wait — an accepted outcome, the malformed-output fallback, an actor
869
+ error — and every non-reply exit from a wait shall clear that state's pending
870
+ question and reply context, so a call that fails after resuming leaves no
871
+ stale question for the next turn's classifier to offer.
872
+
814
873
  The scalar `awaitBossReply` state and every local branch wait are quiescent for
815
874
  the runtime drive boundary.
816
875
  They shall allow a fresh root entry event or interrupt to abandon the relevant
@@ -882,6 +941,16 @@ Where Source declares a JSON-safe terminal result, the setup types shall
882
941
  declare that output and the root machine shall derive it from typed context
883
942
  through XState's machine `output` function. A final-state transition alone does
884
943
  not satisfy a declared output contract.
944
+ Where the selected pipeline supplies the
945
+ [workflow contracts](workflow-contracts.json) catalog and the compiled Source's
946
+ basename is one of its `literalTargetBindings`, the declared terminal output
947
+ shall be exactly that builtin's public interface — its variants, `status`
948
+ constants, property names, and requiredness — derived from typed context,
949
+ because callers compiled against the catalog consume it by those names; an
950
+ authored outcome the interface cannot express is an inconsistency between the
951
+ Source and the catalog, reported as an incompatible compiler input under
952
+ [Nested playbook calls](#nested-playbook-calls) rather than expressed by an
953
+ invented or renamed outcome.
885
954
  Fields that Source requires in every terminal output shall be required in the
886
955
  TypeScript output type. In particular, a declared `{ response }` result shall
887
956
  compile as `{ response: string }`, not `{ response?: string }`; reaching the
package/slc/link.md CHANGED
@@ -38,15 +38,20 @@ The link compiler shall not modify the FSM artifact and shall not re-derive Capt
38
38
 
39
39
  ## Optional deterministic materialization
40
40
 
41
- For an ordinary flat workflow using only `player` and `script` actors,
42
- the supported prompt profile below, shared remaining strategies, and primitive configured options, the linker may
41
+ For an ordinary workflow using only `player` and `script` actors — flat, or
42
+ with root parallel groups of the [Parallel groups](gears2fsm.md#parallel-groups)
43
+ shape — the supported prompt profile below, shared remaining strategies, and primitive configured options, the linker may
43
44
  use the adjacent `materialize-link.mjs` tool to emit the thin module.
44
45
  The complete definition below remains binding; the tool replaces repetitive
45
46
  module generation, not semantic analysis or emitted conformance verification.
46
47
  Read the actual FSM and supply every erased or authored contract exactly.
47
48
  Do not use `flat-defaults` or `flat-quoted-relays` when another custom composer, classifier, required-field
48
49
  extractor, session-derived input mapping, controller strategy, nested call,
49
- parallel state, or compound state is needed.
50
+ or compound state other than such a parallel group is needed.
51
+ Despite their names, every profile accepts those parallel groups: the tool
52
+ takes each region working leaf's role and label under its stable state id
53
+ like any other player state, declares nothing further, and leaves the
54
+ compiled shape to the factory preflight, which rejects any other form.
50
55
 
51
56
  Invoke the tool with the actual definition directory, source FSM, and declared
52
57
  target; supply a JSON descriptor on standard input:
@@ -60,7 +65,7 @@ node "<definition-directory>/materialize-link.mjs" --fsm "<source.fsm.ts>" --out
60
65
  "label": "EXAMPLE",
61
66
  "options": {},
62
67
  "inputMapping": {},
63
- "entryEvent": { "type": "BOSS_TASK", "textField": "bossIntent", "contextField": "bossIntent" },
68
+ "entryEvent": { "type": "BOSS_TASK", "textField": "bossIntent" },
64
69
  "bossEvents": [],
65
70
  "outcomeAuthority": {
66
71
  "governedPlayerStates": {
@@ -101,6 +106,12 @@ session-derived input values are outside this profile; do not widen their
101
106
  contracts to an unconstrained primitive.
102
107
  The tool adds optional string `cwd` for script-bearing machines; it does not
103
108
  put `cwd` in FSM input unless the descriptor explicitly maps it.
109
+ When the linked module is part of a package that compiles and ships
110
+ JavaScript siblings, supply the optional `fsmSpecifier` — the `.js` sibling
111
+ the build emits for the source FSM, such as `./code.fsm.js`, in the same
112
+ directory and with the same basename — so the emitted import satisfies the
113
+ NodeNext rule under Output; omit it for a source-only host, where the tool
114
+ derives the specifier from the `--fsm` path.
104
115
  An explicit `entryEvent: null` selects the shared classifier only where this
105
116
  definition permits no deterministic entry; it does not relax entry rules.
106
117
  `bossEvents` retains the exact additional erased event fields, source ownership,
@@ -125,8 +136,9 @@ correct the identified problem before treating linking as successful.
125
136
 
126
137
  ### Experimental labelled-string and nested-call profile
127
138
 
128
- `flat-labelled-relays` is an unmeasured candidate for flat single-region
129
- machines with delegated players, scripts, and nested `playbook` calls. It
139
+ `flat-labelled-relays` is an unmeasured candidate for machines with flat
140
+ root states or root parallel groups of that shape, whose delegated players,
141
+ scripts, and nested `playbook` calls it supports. It
130
142
  requires the installed shared `composePlayerContinuation` API. It leaves
131
143
  nested input composition, child targets, result guards, recovery, and terminal
132
144
  semantics in the unchanged FSM; the shared factory provides the nested bridge.
@@ -154,8 +166,8 @@ source tokens. Undeclared empty relay lines retain their authored text.
154
166
  The emitted verification composer has the same identity and optional third
155
167
  resume arguments as the runtime; the shared helper owns fresh/resumed Q&A.
156
168
  Structured rendering, custom classifiers/extractors/controllers, direct
157
- Captain actors, compound/parallel topology, and constrained/nonprimitive
158
- options remain unsupported. Source-derived metadata and all existing
169
+ Captain actors, compound topology other than such a parallel group, and
170
+ constrained/nonprimitive options remain unsupported. Source-derived metadata and all existing
159
171
  conformance checks remain mandatory. This profile changes no runtime bridge,
160
172
  workflow semantics, or measured performance claim.
161
173
 
@@ -184,6 +196,8 @@ interface PlaybookRuntime {
184
196
  handleBossInput(turn: {
185
197
  text: string;
186
198
  signal: AbortSignal;
199
+ /** Called synchronously just before accepting the event, at most once. */
200
+ onAccepted?: () => void;
187
201
  }): Promise<PlaybookRunResult>;
188
202
  resumePlaybookCall(input: {
189
203
  callId: string;
@@ -354,6 +368,7 @@ interface PlaybookEffectBoundary {
354
368
  readonly baseline: PlaybookRepositoryObservation;
355
369
  readonly after?: PlaybookRepositoryObservation;
356
370
  readonly physicalReceipt?: PlaybookRepositoryReceipt;
371
+ readonly restored?: PlaybookRepositoryObservation;
357
372
  readonly finalText?: string;
358
373
  readonly semanticCandidate?: JsonValue;
359
374
  readonly initialSemanticCandidate?: JsonValue;
@@ -393,6 +408,7 @@ type PlaybookEffectBoundaryStart = Omit<
393
408
  | 'finalText'
394
409
  | 'semanticCandidate'
395
410
  | 'initialSemanticCandidate'
411
+ | 'restored'
396
412
  >;
397
413
 
398
414
  type PlaybookEffectLogicalOperationStart = Omit<
@@ -454,6 +470,23 @@ interface PlaybookEffectLedgerCapability {
454
470
  ): Promise<PlaybookEffectLedger>;
455
471
  }
456
472
 
473
+ interface PlaybookRecoveryCheckpoint {
474
+ readonly id?: string;
475
+ readonly stateId: string;
476
+ readonly prompt: string;
477
+ readonly machine: JsonValue;
478
+ readonly boundaryPrefix: number;
479
+ readonly result?: JsonValue;
480
+ readonly delivered?: true;
481
+ }
482
+
483
+ interface PlaybookStepRecord {
484
+ readonly id: string;
485
+ readonly kind: 'player' | 'captain' | 'script';
486
+ readonly stateId: string;
487
+ readonly result?: JsonValue;
488
+ }
489
+
457
490
  interface PlaybookRuntimeSnapshot {
458
491
  schemaVersion: 4;
459
492
  playbookId: string;
@@ -484,6 +517,8 @@ interface PlaybookRuntimeSnapshot {
484
517
  readonly boundaryPrefix: number;
485
518
  readonly attemptId: string | null;
486
519
  };
520
+ /** Interrupted invocation, captured before its external call (DR-069). */
521
+ recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
487
522
  suspendedCall?: PlaybookSuspendedCall;
488
523
  }
489
524
 
@@ -568,7 +603,7 @@ interface XStatePlaybookRuntimeConstruction<
568
603
 
569
604
  The shared type-only contract module shall export `PlaybookRepositoryDisposition`, `PlaybookRepositoryObservation`, `PlaybookRepositoryReceipt`, `PlaybookEffectBoundary`, `PlaybookEffectBoundaryStart`, `PlaybookEffectLogicalOperation`, `PlaybookEffectLedger`, `PlaybookEffectLedgerCommand`, `PlaybookEffectLedgerCommandBatch`, and `PlaybookEffectLedgerCapability`; the executable `@sublang/playbook/xstate-runtime` module shall export `assertPlaybookEffectLedger`, `emptyPlaybookEffectLedger`, and `isPlaybookEffectLedgerMonotonicExtension` over those types.
570
605
  That executable module shall also export the centralized schema-3 semantic surface: `PlaybookSemanticFieldAuthority`, `PlaybookSemanticOutcomeSpec`, `PlaybookSemanticEvidenceInput`, `PlaybookReconciledSemanticOutput`, `PlaybookRetainedSemanticEvidence`, `PlaybookSemanticReconciliationReason`, `PlaybookSemanticReconciliation`, `PlaybookSemanticCandidateStructureError`, and `reconcilePlaybookSemanticEvidence`.
571
- The pure reconciler shall accept the declared state-local outcomes, an unknown semantic candidate, and optional unknown `finalText`, repository receipt, and runtime-field evidence; shall return a detached frozen `resolved` or `deferred` decision with exact output and retained evidence, or an `unresolved` decision with retained evidence and one closed reason from `missing-presentation-evidence`, `missing-repository-receipt`, `invalid-repository-receipt`, `repository-disposition-mismatch`, `missing-effect-evidence`, `missing-runtime-evidence`, and `inconsistent-runtime-evidence`; and shall reserve `PlaybookSemanticCandidateStructureError` for candidate defects eligible for the bounded correction path rather than effect-evidence disagreement.
606
+ The pure reconciler shall accept the declared state-local outcomes, an unknown semantic candidate, and optional unknown `finalText`, repository receipt, and runtime-field evidence; shall return a detached frozen `resolved` or `deferred` decision with exact output and retained evidence, or an `unresolved` decision with retained evidence and one closed reason from `no-matching-outcome`, `missing-presentation-evidence`, `missing-repository-receipt`, `invalid-repository-receipt`, `repository-disposition-mismatch`, `missing-effect-evidence`, `missing-runtime-evidence`, and `inconsistent-runtime-evidence`; and shall reserve `PlaybookSemanticCandidateStructureError` for candidate defects eligible for the bounded correction path rather than effect-evidence disagreement.
572
607
  The empty ledger shall be exactly `{ schemaVersion: 1, revision: 0, boundaries: [], logicalOperations: [] }`, and revision shall be zero if and only if both ordered ledgers are empty.
573
608
  The validator shall capture the complete supplied ledger once as detached frozen JSON and enforce every closed member, identity, ordering, receipt, cross-reference, correction-budget, and logical-operation invariant represented above.
574
609
  One optional host-owned UUID `cohortId` shall identify every member of exactly one contiguous, distinct-role, all-`unchanged` physical cohort in declared role order; every member shall share attempt, playbook, runtime-session, turn, canonical-worktree, and baseline identity and shall be uniformly started or uniformly complete, complete members shall carry the identical after observation and receipt, and the id shall never be reused by another group.
@@ -630,8 +665,8 @@ Boss text supplied by the entry event is not a required startup option unless So
630
665
  A generated entry guard that requires the text already in context is likewise
631
666
  not independent Source evidence; it is a producer defect when the entry action
632
667
  has yet to copy the event's text. Do not compensate with a required option or
633
- invented startup task. `entryEvent.contextField` supplies failure-retry text;
634
- it does not populate fresh entry context before FSM guards execute.
668
+ invented startup task. Entry metadata does not populate fresh context before
669
+ FSM guards execute. Interrupted-step checkpoints retain accepted input.
635
670
  A source-appropriate optional seed may remain, and genuine required bootstrap catalogs or other options shall not be erased or filled with invented defaults.
636
671
 
637
672
  Concrete player binding and prompt identity are host policy and shall not enter `PlaybookRuntimeOptions`, machine input, or the emitted artifact.
@@ -642,6 +677,7 @@ The host shall derive `promptIdentity` from the current effective model when pre
642
677
 
643
678
  ```typescript
644
679
  interface PlaybookPorts {
680
+ recordStep?(step: PlaybookStepRecord, position?: PlaybookRuntimeSnapshot): Promise<void>;
645
681
  callPlayer(
646
682
  roleId: string,
647
683
  prompt: string,
@@ -1239,7 +1275,7 @@ undeclared extra key, so hidden adjudication cannot replace, paraphrase, or
1239
1275
  decorate prose Boss already saw.
1240
1276
  For an artifact-schema-3 governed delegated-player call, the shared engine
1241
1277
  shall instead use one semantic reconciler for both the default linked runtime
1242
- and any bespoke linked runtime that adopts schema `3`.
1278
+ and any linked machinery of an artifact's own that adopts schema `3`.
1243
1279
  That reconciler shall retain the validated player's exact non-empty
1244
1280
  `finalText` as opaque presentation evidence, let the hidden adjudicator read
1245
1281
  it only as semantic evidence, and require the adjudicator's detached
@@ -1257,8 +1293,8 @@ authored placeholder or guidance, with every presentation-, effect-, or
1257
1293
  runtime-owned field named as runtime-supplied to omit — so the judge is never asked for a
1258
1294
  field it does not own; the artifact's description text stays unchanged.
1259
1295
  The shared engine shall export that rendering as `renderGovernedOutcomeContract`
1260
- on `@sublang/playbook/xstate-runtime`, and a bespoke linked runtime shall
1261
- render its judge prompt through it rather than restate the contract.
1296
+ on `@sublang/playbook/xstate-runtime`, and linked machinery of an artifact's
1297
+ own shall render its judge prompt through it rather than restate the contract.
1262
1298
  The reconciler shall construct the complete actor output rather than accept a
1263
1299
  cross-authority object from the judge: every presentation-owned payload field
1264
1300
  shall receive the canonical `finalText.trim()` value; every effect-owned
@@ -1271,6 +1307,13 @@ field.
1271
1307
  It shall reject an absent required field, an undeclared or extra field, a
1272
1308
  field supplied by the wrong authority, an invalid value, or any mutually
1273
1309
  inconsistent candidate before FSM delivery.
1310
+ When no outcome matches, a governed judge may instead return exactly
1311
+ `{ blocked: <nonempty explanation> }`.
1312
+ The shared reconciler retains this candidate and returns `no-matching-outcome`
1313
+ with its explanation as a `runtime-defect` cause; it authorizes neither a
1314
+ corrective judge nor an FSM outcome.
1315
+ Both shared and bespoke judge prompts shall state this alternative explicitly
1316
+ instead of forcing a match when the declared results are incomplete.
1274
1317
 
1275
1318
  For a non-deferred candidate, reconciliation shall require a complete durable
1276
1319
  physical receipt, or the complete cumulative logical receipt of a deferred
@@ -1344,7 +1387,8 @@ Two default adjudication strategies, in selection order:
1344
1387
  answer keyed to exactly one of the declared guards: a nongoverned player uses
1345
1388
  `{ guard, …structuralPayloadFields }`, while a governed schema-3 player uses
1346
1389
  `{ guard, …semanticOwnedPayloadFields }` and explicitly forbids every other
1347
- payload field. Both forms exclude the runtime-injected direct-Captain
1390
+ payload field. The governed form also permits the blocked report above when no outcome matches.
1391
+ Both forms exclude the runtime-injected direct-Captain
1348
1392
  `question` and `response` fields above. The prompt shall identify hidden control work,
1349
1393
  prohibit tool use, file inspection, and external evidence, direct the judge
1350
1394
  to decide only from the supplied actor output and declared outcomes, and
@@ -1875,7 +1919,8 @@ rehydrate it in a later process (DR-014). A runtime that implements either
1875
1919
  member shall implement both. When generated for a runtime whose host needs
1876
1920
  durability, the pair shall behave as follows.
1877
1921
 
1878
- `exportSnapshot()` shall return `undefined` unless the runtime is at a safe
1922
+ A shared runtime may also receive the internal step-start snapshot for its current invocation or `exportSnapshot({child})` for a bridge-owned child during startup; these capture stopped positions without execution.
1923
+ Ordinary `exportSnapshot()` shall return `undefined` unless the runtime is at a safe
1879
1924
  capture point: initialized, not disposing or disposed, no active
1880
1925
  `handleBossInput`/`resumePlaybookCall` boundary, and the root actor at a
1881
1926
  quiescent state with actor status `active`.
@@ -1960,9 +2005,11 @@ A linked runtime may implement the optional adoption capability of
1960
2005
  initialization path distinct from `init` and same-engagement `restore`.
1961
2006
  Adoption may bind a retained generation to a fresh valid `PlaybookSession`
1962
2007
  identity. Every runtime the shared `createXStatePlaybookRuntime` factory
1963
- constructs implements `adopt`, regardless of whether the artifact supplies
1964
- retained-generation classification metadata; a bespoke runtime may omit it,
1965
- and hosts feature-detect the capability by member presence.
2008
+ constructs for a flat machine implements `adopt`, regardless of whether the
2009
+ artifact supplies retained-generation classification metadata; one it
2010
+ constructs for a machine that declares a parallel state omits it, as linked
2011
+ machinery of an artifact's own may, and hosts feature-detect the capability
2012
+ by member presence.
1966
2013
 
1967
2014
  Before actor construction or any player-session-store, port, trace, status,
1968
2015
  or telemetry effect, `adopt` shall validate and detach the target session, the
@@ -2053,9 +2100,9 @@ and detached from that declaration. Absence means the runtime contributes no
2053
2100
  retained generation; presence supplies only terminal classification metadata
2054
2101
  and does not itself supply the adoption operation.
2055
2102
  Every runtime the shared `createXStatePlaybookRuntime` factory constructs from
2056
- a supplied `unfinishedFinalStateIds` spec member shall expose the marker; a
2057
- bespoke runtime opts into classification only by implementing the public member
2058
- itself.
2103
+ a supplied `unfinishedFinalStateIds` spec member shall expose the marker;
2104
+ linked machinery of an artifact's own opts into classification only by
2105
+ implementing the public member itself.
2059
2106
 
2060
2107
  ## Control surface (optional)
2061
2108
 
@@ -2082,6 +2129,14 @@ interface PlaybookControlAction {
2082
2129
  reason?: PlaybookControlActionReason; // why it is not `ready`
2083
2130
  }
2084
2131
 
2132
+ interface PlaybookRecoveryOffer {
2133
+ prompt: string;
2134
+ description?: string;
2135
+ preparation?: string;
2136
+ evidence?: JsonValue;
2137
+ continuation: { kind: 'reply' } | { kind: 'runtime'; actionId: string };
2138
+ }
2139
+
2085
2140
  interface PlaybookControlView {
2086
2141
  state: PlaybookState;
2087
2142
  stateDescription?: string; // runtime-published meaning of the current state
@@ -2089,12 +2144,13 @@ interface PlaybookControlView {
2089
2144
  pendingQuestions: readonly PlaybookPendingBossQuestion[];
2090
2145
  lastError?: NormalizedError;
2091
2146
  actions: readonly PlaybookControlAction[];
2147
+ recovery?: PlaybookRecoveryOffer;
2092
2148
  }
2093
2149
 
2094
2150
  type PlaybookControlReceipt =
2095
2151
  | { disposition: 'rejected'; reason: string } // before any effect
2096
2152
  | { disposition: 'executed'; run: PlaybookRunResult }
2097
- | { disposition: 'failed'; error: NormalizedError }; // effects may exist
2153
+ | { disposition: 'failed'; error: NormalizedError; run?: PlaybookRunResult }; // effects may exist
2098
2154
 
2099
2155
  // Optional PlaybookRuntime members — both or neither:
2100
2156
  describe?(): PlaybookControlView;
@@ -2115,6 +2171,7 @@ throw. The view carries the current normalized state descriptor, the state
2115
2171
  description defined below, the authored context projection defined below, the
2116
2172
  pending Boss questions with their stable ids, the last recorded error in
2117
2173
  normalized form, and the currently valid actions.
2174
+ The shared runtime owns the optional recovery context and invocation checkpoint; linked artifacts need no recovery-specific source, event, or actor.
2118
2175
 
2119
2176
  `stateDescription` is the runtime's own Boss-facing statement of what its
2120
2177
  current state means, taken from the same source state descriptions the action
@@ -2167,25 +2224,17 @@ parked-session snapshot uses (actor status `active`, quiescent, no pending
2167
2224
  nested call); anywhere else `actions` is empty while the rest of the view
2168
2225
  still describes the state.
2169
2226
  While effect-possible outcome evidence remains unresolved, the view shall omit its pending Boss questions and state description and shall replace every ordinary action with exactly `reconcile:unresolved-effect` labeled `Retry unresolved effect reconciliation` and `abandon:unresolved-effect` labeled `Abandon unresolved workflow attempt`.
2170
- Otherwise two ordinary families exist, labeled from source state descriptions:
2171
-
2172
- - **Failure-state retry** — while the singular state id is the recoverable
2173
- failure state and the live snapshot accepts the retry event sourced below,
2174
- the runtime shall advertise `retry:<EVENT_TYPE>` replaying exactly that
2175
- event. Where the emitted module's entry-event declaration names the FSM
2176
- context member the machine's entry action copies the exact Boss text into
2177
- (DR-034), the retry event is that deterministic entry event built from the
2178
- live snapshot's member — excluded when the member is absent, not a string,
2179
- or blank, and never falling back to the record. Where it names no member,
2180
- the retry event is the recorded last classified event (the event a public
2181
- Boss boundary sent that drove the run into `failed`, kept with its recorded
2182
- payload), and there is none while the runtime holds none. The member is
2183
- declared, never inferred from a context member that happens to match the
2184
- entry event's text field.
2227
+ A valid invocation checkpoint is the only source of a retry: it retries only the stopped invocation after its effect checks, using `retry:<EVENT_TYPE>` for the same entry target or `retry:step` otherwise.
2228
+ Saved-result assessment (`retry:adjudication`) never repeats the player and is not offered for an output the machine has already received (checkpoint `delivered: true`), and verified read-only restoration (`retry:restored-step`) records an exact baseline check before replay.
2229
+ These retries have standing `ready`; an unsafe checkpoint grants no replay. Saved-result assessment withholds ordinary state jumps. Reconciliation and abandonment remain available whenever effects are unresolved, even alongside an eligible assessment or restoration.
2230
+ Ordinary retry and jump actions use source state descriptions and require assessment and unresolved-effect fences to be absent:
2231
+
2232
+ - **Failure-state retry** — a valid invocation checkpoint retries that step only; a completed saved result is consumed without executing the operation again. No checkpoint means no retry. Legacy `entryEvent.contextField` metadata is ignored.
2185
2233
  - **Jump entries** — for each registered resumable state id whose
2186
2234
  explicit-state-jump event (`BOSS_INTERRUPT` with that `targetId`, optional
2187
2235
  textual fields omitted) the live snapshot accepts, guards included, the
2188
- runtime shall advertise `jump:<stateId>`.
2236
+ runtime shall advertise `jump:<stateId>`, except that a jump out of a governed
2237
+ failure must also pass the full failed-attempt effect check above.
2189
2238
 
2190
2239
  A candidate whose event requires a payload the runtime cannot source from
2191
2240
  recorded state shall be excluded from `actions` — `apply` never invents free
@@ -2195,9 +2244,8 @@ back to a target id or to the replayed event type, because a controller host
2195
2244
  names an executed or refused action by its label and never by its id, so an
2196
2245
  identifier used as a label defeats that substitution. A jump whose target
2197
2246
  publishes no description is therefore not advertised — borrowing another
2198
- state's description would name the wrong state — and a retry falls back from
2199
- its target's description to its own source state's, and is not advertised when
2200
- neither exists.
2247
+ state's description would name the wrong state — and a retry requires the
2248
+ interrupted invocation's source description.
2201
2249
 
2202
2250
  `apply({ actionId, key, signal })` shall revalidate the action against the
2203
2251
  live state and settle `{ disposition: 'rejected', reason }` with no effect
@@ -2213,7 +2261,7 @@ pair, and may execute once the action is advertised — and a key whose call
2213
2261
  threw before reaching acceptance (lifecycle misuse, invalid input, a
2214
2262
  pre-acceptance abort, a rejected start-boundary sink) likewise records
2215
2263
  nothing, so a later call with that key may execute.
2216
- Executing an ordinary retry or jump sends the validated event through the same actor drive as `handleBossInput` — state
2264
+ Executing a jump sends the validated event through the same actor drive as `handleBossInput` — state
2217
2265
  transitions, player/judge boundaries, statuses, and traces flow unchanged —
2218
2266
  and settles `executed` with the projected run result, or `failed` with the
2219
2267
  normalized error when the run settles in the failure state, aborts, or a
@@ -2246,12 +2294,18 @@ signal's own abort reason, in which case it evidences the cancellation and is
2246
2294
  dropped, not latched
2247
2295
  ([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
2248
2296
 
2249
- The recorded receipts and the recorded last classified event are
2250
- process-local: the durable runtime snapshot persists neither. A restored
2251
- runtime therefore advertises the retry of a declared entry-event source
2252
- immediately — that payload rides the persisted machine snapshot — while a
2253
- module declaring no source advertises a retry again only after its next
2254
- classified event.
2297
+ Recorded control receipts are process-local. Invocation checkpoints retain the original input and accepted output across restoration; a restored runtime offers only the currently valid step controls.
2298
+
2299
+ The optional `PlaybookPorts.recordStep(step, position?)` lets a durable host save ordinary work without understanding machine internals.
2300
+ The shared runtime records player, direct-Captain and script starts before execution and their actor results before advancing.
2301
+ The runtime owns the complete stopped position, including accepted Boss input; the host joins nested frames and atomically stores starts and results; retention changes wait for settlement.
2302
+ An invocation inside a parallel region records its start and result without a single-invocation recovery position, including when only its last region is resuming.
2303
+ Sequential invocations after the join use ordinary checkpoints.
2304
+ A custom runtime that cannot provide a position remains runnable, but a crash without a supported position returns to Captain with recorded work preserved.
2305
+ Automatic recovery requires a failed or quiescent outcome produced by an operation in the same turn; resuming or adopting an older stop starts no preparation.
2306
+ The store identifies the step that owns a saved position, so a consumed result cannot be attached to a later stopped snapshot.
2307
+ Only internal step-start capture creates a synthetic failed position; public snapshot capture returns the actual stopped state.
2308
+ Opening or reporting an interrupted run executes no work; Boss chooses the next action after checking outside effects and stopping any surviving worker.
2255
2309
 
2256
2310
  ## Abort
2257
2311
 
@@ -2360,6 +2414,10 @@ The runtime shall emit, at minimum:
2360
2414
  semantics matter to Boss — e.g., `respondToReview`, `failed`). The
2361
2415
  default is to emit on every transition and let the host filter; hosts
2362
2416
  may bind a stricter rule.
2417
+ Mark both a player-question status and its waiting marker with status data
2418
+ `{ kind: "boss-question" }`; the session Captain replaces those statuses with
2419
+ its own clear reply, using the complete pending question from the control view.
2420
+ Keep the original question in state and telemetry, and deliver Boss input unchanged.
2363
2421
  - One `emitTelemetry` per state transition under a namespaced topic
2364
2422
  (recommended `playbook.fsm.state`), with structured `from`, `to`, `event`,
2365
2423
  `previousState`, and `state` fields. Descriptors carry the JSON-safe XState
@@ -2387,14 +2445,19 @@ Every linked artifact shall emit an `unfinishedFinalStateIds` set beside its res
2387
2445
  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.
2388
2446
  The linker shall not infer the set from a state description, opaque output, or procedure prose.
2389
2447
  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.
2390
- 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.
2391
- For an FSM that declares no `type: 'parallel'` state — necessarily flat
2392
- under [gears2fsm.md](gears2fsm.md)'s one-state-per-item mapping — it shall
2448
+ For a factory-backed artifact the set is a `spec` member; linked machinery of an artifact's own shall retain equivalent linked metadata, and the artifact declaration is not itself the public runtime retention marker or an adoption capability.
2449
+ For every FSM [gears2fsm.md](gears2fsm.md) produces — flat under its
2450
+ one-state-per-item mapping, or carrying root parallel groups of its
2451
+ [Parallel groups](gears2fsm.md#parallel-groups) shape — the linker shall
2393
2452
  emit the thin shared-factory module defined below.
2394
- For an FSM that declares a parallel state, it shall emit bespoke linked
2395
- machinery satisfying this document's runtime contract and shall not invoke
2396
- `createXStatePlaybookRuntime`, whose supported domain is flat single-region
2397
- FSMs under [DR-019](../specs/decisions/019-shared-linked-runtime-factory.md).
2453
+ The shared factory interprets such a parallel group through its parallel
2454
+ profile under [DR-067](../specs/decisions/067-parallel-proposals-through-the-shared-factory.md), deriving the regions, their leaves, and the
2455
+ cohort of working leaves from the FSM and its exported `concurrentRoleSets`,
2456
+ so the emitted module declares nothing further for it; bespoke linked
2457
+ machinery is no longer the prescribed output for any such FSM.
2458
+ Linked machinery of an artifact's own that satisfies this document's runtime
2459
+ contract remains a valid implementation for an artifact outside this package
2460
+ and declares the `bespoke` registry profile below, so hosts keep accepting it.
2398
2461
  The FSM-interpreter machinery — actor wiring, boundary tracing, Boss-event
2399
2462
  mapping, adjudication, script execution, nested-playbook bridging, session
2400
2463
  lifecycle, abort handling, and the optional parked-session snapshot and
@@ -2510,8 +2573,9 @@ The thin emitted module:
2510
2573
  error.
2511
2574
  `NO_ACTION` and `BOSS_REPLY` are runtime-owned event types the factory
2512
2575
  supplies itself — `NO_ACTION` as exactly `{ type: 'NO_ACTION' }`, and
2513
- `BOSS_REPLY` as an optional judge-selected `questionId` plus the exact-text
2514
- `answer` the runtime attaches. `bossEvents` shall carry no entry for either
2576
+ `BOSS_REPLY` as a judge-selected `questionId` — optional while one question
2577
+ is pending and required, from the pending ids, while several are — plus the
2578
+ exact-text `answer` the runtime attaches. `bossEvents` shall carry no entry for either
2515
2579
  type; supplying one is a construction error, so a linker that judges a
2516
2580
  runtime-owned arm to have lost payload detail under erasure shall report
2517
2581
  that gap rather than emit the entry.
@@ -2553,8 +2617,11 @@ The thin emitted module:
2553
2617
  host-construction boundary's setup diagnostic.
2554
2618
  - Exposes, under an `_internal` export, the pure helpers verification
2555
2619
  needs — at least the prompt composers its own machine uses, which may
2556
- re-export the shared defaults when the spec does not override composition —
2557
- so compilation-correctness tests can exercise composition without a host.
2620
+ re-export the shared defaults when the spec does not override composition,
2621
+ and the `VERBATIM_PAYLOAD_FIELDS` and `UNFINISHED_FINAL_STATE_IDS` sets the
2622
+ spec declares, which the linked-ownership check reads back — so
2623
+ compilation-correctness tests can exercise composition and ownership
2624
+ without a host.
2558
2625
  A playbook that calls players exposes `composePlayerPrompt`; a playbook
2559
2626
  whose states make direct-Captain calls exposes `composeCaptainPrompt`. A
2560
2627
  controller playbook that calls no players exposes no player composer: