@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
@@ -8,8 +8,8 @@
8
8
  // callerInput; pending player questions retain BOSS_REPLY
9
9
  // Adjudication: LLM judge per state; coderOutput and reviewerOutput are
10
10
  // carried verbatim
11
- // Compat: artifact schema 2 / runtime ABI 1
12
- import { RUNTIME_ABI, createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
11
+ // Compat: artifact schema 3 / runtime ABI 1
12
+ import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
13
13
  import { reviewMachine, } from './review.fsm.js';
14
14
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
15
15
  const CONTINUATION_PREAMBLE = 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
@@ -17,6 +17,7 @@ const VERBATIM_PAYLOAD_FIELDS = new Set([
17
17
  'reviewerOutput',
18
18
  'coderOutput',
19
19
  ]);
20
+ const UNFINISHED_FINAL_STATE_IDS = new Set();
20
21
  function snapshotReviewOptions(value) {
21
22
  const captured = snapshotJsonValue(value, 'REVIEW runtime options');
22
23
  if (captured === null ||
@@ -68,12 +69,24 @@ function composePlayerPrompt(input, promptIdentity) {
68
69
  export const _internal = {
69
70
  composePlayerPrompt,
70
71
  VERBATIM_PAYLOAD_FIELDS,
72
+ UNFINISHED_FINAL_STATE_IDS,
71
73
  };
72
74
  const runtimeSpec = {
73
75
  label: 'REVIEW',
74
- compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
76
+ // DR-022 / slc/link.md: the declaration carries the value current at link
77
+ // time — a literal, never the loading engine's RUNTIME_ABI self-report,
78
+ // which would follow whatever engine loads the module and make the
79
+ // factory's skew check compare that engine with itself.
80
+ compat: { artifactSchema: 3, runtimeAbi: 1 },
75
81
  snapshotOptions: snapshotReviewOptions,
76
- entryEvent: { type: 'START_REVIEW', textField: 'callerInput' },
82
+ entryEvent: {
83
+ type: 'START_REVIEW',
84
+ textField: 'callerInput',
85
+ // `copyStartInput` copies the entry text here, so the failure-state
86
+ // retry reads it back from the persisted machine snapshot and survives
87
+ // a continued session (DR-034).
88
+ contextField: 'callerInput',
89
+ },
77
90
  roleStates: {
78
91
  reviewInitial: {
79
92
  role: 'reviewer',
@@ -92,9 +105,70 @@ const runtimeSpec = {
92
105
  label: 'REVIEW-4: Reviewer adjudicates an all-rejected Coder disposition.',
93
106
  },
94
107
  },
108
+ outcomeAuthority: {
109
+ governedPlayerStates: {
110
+ reviewInitial: {
111
+ hasFindings: {
112
+ fields: { reviewerOutput: 'presentation' },
113
+ repositoryDisposition: 'unchanged',
114
+ },
115
+ noFindings: {
116
+ fields: {},
117
+ repositoryDisposition: 'unchanged',
118
+ },
119
+ needsBossReply: {
120
+ fields: { question: 'presentation' },
121
+ repositoryDisposition: 'unchanged',
122
+ },
123
+ },
124
+ addressFindings: {
125
+ committed: {
126
+ fields: { coderOutput: 'presentation' },
127
+ repositoryDisposition: 'one-descendant-commit',
128
+ },
129
+ rejectedAll: {
130
+ fields: { coderOutput: 'presentation' },
131
+ repositoryDisposition: 'unchanged',
132
+ },
133
+ needsBossReply: {
134
+ fields: { question: 'presentation' },
135
+ repositoryDisposition: 'deferred',
136
+ },
137
+ },
138
+ reviewAfterCommit: {
139
+ hasFindings: {
140
+ fields: { reviewerOutput: 'presentation' },
141
+ repositoryDisposition: 'unchanged',
142
+ },
143
+ noFindings: {
144
+ fields: {},
145
+ repositoryDisposition: 'unchanged',
146
+ },
147
+ needsBossReply: {
148
+ fields: { question: 'presentation' },
149
+ repositoryDisposition: 'unchanged',
150
+ },
151
+ },
152
+ reviewAfterRebuttal: {
153
+ hasFindings: {
154
+ fields: { reviewerOutput: 'presentation' },
155
+ repositoryDisposition: 'unchanged',
156
+ },
157
+ noFindings: {
158
+ fields: {},
159
+ repositoryDisposition: 'unchanged',
160
+ },
161
+ needsBossReply: {
162
+ fields: { question: 'presentation' },
163
+ repositoryDisposition: 'unchanged',
164
+ },
165
+ },
166
+ },
167
+ },
95
168
  composePlayerPrompt: (input, promptIdentity) => composePlayerPrompt(input, promptIdentity),
96
169
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
97
170
  controlContextFields: [],
171
+ unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
98
172
  transitionEventFields: [
99
173
  'callerInput',
100
174
  'bossIntent',
@@ -8,21 +8,23 @@
8
8
  // callerInput; pending player questions retain BOSS_REPLY
9
9
  // Adjudication: LLM judge per state; coderOutput and reviewerOutput are
10
10
  // carried verbatim
11
- // Compat: artifact schema 2 / runtime ABI 1
11
+ // Compat: artifact schema 3 / runtime ABI 1
12
12
 
13
13
  import {
14
- RUNTIME_ABI,
15
14
  createXStatePlaybookRuntime,
16
15
  snapshotJsonValue,
17
16
  type PlaybookPlayerInput,
17
+ type XStatePlaybookRuntimeFactory,
18
+ type XStatePlaybookRuntimeConstruction,
18
19
  type XStatePromptIdentity,
19
- type XStatePlaybookRuntimeSpec,
20
+ type XStatePlaybookRuntimeSpecV3,
20
21
  } from '@sublang/playbook/xstate-runtime';
21
22
  import {
22
23
  reviewMachine,
23
24
  type PlayerInput,
24
25
  type ReviewInput,
25
26
  } from './review.fsm.js';
27
+ import type { PlaybookHostConstructionCapabilities } from '../code.playbook/playbook-captain.js';
26
28
  import type {
27
29
  CaptainCallOptions,
28
30
  CaptainResult,
@@ -76,6 +78,9 @@ export type {
76
78
  };
77
79
 
78
80
  export type ReviewPlaybookOptions = ReviewInput;
81
+ export type ReviewPlaybookHostCapabilities =
82
+ PlaybookHostConstructionCapabilities &
83
+ XStatePlaybookRuntimeConstruction<ReviewPlaybookOptions, object>['hostCapabilities'];
79
84
 
80
85
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
81
86
  const CONTINUATION_PREAMBLE =
@@ -85,6 +90,7 @@ const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
85
90
  'reviewerOutput',
86
91
  'coderOutput',
87
92
  ]);
93
+ const UNFINISHED_FINAL_STATE_IDS: ReadonlySet<string> = new Set();
88
94
 
89
95
  function snapshotReviewOptions(value: unknown): ReviewPlaybookOptions {
90
96
  const captured = snapshotJsonValue(value, 'REVIEW runtime options');
@@ -152,13 +158,25 @@ function composePlayerPrompt(
152
158
  export const _internal = {
153
159
  composePlayerPrompt,
154
160
  VERBATIM_PAYLOAD_FIELDS,
161
+ UNFINISHED_FINAL_STATE_IDS,
155
162
  };
156
163
 
157
164
  const runtimeSpec = {
158
165
  label: 'REVIEW',
159
- compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
166
+ // DR-022 / slc/link.md: the declaration carries the value current at link
167
+ // time — a literal, never the loading engine's RUNTIME_ABI self-report,
168
+ // which would follow whatever engine loads the module and make the
169
+ // factory's skew check compare that engine with itself.
170
+ compat: { artifactSchema: 3, runtimeAbi: 1 },
160
171
  snapshotOptions: snapshotReviewOptions,
161
- entryEvent: { type: 'START_REVIEW', textField: 'callerInput' },
172
+ entryEvent: {
173
+ type: 'START_REVIEW',
174
+ textField: 'callerInput',
175
+ // `copyStartInput` copies the entry text here, so the failure-state
176
+ // retry reads it back from the persisted machine snapshot and survives
177
+ // a continued session (DR-034).
178
+ contextField: 'callerInput',
179
+ },
162
180
  roleStates: {
163
181
  reviewInitial: {
164
182
  role: 'reviewer',
@@ -180,12 +198,73 @@ const runtimeSpec = {
180
198
  'REVIEW-4: Reviewer adjudicates an all-rejected Coder disposition.',
181
199
  },
182
200
  },
201
+ outcomeAuthority: {
202
+ governedPlayerStates: {
203
+ reviewInitial: {
204
+ hasFindings: {
205
+ fields: { reviewerOutput: 'presentation' },
206
+ repositoryDisposition: 'unchanged',
207
+ },
208
+ noFindings: {
209
+ fields: {},
210
+ repositoryDisposition: 'unchanged',
211
+ },
212
+ needsBossReply: {
213
+ fields: { question: 'presentation' },
214
+ repositoryDisposition: 'unchanged',
215
+ },
216
+ },
217
+ addressFindings: {
218
+ committed: {
219
+ fields: { coderOutput: 'presentation' },
220
+ repositoryDisposition: 'one-descendant-commit',
221
+ },
222
+ rejectedAll: {
223
+ fields: { coderOutput: 'presentation' },
224
+ repositoryDisposition: 'unchanged',
225
+ },
226
+ needsBossReply: {
227
+ fields: { question: 'presentation' },
228
+ repositoryDisposition: 'deferred',
229
+ },
230
+ },
231
+ reviewAfterCommit: {
232
+ hasFindings: {
233
+ fields: { reviewerOutput: 'presentation' },
234
+ repositoryDisposition: 'unchanged',
235
+ },
236
+ noFindings: {
237
+ fields: {},
238
+ repositoryDisposition: 'unchanged',
239
+ },
240
+ needsBossReply: {
241
+ fields: { question: 'presentation' },
242
+ repositoryDisposition: 'unchanged',
243
+ },
244
+ },
245
+ reviewAfterRebuttal: {
246
+ hasFindings: {
247
+ fields: { reviewerOutput: 'presentation' },
248
+ repositoryDisposition: 'unchanged',
249
+ },
250
+ noFindings: {
251
+ fields: {},
252
+ repositoryDisposition: 'unchanged',
253
+ },
254
+ needsBossReply: {
255
+ fields: { question: 'presentation' },
256
+ repositoryDisposition: 'unchanged',
257
+ },
258
+ },
259
+ },
260
+ },
183
261
  composePlayerPrompt: (
184
262
  input: PlaybookPlayerInput,
185
263
  promptIdentity: XStatePromptIdentity,
186
264
  ) => composePlayerPrompt(input as PlayerInput, promptIdentity),
187
265
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
188
266
  controlContextFields: [],
267
+ unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
189
268
  transitionEventFields: [
190
269
  'callerInput',
191
270
  'bossIntent',
@@ -193,9 +272,17 @@ const runtimeSpec = {
193
272
  'answer',
194
273
  'questionId',
195
274
  ],
196
- } satisfies XStatePlaybookRuntimeSpec<ReviewPlaybookOptions>;
275
+ } satisfies XStatePlaybookRuntimeSpecV3<ReviewPlaybookOptions>;
197
276
 
198
- const createPlaybookRuntime: PlaybookRuntimeFactory<ReviewPlaybookOptions> =
199
- createXStatePlaybookRuntime(reviewMachine, runtimeSpec);
277
+ const createPlaybookRuntime: XStatePlaybookRuntimeFactory<
278
+ XStatePlaybookRuntimeConstruction<
279
+ ReviewPlaybookOptions,
280
+ ReviewPlaybookHostCapabilities
281
+ >,
282
+ 3
283
+ > = createXStatePlaybookRuntime<
284
+ ReviewPlaybookOptions,
285
+ ReviewPlaybookHostCapabilities
286
+ >(reviewMachine, runtimeSpec);
200
287
 
201
288
  export default createPlaybookRuntime;
@@ -1,4 +1,4 @@
1
- import { type PlaybookRuntime } from './review.playbook.js';
1
+ import { type ReviewPlaybookHostCapabilities, type PlaybookRuntime } from './review.playbook.js';
2
2
  export interface PlaybookSummaryPolicy {
3
3
  stateCountLabels: Readonly<Record<string, string>>;
4
4
  copyPasteGuardNames: readonly string[];
@@ -12,12 +12,19 @@ export interface ReviewPlaybookRegistryEntry {
12
12
  id: 'review';
13
13
  command: 'review';
14
14
  intent: string;
15
- artifactSchema: 2;
15
+ artifactSchema: 3;
16
+ runtimeProfile: {
17
+ readonly kind: 'shared-factory';
18
+ readonly compat: {
19
+ readonly artifactSchema: 3;
20
+ readonly runtimeAbi: number;
21
+ };
22
+ };
16
23
  requiredRoleIds: readonly ['coder', 'reviewer'];
17
24
  concurrentRoleSets: readonly [];
18
25
  summaryPolicy: PlaybookSummaryPolicy;
19
26
  validateOptions(optionSlice: unknown): ReviewOptions;
20
- createRuntime(options: ReviewOptions): PlaybookRuntime;
27
+ createRuntime(options: ReviewOptions, hostCapabilities: ReviewPlaybookHostCapabilities): PlaybookRuntime;
21
28
  }
22
29
  export declare const reviewStateCountLabels: {
23
30
  readonly reviewInitial: "review round";
@@ -48,13 +48,20 @@ export const reviewPlaybookRegistryEntry = {
48
48
  id: 'review',
49
49
  command: 'review',
50
50
  intent: 'review the latest commit until no material correctness or spec findings remain',
51
- artifactSchema: 2,
51
+ artifactSchema: 3,
52
+ runtimeProfile: Object.freeze({
53
+ kind: 'shared-factory',
54
+ compat: createPlaybookRuntime.compat,
55
+ }),
52
56
  requiredRoleIds: ['coder', 'reviewer'],
53
57
  concurrentRoleSets: [],
54
58
  summaryPolicy: reviewSummaryPolicy,
55
59
  validateOptions: validateReviewOptions,
56
- createRuntime(options) {
57
- return createPlaybookRuntime(options);
60
+ createRuntime(options, hostCapabilities) {
61
+ return createPlaybookRuntime({
62
+ configuredOptions: options,
63
+ hostCapabilities,
64
+ });
58
65
  },
59
66
  };
60
67
  export default reviewPlaybookRegistryEntry;
@@ -2,6 +2,7 @@
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
 
4
4
  import createPlaybookRuntime, {
5
+ type ReviewPlaybookHostCapabilities,
5
6
  type PlaybookRuntime,
6
7
  } from './review.playbook.js';
7
8
 
@@ -20,12 +21,22 @@ export interface ReviewPlaybookRegistryEntry {
20
21
  id: 'review';
21
22
  command: 'review';
22
23
  intent: string;
23
- artifactSchema: 2;
24
+ artifactSchema: 3;
25
+ runtimeProfile: {
26
+ readonly kind: 'shared-factory';
27
+ readonly compat: {
28
+ readonly artifactSchema: 3;
29
+ readonly runtimeAbi: number;
30
+ };
31
+ };
24
32
  requiredRoleIds: readonly ['coder', 'reviewer'];
25
33
  concurrentRoleSets: readonly [];
26
34
  summaryPolicy: PlaybookSummaryPolicy;
27
35
  validateOptions(optionSlice: unknown): ReviewOptions;
28
- createRuntime(options: ReviewOptions): PlaybookRuntime;
36
+ createRuntime(
37
+ options: ReviewOptions,
38
+ hostCapabilities: ReviewPlaybookHostCapabilities,
39
+ ): PlaybookRuntime;
29
40
  }
30
41
 
31
42
  export const reviewStateCountLabels = {
@@ -94,13 +105,20 @@ export const reviewPlaybookRegistryEntry: ReviewPlaybookRegistryEntry = {
94
105
  command: 'review',
95
106
  intent:
96
107
  'review the latest commit until no material correctness or spec findings remain',
97
- artifactSchema: 2,
108
+ artifactSchema: 3,
109
+ runtimeProfile: Object.freeze({
110
+ kind: 'shared-factory',
111
+ compat: createPlaybookRuntime.compat,
112
+ }),
98
113
  requiredRoleIds: ['coder', 'reviewer'],
99
114
  concurrentRoleSets: [],
100
115
  summaryPolicy: reviewSummaryPolicy,
101
116
  validateOptions: validateReviewOptions,
102
- createRuntime(options) {
103
- return createPlaybookRuntime(options);
117
+ createRuntime(options, hostCapabilities) {
118
+ return createPlaybookRuntime({
119
+ configuredOptions: options,
120
+ hostCapabilities,
121
+ });
104
122
  },
105
123
  };
106
124
 
package/slc/gears2fsm.md CHANGED
@@ -222,12 +222,13 @@ teardown event. The completion rule of
222
222
  output clause binds only where Source declares a terminal result, which a
223
223
  controller Source does not.
224
224
  The controller decision state's direct-Captain result contract discriminates
225
- the closed action set of DR-029. Its guard discriminants are a stable
226
- compiler contract, not names the compiler may invent — `respond`, `start`,
227
- `switch`, `dismiss`, `deliver`, and `runtime` — with each guard's required
228
- payload fields:
225
+ the closed action set of DR-029 as extended by DR-038. Its guard discriminants
226
+ are a stable compiler contract, not names the compiler may invent — `respond`, `resume`,
227
+ `start`, `switch`, `dismiss`, `deliver`, and `runtime` — with each guard's
228
+ required payload fields:
229
229
 
230
230
  - `respond` requires `text`;
231
+ - `resume` requires `playbookId` and carries no `input`;
231
232
  - `start` and `switch` each require `playbookId` and `input`;
232
233
  - `runtime` requires `actionId`;
233
234
  - `dismiss` and `deliver` require none — a `deliver` result in particular
@@ -244,8 +245,9 @@ Each state shall declare:
244
245
  - a stable `id` (for `#id` targeting and Boss interrupts);
245
246
  - an intuitive state key (the property name under `states: { ... }`);
246
247
  - a one-line `description` (for inspector tools and documentation);
247
- - JSON-safe `meta: { playbook: { stateId, description, role? } }` repeating
248
- its stable id and description so linked runtimes can discover active public
248
+ - JSON-safe `meta: { playbook: { stateId, description, role? } }` naming the
249
+ state's public playbook identity per the identity rule below and repeating
250
+ its description so linked runtimes can discover active public
249
251
  identities through `snapshot.getMeta()` without private XState nodes. A
250
252
  delegated-role state shall also carry the canonical lowercase source role id in
251
253
  `meta.playbook.role`; every other state shall omit `role`;
@@ -258,6 +260,8 @@ Each state shall declare:
258
260
  and no `role`.
259
261
 
260
262
  The source item ID shall live in `invoke.input.sourceItem`, not in a comment — this keeps the GEARS-to-state mapping machine-readable.
263
+
264
+ Outside a parallel group's regions, a state's `meta.playbook.stateId` shall equal its state key — the one identity a factory-backed linked runtime indexes by.
261
265
  A delegated state's `invoke.input.role` shall match the canonical lowercase id of its source item's named role.
262
266
  A direct Captain state shall not invent a `Captain` role binding.
263
267
 
@@ -650,7 +654,7 @@ Boss-reply suspension, because its hub already receives every Boss turn and a
650
654
  clarifying question to Boss is a `respond` selection over the closed action
651
655
  set. The rule below is therefore universal over workflow states and silent
652
656
  about that class; in particular, adding `needsBossReply` to the controller
653
- decision state would add a seventh outcome to a closed six-action contract
657
+ decision state would add an eighth outcome to a closed seven-action contract
654
658
  whose guard discriminants [Setup](#setup) fixes, and is nonconformant.
655
659
  There is no source-level opt-in annotation and no `needsBossReply` result metadata in GEARS output.
656
660
  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`.
@@ -748,6 +752,20 @@ an unhandled runtime error.
748
752
  Every machine shall declare at least one `type: 'final'` state (typically `done`) reachable on completion.
749
753
  A never-terminating machine is a defect: the runner has no completion signal.
750
754
 
755
+ At least one is a floor, not a ceiling. A final state's `description` is the
756
+ machine's published terminal meaning: a host that cannot read the machine's
757
+ output quotes that description to report what the run did. It shall therefore
758
+ be true of every arm that enters the state and of no other terminal outcome.
759
+ Where Source declares more than one terminal outcome — an approval that
760
+ completes the workflow and a failure the workflow reports to its caller
761
+ instead of parking — each outcome shall get its own `type: 'final'` state
762
+ whose description names it. Routing an approval arm and a failure, abort, or
763
+ invalid-result arm into one final state is a defect of the same kind as a
764
+ wrong result field, because the quoting host cannot detect the difference.
765
+ This constrains only published meaning: the declared machine `output` still
766
+ derives its status and fields from typed context, so a caller that does read
767
+ the output is unaffected.
768
+
751
769
  Where Source declares a JSON-safe terminal result, the setup types shall
752
770
  declare that output and the root machine shall derive it from typed context
753
771
  through XState's machine `output` function. A final-state transition alone does