@sublang/playbook 13.2.0 → 14.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 (36) hide show
  1. package/README.md +5 -5
  2. package/docs/configuration.md +14 -9
  3. package/docs/embedding.md +20 -1
  4. package/package.json +6 -1
  5. package/reference/sdlc/code.playbook/bin/launch-config.js +88 -5
  6. package/reference/sdlc/code.playbook/bin/playbook.js +4 -0
  7. package/reference/sdlc/code.playbook/bin/repository-effects.js +11 -3
  8. package/reference/sdlc/code.playbook/bin/run.js +4 -0
  9. package/reference/sdlc/code.playbook/bin/session-host.js +13 -3
  10. package/reference/sdlc/code.playbook/bin/session-store.js +2 -2
  11. package/reference/sdlc/code.playbook/playbook-captain.d.ts +26 -1
  12. package/reference/sdlc/code.playbook/playbook-captain.js +233 -8
  13. package/reference/sdlc/code.playbook/playbook-captain.ts +298 -8
  14. package/reference/sdlc/code.playbook/playbook.config.template.yaml +5 -6
  15. package/reference/sdlc/code.playbook/session-host.d.ts +26 -0
  16. package/reference/sdlc/dev.playbook/dev.playbook.d.ts +3 -10
  17. package/reference/sdlc/dev.playbook/dev.playbook.js +5 -5
  18. package/reference/sdlc/dev.playbook/dev.playbook.ts +7 -7
  19. package/reference/sdlc/pr.md +17 -4
  20. package/reference/sdlc/pr.playbook/pr.fsm.d.ts +17 -3
  21. package/reference/sdlc/pr.playbook/pr.fsm.js +49 -18
  22. package/reference/sdlc/pr.playbook/pr.fsm.ts +63 -20
  23. package/reference/sdlc/pr.playbook/pr.gears.md +20 -7
  24. package/reference/sdlc/pr.playbook/pr.playbook.js +2 -1
  25. package/reference/sdlc/pr.playbook/pr.playbook.ts +2 -1
  26. package/slc/experiments/fsm-scaffold-guidance.md +31 -0
  27. package/slc/gears2fsm.md +145 -58
  28. package/slc/link.md +158 -7
  29. package/slc/materialize-link.mjs +460 -0
  30. package/slc/optimize.md +46 -6
  31. package/slc/scaffold-fsm.mjs +225 -0
  32. package/slc/slc.pin-inputs.json +13 -0
  33. package/slc/text2gears.md +38 -3
  34. package/slc/workflow-contracts.json +371 -0
  35. package/src/xstate-playbook-runtime.js +35 -1
  36. package/src/xstate-playbook-runtime.ts +54 -1
package/slc/gears2fsm.md CHANGED
@@ -54,8 +54,10 @@ inference. Helpers that construct transition arrays shall preserve guard,
54
54
  action, and target literals with `as const`, `satisfies`, or typed action/guard
55
55
  functions rather than widening registered names to plain `string`.
56
56
 
57
- The artifact shall not import a runner or bake in concrete actor
58
- implementations. Each actor placeholder shall fail explicitly (for example,
57
+ The artifact shall not bind or construct a runner or bake in concrete actor
58
+ implementations. A named stateless public boundary validator is permitted;
59
+ importing it shall not construct a runtime, bind host capabilities, or call ports.
60
+ Each actor placeholder shall fail explicitly (for example,
59
61
  throw `'captain actor must be provided by the runner'`).
60
62
  Where the Source artifact begins with an SPDX comment block, the generated
61
63
  artifact shall preserve its license and copyright text before the imports
@@ -83,7 +85,15 @@ comment delimiters into a TypeScript target.
83
85
  - `stateId`: the stable id of the invoking working leaf;
84
86
  - `sourceItem`: the GEARS item ID this state realizes;
85
87
  - `command`: the script item's blockquote text, verbatim after Markdown
86
- unescaping;
88
+ unescaping, with every runtime-value placeholder it carries bound from typed
89
+ machine context — a script has no prose contract to carry the field
90
+ separately, and the provided actor executes `command` verbatim.
91
+ A bound value is data, never syntax: the placeholder occupies a
92
+ single-quoted shell word, and binding replaces that whole word — quotes
93
+ included — with the value encoded as its own single-quoted literal, each
94
+ embedded `'` written `'\''`. Interpolating a value into double-quoted or
95
+ unquoted command text is malformed, because a retained value is reported
96
+ text that would otherwise expand or run as shell syntax;
87
97
  - `result`: a record whose keys are the item's two declared guard names, first
88
98
  the zero-exit guard, then the nonzero-exit guard.
89
99
 
@@ -119,19 +129,28 @@ Nested-playbook output, completed-result evidence, plans, context, and machine
119
129
  output shall use that readonly type rather than a mutable array/record
120
130
  near-duplicate. The linker shall not cast or copy around a variance mismatch.
121
131
 
122
- Every runtime-value placeholder established by Source in a direct-Captain or
123
- delegated-player prompt shall be backed by a typed actor-input field populated
124
- from typed machine context, so the linker can substitute it with the exact
125
- runtime value. Angle-bracketed metavariables quoted inside domain instructions
126
- (for example the literal `<model>` in a commit-message format) remain ordinary
127
- prompt text and are not runtime-value placeholders. For the generic Captain
128
- forms, wire `<boss-intent>` from `bossIntent`,
132
+ Except for a Source-declared local-role prompt-identity placeholder, every
133
+ runtime-value placeholder established by Source in a direct-Captain or
134
+ delegated-player prompt shall be backed by a typed ordinary actor-input field
135
+ populated from typed machine context, so the linker can substitute it with the
136
+ exact runtime value. Angle-bracketed metavariables quoted inside domain
137
+ instructions (for example the literal `<model>` in a commit-message format)
138
+ remain ordinary prompt text and are not runtime-value placeholders. For the
139
+ generic Captain forms, wire `<boss-intent>` from `bossIntent`,
129
140
  `<enabled-playbooks>` from `enabledPlaybooks`, `<remaining-plan>` from
130
141
  `remainingPlan`, and `<completed-call-results>` from
131
- `completedCallResults`. Other placeholders shall retain the semantic typed
132
- field established by Source (for example `<#>` from `irNumber`). Leaving a
133
- placeholder literal, replacing it with an empty default because its field was
134
- omitted, or making the linker recover it from untyped context is malformed.
142
+ `completedCallResults`. Other non-identity placeholders shall retain the
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.
147
+ 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
+ When Source declares a placeholder as the current identity of a local acting
149
+ role, preserve the placeholder literal in the FSM prompt and do not add any
150
+ identity-value field to machine input, runtime options, machine context, or
151
+ actor input, required or optional. The linker shall resolve the placeholder at
152
+ prompt-composition time by calling the invocation-scoped
153
+ `promptIdentity(roleId)` lookup for the declared local role identified by Source.
135
154
  The sole blockquote placeholder of a dynamic nested-playbook item is instead
136
155
  the child `textContext` field specified in §Nested playbook calls.
137
156
 
@@ -261,14 +280,16 @@ Each state shall declare:
261
280
 
262
281
  The source item ID shall live in `invoke.input.sourceItem`, not in a comment — this keeps the GEARS-to-state mapping machine-readable.
263
282
 
283
+ Public `meta.playbook` state metadata belongs only to nodes declared under `states`; the machine root shall omit `meta.playbook`, while its XState `id`, description, and metadata outside that namespace remain unrestricted.
264
284
  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.
265
285
  A delegated state's `invoke.input.role` shall match the canonical lowercase id of its source item's named role.
266
286
  A direct Captain state shall not invent a `Captain` role binding.
267
287
 
268
- Every invoking working leaf — sequential or parallel, whatever its actor
269
- kind — shall carry the tag `playbook.busy`: the shared quiescence helper
270
- derives busyness strictly from active-state tags, so an untagged working leaf
271
- reads as quiescent while its call is still in flight.
288
+ Every direct-Captain, delegated-player, or script working leaf — sequential
289
+ or parallel — shall carry the tag `playbook.busy`: the shared quiescence
290
+ helper derives busyness strictly from active-state tags, so an untagged
291
+ Captain, player, or script leaf reads as quiescent while its call is still in
292
+ flight.
272
293
 
273
294
  The machine's initial state shall be a quiescent idle hub (no `invoke`) — typically `ready` — that accepts the Boss entry events and carries the `playbook.parked` tag because it can return control to Boss.
274
295
  Captain- and player-invoking work begins only on a Boss-originated event, so
@@ -315,7 +336,12 @@ An item that prompts or relays to a named role shall map to exactly one `player`
315
336
  A nested-call item shall map to exactly one `playbook` invocation.
316
337
  A script item (`Captain shall run:`) shall map to
317
338
  exactly one `script` invocation whose `input.command` carries the blockquote
318
- verbatim and whose `result` preserves the item's two guards in declared order.
339
+ with its runtime-value placeholders substituted, and whose `result` preserves
340
+ the item's two guards in declared order.
341
+ Where the substituted value is absent, the compiled input shall keep the
342
+ placeholder literal rather than an empty default, so the command compares
343
+ against text no output can equal and the step refuses instead of acting on an
344
+ unbound target.
319
345
  The compiler shall not infer one actor kind from a
320
346
  runtime player name or encode Captain as a player.
321
347
  A script state is not agent-invoking: the compiler shall not add
@@ -369,6 +395,28 @@ the sibling invocations automatically.
369
395
 
370
396
  ## Nested playbook calls
371
397
 
398
+ The independently packaged [workflow contracts](workflow-contracts.json) describe
399
+ only the public outputs of builtin dependencies from `@sublang/playbook`.
400
+ This pipeline adopts the catalog's `literalTargetBindings` as its default
401
+ nested-target namespace. An explicit Source or supplied compiler-dependency
402
+ binding overrides a default and requires the replacement's own public interface.
403
+ Runtime hosts shall honor the compiled dependency bindings as external ABIs.
404
+ When the call target is bound to one of those packaged builtins, read its exact
405
+ output interface before compiling a Source-authored acceptance or relay
406
+ predicate; do not infer field names from the desired meaning or inspect the
407
+ callee implementation. A local basename or a custom target reusing an id does
408
+ not establish that dependency binding.
409
+ The shared bridge correlates the invocation and its supplied input scope;
410
+ the builtin REVIEW result attests to that scope with `noUnsettledFindings: true`
411
+ and `evaluatedRevision`. Do not invent `reviewedCommit` or require the final
412
+ evaluated revision to equal the caller's earlier commit, since REVIEW may make
413
+ its own fixes. Retain every Source-authored predicate on the declared fields.
414
+ If an output-dependent call needs an external interface that is unavailable or
415
+ inconsistent with the declared dependency, report `BLOCKED` with that missing
416
+ compiler input rather than inventing fields or asking the source author to
417
+ repair sufficient domain behavior. Missing or contradictory authored behavior
418
+ still requires the host's source-clarification protocol.
419
+
372
420
  An item whose behavior is a literal or dynamic
373
421
  `Captain shall call playbook ...:` shall compile to a state that invokes a
374
422
  typed `playbook` actor, not the `captain` or `player` actor.
@@ -416,8 +464,13 @@ parsing the `invoke.input` function's source. The evaluated `playbookId` and
416
464
  corresponding metadata property. Literal calls need not carry these dynamic
417
465
  metadata properties and retain their existing behavior.
418
466
 
419
- The call state shall carry tag `playbook.suspended` and shall route
420
- `invoke.onDone` from child output and `invoke.onError` from child failure.
467
+ The call state shall carry tag `playbook.suspended`.
468
+ The call state and every ancestor state, including the machine root, shall omit
469
+ tag `playbook.busy`.
470
+ An independently active sibling Captain, player, or script leaf may carry
471
+ `playbook.busy`.
472
+ The call state shall route `invoke.onDone` from child output and
473
+ `invoke.onError` from child failure.
421
474
  The child call shall remain state-scoped: leaving the call state stops the
422
475
  invoked actor and aborts the host call through XState's invocation signal
423
476
  [[2]].
@@ -447,9 +500,12 @@ result, the error action shall inspect whether its status was `aborted` or
447
500
  `error`; it shall not collapse both into an invented success/failure enum. The
448
501
  FSM may inspect that public structural data without importing the runner or
449
502
  constructing runtime call identities.
450
- Because a failure terminal reaches `onError`, `onDone` alone proves the child
451
- succeeded: a caller shall not decide success by inspecting the callee's output
452
- fields, and shall read those fields only where its own Source relays them.
503
+ `onDone` proves successful bridge delivery without a declared child failure;
504
+ it does not establish every caller-owned domain condition. The caller shall
505
+ enforce its own explicit Source-authored acceptance or relay predicates on
506
+ the delivered output before continuing, without inventing predicates from
507
+ callee implementation details or overriding the child's compiled terminal
508
+ kind with output fields.
453
509
  For a workflow that reassesses child results, use a typed JSON-safe record such
454
510
  as `{ playbookId, status: 'ok', output }` on `onDone` and
455
511
  `{ playbookId, status: 'aborted' | 'error', error }` on `onError`, with a
@@ -466,36 +522,47 @@ been created. On success, persist only `event.output`, which is the actual child
466
522
  machine output returned by the bridge, not a runtime call-result envelope.
467
523
  When that optional output is absent, omit the `output` property from the
468
524
  completed-result record rather than storing `undefined`.
469
- The outer trusted error is an actual `Error` instance and therefore is not a
470
- plain JSON object. The structural guard shall inspect its public `.result`
471
- property directly, then validate only that nested result before sanitizing it;
472
- it shall not require the outer error itself to pass a plain-object/JSON guard.
473
- Validation of that nested public result includes its status-specific required
474
- members and target identity: `playbookId` shall equal the current selected
475
- target, an `error` result shall carry a normalized error, and every optional
476
- member that is present shall have the public contract's declared shape. A
477
- look-alike such as `{ status: 'error' }` is malformed control data, not an
478
- authored child failure, and shall take the fallback `failed` arm without
479
- appending evidence. The guard shall not fabricate missing identity or error
480
- members merely because the status string happens to be recognized.
481
- The public result's declared optional `childSessionId` and `state` members are
482
- valid when their shapes satisfy the shared contract; validate and then discard
483
- them when building compact Captain evidence. They are not undeclared extras.
484
- Likewise, the public normalized error may carry its declared optional string
485
- `stack`; validate it and omit it from the compact `{ name, message }` evidence
486
- rather than rejecting an otherwise valid authored child result.
487
- Apply the public union exactly: an `aborted` or `error` result shall reject an
488
- `output` member; `childSessionId`, when present, shall be non-empty; `error`
489
- shall contain only non-empty `name`, string `message`, and optional string
490
- `stack`; and `state`, when present, shall validate every declared
491
- `PlaybookState` member and reject unknown or missing members. Treating an
492
- arbitrary JSON-safe object as a valid `state`, or checking only that these
493
- members have broad string/object types, is not complete public-result
494
- validation.
495
- In other words, the guard validates the complete public result it received,
496
- while the action retains only the current selected playbook id, status, and
497
- compact error. Do not implement evidence minimization by accepting only the
498
- three keys that survive that projection.
525
+ Recognize authored rejected child results with the existing named
526
+ `validatePlaybookCallResult` export from `@sublang/playbook/xstate-runtime`
527
+ and the type-only `PlaybookCallResult` from `@sublang/playbook/runtime`:
528
+
529
+ ```typescript
530
+ import { validatePlaybookCallResult } from '@sublang/playbook/xstate-runtime';
531
+ import type { PlaybookCallResult } from '@sublang/playbook/runtime';
532
+
533
+ export function authoredChildResult(error: unknown, expectedPlaybookId: string): PlaybookCallResult | undefined {
534
+ if (!(error instanceof Error)) return undefined;
535
+ try {
536
+ const result = validatePlaybookCallResult(
537
+ (error as Error & { result?: unknown }).result,
538
+ expectedPlaybookId,
539
+ );
540
+ return result.status !== 'ok' || result.terminal?.kind === 'failure' ? result : undefined;
541
+ } catch {
542
+ return undefined;
543
+ }
544
+ }
545
+ ```
546
+
547
+ Pass the actual selected target id from the same source-owned target used by
548
+ `invoke.input`; do not fabricate a request or child-session identity for this
549
+ check. The shared bridge owns invocation correlation. Its existing validator
550
+ owns the complete [public result union](link.md#playbookports-contract),
551
+ including terminal/state shapes, optional abort error, and JSON validity;
552
+ do not rewrite those structural checks or redeclare their public types.
553
+ An invalid result returns no authored outcome and takes the existing
554
+ control-error fallback. A validated successful child output still needs its
555
+ separate Source-owned acceptance predicate on `onDone`.
556
+ After preserving Source-authored success acceptance and recovery cases plus the
557
+ public control-error `onError` fallback, omit an `onDone` transition whose guard
558
+ cannot be reached from any legal predecessor/context after the prior ordered
559
+ `onDone` arms.
560
+ This rule does not require arbitrary finite enumeration, drop valid failure
561
+ behavior, or relax verifier obligations.
562
+ Validate the full public result before projecting only the permitted compact
563
+ evidence; evidence minimization shall not narrow the accepted public union.
564
+ The helper supplies no workflow route, child-domain predicate, runner or actor
565
+ implementation. Its ordinary package import is an explicit artifact dependency.
499
566
 
500
567
  Before entering a dynamic call, the machine shall reject an empty target and
501
568
  empty input text, any target equal to `selfPlaybookId`, and any target that
@@ -551,10 +618,13 @@ The artifact shall not bake them into machine input, options, or context; model
551
618
  Host-owned configuration such as an enabled-playbook catalog shall remain
552
619
  immutable machine input/context for the session. Boss events and actor outputs
553
620
  shall not carry, replace, append to, or otherwise overwrite that catalog.
554
- A placeholder whose value Source assigns to the host — for example the
555
- `<definition>` a phase host supplies to a compiled phase — is such host-owned
556
- configuration: a required machine `input` field carried into typed context and
557
- the acting actor's input, never a Boss-event or actor-output payload.
621
+ A non-identity placeholder whose value Source assigns to the host — for
622
+ example the `<definition>` a phase host supplies to a compiled phase — is such
623
+ host-owned configuration: a required machine `input` field carried into typed
624
+ context and the acting actor's input, never a Boss-event or actor-output
625
+ payload. A Source-declared local-role prompt-identity placeholder is the
626
+ explicit exception; it resolves from the invocation-scoped `promptIdentity`
627
+ lookup and is not persisted as host configuration.
558
628
  Every machine with a dynamic call shall receive its own registered or authored
559
629
  playbook id as immutable machine input/context named `selfPlaybookId`, and its
560
630
  dynamic-call guard shall reject that target. The leaf-level `stateId` name is
@@ -588,6 +658,8 @@ while an actual back-edge is rejected.
588
658
  A transition fires on an event — typically `onDone` (actor completed) [[4]].
589
659
  When multiple are possible, a synchronous guard [[5]] picks the path.
590
660
  Transitions shall persist relevant typed fields from `event.output` to context via `assign` [[6]] so downstream prompts can read them.
661
+ Where a Source outcome's availability condition is deterministically knowable from execution state, such as a prior Boss reply already received by the machine, the transition shall enforce that condition with authored guards or typed state before the result can be accepted; stating the condition only in a Results description, Judge prose, or prompt text is not enough.
662
+ The compiler shall update or reset those source-owned facts only at their actual Source lifecycle boundaries, and shall not attempt to mechanically decide semantic judgments that Source leaves to the acting agent.
591
663
  Transitions shall be self-driving when source items define the next obligation.
592
664
  Routing to an idle hub is for recovery, unrecoverable Boss input, or one-shot entry events — not the happy path.
593
665
 
@@ -647,6 +719,15 @@ contract shall require exactly `targetId: 'routing'` plus the fresh
647
719
  `BOSS_INTERRUPT` jumps into an **active** machine, pre-empting whichever state is running.
648
720
  **Boss entry events** start or resume from idle or recoverable states when Boss-supplied parameters can't be inferred from machine state alone.
649
721
  Entry events shall be typed alongside `BOSS_INTERRUPT` and populate context via a dedicated action.
722
+ Boss text supplied by an entry event shall not become a required machine-construction input unless Source independently requires that value before the first Boss turn; an optional source-appropriate seed may remain.
723
+ A generated required input annotation alone is not evidence of that independent Source bootstrap requirement.
724
+ Entry guards run before entry copy actions: validate supplied text from the
725
+ current event, not solely from the context that action will populate.
726
+ For an omitted event value, use an existing context seed only where Source
727
+ permits it; do not manufacture a required constructor value to satisfy a
728
+ 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.
650
731
  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.
651
732
  The two surfaces shall not be collapsed. `BOSS_INTERRUPT` always carries its
652
733
  target id and may additionally carry typed Boss-supplied fields such as an
@@ -698,6 +779,9 @@ A delegated `PlayerInput` shall produce the role asker with its canonical local
698
779
  Only
699
780
  `question` shall come from adjudicated actor output.
700
781
 
782
+ The canonical storage paths are `context.pendingBossQuestion` and `context.bossReply` for the scalar form, or `context.pendingBossQuestions[stateId]` and `context.bossReplies[stateId]` for the keyed form.
783
+ A private wrapper such as `context.continuation` shall not replace these fields directly on machine context.
784
+
701
785
  A machine with at most one active Captain or player task may use the scalar
702
786
  form:
703
787
 
@@ -740,6 +824,9 @@ A captain- or player-invoking state's `invoke.input` function shall carry the
740
824
  pending question and reply selected for that working leaf as singular
741
825
  `pendingBossQuestion` and `bossReply` fields, regardless of the scalar or keyed
742
826
  context representation, so prompt composition has one stable contract.
827
+ When Source requires earlier discussion or constraints on a later invocation, the compiler shall persist that source-owned history in serializable machine context before replacing or clearing the pending Q/A fields.
828
+ The later `invoke.input` shall relay that persisted context on both resumed and fresh calls that Source says need it, preserving Source-owned relevance and format without imposing all-history semantics on sources that do not require it.
829
+ Shared Boss-reply continuation carries only the latest Q/A pair, and a backend continuation token is not durable Source history.
743
830
  When both fields are present, the linked runtime shall compose the continuation preamble and labelled Q&A blocks per [link.md "Player prompt composition"](link.md#player-prompt-composition).
744
831
  The FSM artifact shall not bake the continuation preamble into the GEARS-derived `prompt` body.
745
832
 
package/slc/link.md CHANGED
@@ -24,6 +24,9 @@ Hosts are out of scope for this phase.
24
24
  Each host has an adapter that loads a `PlaybookRuntime` module and supplies the host's primitives as `PlaybookPorts`.
25
25
  The adapter shall speak only `PlaybookPorts` to the runtime and shall not leak host types back into it.
26
26
 
27
+ At construction, the shared factory validates linked metadata and the shared construction shape; the Captain host owns registry-manifest and live authority-envelope validation at its construction boundary.
28
+ Do not audit the bare shared factory as if it owned that Captain-host boundary or synthesize host capabilities in the emitted artifact.
29
+
27
30
  The link compiler shall not modify the FSM artifact and shall not re-derive Captain prompts, result keys, or guard semantics — those are fixed by the FSM.
28
31
 
29
32
  ## Formats
@@ -33,6 +36,138 @@ The link compiler shall not modify the FSM artifact and shall not re-derive Capt
33
36
  | source | fsm | .ts |
34
37
  | target | playbook | .ts |
35
38
 
39
+ ## Optional deterministic materialization
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
43
+ use the adjacent `materialize-link.mjs` tool to emit the thin module.
44
+ The complete definition below remains binding; the tool replaces repetitive
45
+ module generation, not semantic analysis or emitted conformance verification.
46
+ Read the actual FSM and supply every erased or authored contract exactly.
47
+ Do not use `flat-defaults` or `flat-quoted-relays` when another custom composer, classifier, required-field
48
+ extractor, session-derived input mapping, controller strategy, nested call,
49
+ parallel state, or compound state is needed.
50
+
51
+ Invoke the tool with the actual definition directory, source FSM, and declared
52
+ target; supply a JSON descriptor on standard input:
53
+
54
+ ```sh
55
+ node "<definition-directory>/materialize-link.mjs" --fsm "<source.fsm.ts>" --out "<target.playbook.ts>" <<'JSON'
56
+ {
57
+ "schema": "sublang.playbook.link.v1",
58
+ "profile": "flat-defaults",
59
+ "machineExport": "exampleMachine",
60
+ "label": "EXAMPLE",
61
+ "options": {},
62
+ "inputMapping": {},
63
+ "entryEvent": { "type": "BOSS_TASK", "textField": "bossIntent", "contextField": "bossIntent" },
64
+ "bossEvents": [],
65
+ "outcomeAuthority": {
66
+ "governedPlayerStates": {
67
+ "work": {
68
+ "done": { "fields": {}, "repositoryDisposition": "one-descendant-commit" },
69
+ "needsBossReply": { "fields": { "question": "presentation" }, "repositoryDisposition": "deferred" }
70
+ }
71
+ }
72
+ },
73
+ "placeholderFields": {},
74
+ "transitionEventFields": ["bossIntent", "answer", "questionId"],
75
+ "verbatimPayloadFields": [],
76
+ "resumableStateIds": ["work"],
77
+ "unfinishedFinalStateIds": [],
78
+ "controlContextFields": []
79
+ }
80
+ JSON
81
+ ```
82
+
83
+ This descriptor is an example shape, not default workflow semantics.
84
+ Select `flat-defaults` for the unchanged shared player composer, or
85
+ `flat-quoted-relays` when the source requires standalone `> <token>` relay lines.
86
+ The latter uses the same descriptor keys and `placeholderFields` mappings;
87
+ ordinary string tokens substitute literally, while standalone relay values
88
+ quote each nonempty line with `> ` and preserve LF/CRLF and blank lines.
89
+ An empty relay value omits its complete template line; missing or non-string
90
+ values retain the token. Inserted values are never substituted again.
91
+ The emitted composer preserves the installed shared fresh/resumed Q&A prefix
92
+ and does not modify the original input. Labelled relays, identity-specific
93
+ composition, structured renderers, and other custom strategies require
94
+ ordinary linking; do not select either profile for those requirements.
95
+ Every top-level member is required; unknown members are errors.
96
+ `options` maps each configured option to `{ "type": "string" | "number" |
97
+ "boolean", "required": true | false }`; `inputMapping` maps each FSM input
98
+ field to its supplying option name.
99
+ Option unions, closed values, range constraints, nested structures, and
100
+ session-derived input values are outside this profile; do not widen their
101
+ contracts to an unconstrained primitive.
102
+ The tool adds optional string `cwd` for script-bearing machines; it does not
103
+ put `cwd` in FSM input unless the descriptor explicitly maps it.
104
+ An explicit `entryEvent: null` selects the shared classifier only where this
105
+ definition permits no deterministic entry; it does not relax entry rules.
106
+ `bossEvents` retains the exact additional erased event fields, source ownership,
107
+ requiredness, and closed values specified under Output.
108
+ The state/outcome/field authority map, placeholder exceptions, transition and
109
+ verbatim fields, resumable states, unfinished finals, and safe ordered context
110
+ projection retain their exact obligations under Output.
111
+ The tool copies role labels and identities from the machine and never guesses
112
+ result semantics by executing an invocation against invented context.
113
+
114
+ The helper resolves the installed shared engine from the source and target
115
+ locations and refuses differing engine resolutions.
116
+ Its factory preflight checks linked metadata.
117
+ It accepts `.fsm.js` on supported Node versions; `.fsm.ts` requires native
118
+ type stripping (Node 23.6+, or Node 22.18+).
119
+ On success it atomically writes only the declared target after factory
120
+ preflight; run all existing conformance checks afterward.
121
+ Exit 2 reports `unsupported` without changing the target: continue ordinary
122
+ linking under this complete definition.
123
+ Exit 1 reports invalid metadata, loading, preflight, or output failure:
124
+ correct the identified problem before treating linking as successful.
125
+
126
+ ### Experimental labelled-string and nested-call profile
127
+
128
+ `flat-labelled-relays` is an unmeasured candidate for flat single-region
129
+ machines with delegated players, scripts, and nested `playbook` calls. It
130
+ requires the installed shared `composePlayerContinuation` API. It leaves
131
+ nested input composition, child targets, result guards, recovery, and terminal
132
+ semantics in the unchanged FSM; the shared factory provides the nested bridge.
133
+ It does not authorize generating or importing maintained workflow artifacts.
134
+
135
+ Select it only for literal string substitution in unchanged player templates,
136
+ including labelled `> Label: <token>` lines and source-declared role-identity
137
+ tokens. In addition to every descriptor member above, supply:
138
+
139
+ - `playerInputExport`: the exact exported FSM player-input type name.
140
+ - `omitEmptyRelayLines`: exact complete source lines to omit only when their
141
+ mapped string value is empty, for example
142
+ `["> Run results: <run-results>"]`. Each line starts with `> ` and ends in
143
+ one placeholder; any preceding label is literal. Use an explicit empty
144
+ array when no line is optional; never infer optionality from a missing value.
145
+ - `identityPlaceholders`: an explicit token-to-canonical-local-role map, such
146
+ as `{ "coder-llm": "coder" }` only where Source requires that identity.
147
+ These values come solely from the invocation-scoped identity lookup, never
148
+ from options, FSM context, or model prose. Use `{}` when none is authored.
149
+
150
+ The composer replaces original tokens once, prefixes each continuation line
151
+ of a value inserted into a quoted source line with `> `, preserves line
152
+ separators and literal inserted text, and leaves missing/non-string values as
153
+ source tokens. Undeclared empty relay lines retain their authored text.
154
+ The emitted verification composer has the same identity and optional third
155
+ resume arguments as the runtime; the shared helper owns fresh/resumed Q&A.
156
+ 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
159
+ conformance checks remain mandatory. This profile changes no runtime bridge,
160
+ workflow semantics, or measured performance claim.
161
+
162
+ For an FSM that satisfies one of the materializer profiles above, derive its
163
+ complete source-owned descriptor and run `materialize-link.mjs` before writing
164
+ an ordinary linked module by hand. Use the supported profile whose composer
165
+ matches the actual source. An unsupported-profile exit continues ordinary
166
+ linking under this definition; invalid metadata must be corrected. Never
167
+ widen an option, omit a required custom strategy, change the FSM, or relax a
168
+ verification check to make a profile fit.
169
+
170
+
36
171
  ## PlaybookRuntime contract
37
172
 
38
173
  The emitted module shall default-export a factory of the following shape:
@@ -364,7 +499,7 @@ The roleless session-Captain is the sole signature exception: its public options
364
499
 
365
500
  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.
366
501
  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.
367
- 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.
502
+ 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`, additional typed ledger commands for the same atomic completion, one `deferred` binding carrying optional UUID `operationId` plus exact `pendingQuestion` and `playerContinuation`, or literal `unresolved: true`, where `deferred` shall be mutually exclusive with `unresolved`, `logicalOperationId`, and commands; and the ledger exposes its synchronous detached `snapshot(): PlaybookEffectLedger` mirror plus `writeAhead(commands: PlaybookEffectLedgerCommandBatch): Promise<PlaybookEffectLedger>` against the current host's atomic writer.
368
503
  Only `configuredOptions` may reach option snapshotting and FSM input.
369
504
  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.
370
505
 
@@ -425,6 +560,9 @@ Each state shall name exactly the outcomes in that state's `invoke.input.result`
425
560
  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.
426
561
  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.
427
562
  Each repository disposition shall be exactly `unchanged`, `one-descendant-commit`, or `deferred`; an effect-owned field is valid on `one-descendant-commit` and `unchanged` and never on `deferred`, and `deferred` is valid only on `needsBossReply` with presentation-owned `question` and another outcome in that state declaring `one-descendant-commit`.
563
+ The linker shall derive each disposition from the source-derived operation and the outcome's required repository effect, independently of whether its result description declares an effect-owned payload field.
564
+ A completion that requires committing the result to Git shall declare `one-descendant-commit` even when its outcome is simply `done` with `fields: {}`; its eligible `needsBossReply` arm shall declare `deferred` under the rule above.
565
+ The absence of `latestCommit` or any other effect-owned field shall never justify defaulting that completion to `unchanged`.
428
566
  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.
429
567
 
430
568
  `init` receives the host-owned playbook session identity and ports, constructs the XState actor with FSM `input` derived from `options`, and starts the actor.
@@ -459,6 +597,17 @@ remain a required readonly runtime option passed through to machine input; the
459
597
  linker shall neither invent an empty catalog nor require it to be baked into a
460
598
  CLI link option.
461
599
 
600
+ The linked module shall export public synchronous pure `validateOptions(value: unknown): PlaybookRuntimeOptions` and bind that same function as the shared spec's `snapshotOptions`.
601
+ The validator shall first capture `value === undefined ? {} : value` with the public `snapshotJsonValue` exported by `@sublang/playbook/xstate-runtime`, before reading option members, applying defaults, or constructing a replacement record; only top-level `undefined` is normalized, and non-JSON input rejects through that shared boundary.
602
+ It shall then validate the artifact's actual option shape, requiredness, source-authored defaults, unknown keys, and declared values against the detached snapshot, reject null and invalid options, and return a detached immutable plain-JSON option record without runtime construction or live host capabilities.
603
+ Boss text supplied by the entry event is not a required startup option unless Source independently requires it before the first Boss turn; a generated required type annotation alone is not that evidence.
604
+ A generated entry guard that requires the text already in context is likewise
605
+ not independent Source evidence; it is a producer defect when the entry action
606
+ has yet to copy the event's text. Do not compensate with a required option or
607
+ invented startup task. `entryEvent.contextField` supplies failure-retry text;
608
+ it does not populate fresh entry context before FSM guards execute.
609
+ A source-appropriate optional seed may remain, and genuine required bootstrap catalogs or other options shall not be erased or filled with invented defaults.
610
+
462
611
  Concrete player binding and prompt identity are host policy and shall not enter `PlaybookRuntimeOptions`, machine input, or the emitted artifact.
463
612
  For a shell-hosted runtime, `PlaybookSession.roleBindings` shall carry exactly the runtime's local roles, map each to its resolved player id and current prompt identity, and be the sole source for call targeting, player-facing prompt identity, concurrency keys, and trace player ids.
464
613
  The host shall derive `promptIdentity` from the current effective model when present and the established adapter otherwise; a standalone runtime may omit the map and retain only its local role identity.
@@ -1457,10 +1606,12 @@ artifact declares its reached final state's kind: the runtime reads
1457
1606
  `meta.playbook.terminal` and that state's authored description from the
1458
1607
  artifact, never from an agent reply, and a declared value other than `success`
1459
1608
  or `failure` is a control-plane error rather than a child outcome.
1460
- Because the bridge routes a failure terminal to the error path, `onDone` alone
1461
- proves the child succeeded and a caller never inspects a callee's output fields
1462
- to decide that; a caller reads those fields only when its own Source relays
1463
- them.
1609
+ `onDone` proves successful bridge delivery without a declared child failure;
1610
+ it does not establish every caller-owned domain condition. The caller shall
1611
+ enforce its own explicit Source-authored acceptance or relay predicates on
1612
+ the delivered output before continuing, without inventing predicates from
1613
+ callee implementation details or overriding the child's compiled terminal
1614
+ kind with output fields.
1464
1615
  Unknown, duplicate, or stale call ids reject without changing actor state.
1465
1616
  The finish trace shall therefore precede any parent FSM transition caused by
1466
1617
  the child return.
@@ -2250,8 +2401,8 @@ The thin emitted module:
2250
2401
  by `PlaybookSession` or another linker-owned source (§PlaybookRuntime
2251
2402
  contract), plus the optional `cwd` option whenever the FSM contains a
2252
2403
  `script` state (§Script execution).
2253
- - Supplies the spec's `snapshotOptions` with the same options-validation
2254
- semantics previously generated inline: validate and JSON-snapshot the
2404
+ - Exports the public `validateOptions` function specified above and supplies
2405
+ it as the spec's identical `snapshotOptions` callback: validate and JSON-snapshot the
2255
2406
  caller's options, rejecting undeclared keys and non-conforming values, so
2256
2407
  the factory binds an immutable options record before constructing any
2257
2408
  actor.