@sublang/playbook 7.0.0 → 9.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.
- package/README.md +20 -7
- package/docs/cli.md +88 -43
- package/docs/configuration.md +221 -119
- package/docs/embedding.md +78 -27
- package/package.json +4 -3
- package/reference/sdlc/captain.playbook/captain.playbook.js +16 -5
- package/reference/sdlc/captain.playbook/captain.playbook.ts +20 -6
- package/reference/sdlc/code.md +1 -1
- package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
- package/reference/sdlc/code.playbook/bin/launch-config.js +1078 -116
- package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
- package/reference/sdlc/code.playbook/bin/run.js +283 -298
- package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
- package/reference/sdlc/code.playbook/code.fsm.d.ts +9 -6
- package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
- package/reference/sdlc/code.playbook/code.fsm.js +18 -15
- package/reference/sdlc/code.playbook/code.fsm.ts +21 -21
- package/reference/sdlc/code.playbook/code.gears.md +1 -1
- package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
- package/reference/sdlc/code.playbook/code.playbook.js +25 -15
- package/reference/sdlc/code.playbook/code.playbook.ts +34 -17
- package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
- package/reference/sdlc/code.playbook/code.registry.js +3 -10
- package/reference/sdlc/code.playbook/code.registry.ts +7 -32
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
- package/reference/sdlc/code.playbook/playbook-captain.js +1014 -299
- package/reference/sdlc/code.playbook/playbook-captain.ts +1450 -406
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
- package/reference/sdlc/decide.md +4 -4
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +10 -10
- package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
- package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
- package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +11 -13
- package/reference/sdlc/decide.playbook/decide.playbook.js +465 -246
- package/reference/sdlc/decide.playbook/decide.playbook.ts +623 -283
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
- package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
- package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
- package/reference/sdlc/review.md +4 -5
- package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
- package/reference/sdlc/review.playbook/review.fsm.js +30 -24
- package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
- package/reference/sdlc/review.playbook/review.gears.md +6 -5
- package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
- package/reference/sdlc/review.playbook/review.playbook.js +29 -23
- package/reference/sdlc/review.playbook/review.playbook.ts +38 -28
- package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
- package/reference/sdlc/review.playbook/review.registry.js +3 -16
- package/reference/sdlc/review.playbook/review.registry.ts +7 -38
- package/slc/gears2fsm.md +45 -24
- package/slc/link.md +297 -135
- package/slc/text2gears.md +19 -18
- package/src/runtime.d.ts +21 -16
- package/src/runtime.ts +20 -23
- package/src/xstate-playbook-runtime.d.ts +34 -20
- package/src/xstate-playbook-runtime.js +973 -400
- package/src/xstate-playbook-runtime.ts +1203 -457
- package/src/xstate-runtime.d.ts +17 -7
- package/src/xstate-runtime.js +198 -81
- package/src/xstate-runtime.ts +339 -112
package/slc/link.md
CHANGED
|
@@ -59,13 +59,27 @@ interface PlaybookSession {
|
|
|
59
59
|
parentSessionId?: string;
|
|
60
60
|
parentCallId?: string;
|
|
61
61
|
depth: number;
|
|
62
|
+
roleBindings?: Readonly<Record<string, PlaybookRoleBinding>>;
|
|
62
63
|
playerSessions?: PlayerSessionStore;
|
|
63
64
|
ports: PlaybookPorts;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
interface PlaybookRoleBinding {
|
|
68
|
+
readonly playerId: string;
|
|
69
|
+
readonly promptIdentity: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PlaybookPendingBossQuestion {
|
|
73
|
+
questionId: string;
|
|
74
|
+
asker: { kind: 'captain' } | { kind: 'role'; roleId: string };
|
|
75
|
+
question: string;
|
|
76
|
+
sourceItem?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
interface PlayerSessionStore {
|
|
67
|
-
select(
|
|
68
|
-
|
|
80
|
+
select(roleId: string): string | false;
|
|
81
|
+
// Called only for a replacement token or an authorized ok-status clear.
|
|
82
|
+
update(roleId: string, resumeToken?: string): void;
|
|
69
83
|
snapshot(): Readonly<Record<string, string>>;
|
|
70
84
|
restore(tokens: Readonly<Record<string, string>>): void;
|
|
71
85
|
}
|
|
@@ -103,6 +117,30 @@ interface PlaybookPendingCall {
|
|
|
103
117
|
childSessionId: string;
|
|
104
118
|
}
|
|
105
119
|
|
|
120
|
+
interface PlaybookSuspendedCall extends PlaybookPendingCall {
|
|
121
|
+
stateId: string;
|
|
122
|
+
text: string;
|
|
123
|
+
turnId?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface PlaybookRuntimeSnapshot {
|
|
127
|
+
schemaVersion: 3;
|
|
128
|
+
playbookId: string;
|
|
129
|
+
machine: JsonValue;
|
|
130
|
+
roleResumeTokens: { readonly [roleId: string]: string };
|
|
131
|
+
sequences: {
|
|
132
|
+
trace: number;
|
|
133
|
+
turn: number;
|
|
134
|
+
judgeCall: number;
|
|
135
|
+
playerCall: number;
|
|
136
|
+
playbookCall: number;
|
|
137
|
+
captainCall?: number;
|
|
138
|
+
};
|
|
139
|
+
state: PlaybookState;
|
|
140
|
+
pendingBossQuestions: readonly PlaybookPendingBossQuestion[];
|
|
141
|
+
suspendedCall?: PlaybookSuspendedCall;
|
|
142
|
+
}
|
|
143
|
+
|
|
106
144
|
type PlaybookRunResult =
|
|
107
145
|
| { outcome: 'quiescent' | 'no-action'; state: PlaybookState }
|
|
108
146
|
| {
|
|
@@ -113,6 +151,7 @@ type PlaybookRunResult =
|
|
|
113
151
|
| {
|
|
114
152
|
outcome: 'terminal';
|
|
115
153
|
state: PlaybookState;
|
|
154
|
+
stateDescription?: string;
|
|
116
155
|
output?: JsonValue;
|
|
117
156
|
}
|
|
118
157
|
| {
|
|
@@ -146,12 +185,14 @@ differ from both its `rootSessionId` and `parentSessionId`.
|
|
|
146
185
|
Run outcomes are exact: `no-action` means no FSM event was sent;
|
|
147
186
|
`quiescent` means a non-failure parked/idle state; `failed` means the FSM is in
|
|
148
187
|
a recoverable failure state; `terminal` means top-level final with optional
|
|
149
|
-
JSON output
|
|
188
|
+
JSON output and the exact authored `stateDescription` of the reached final
|
|
189
|
+
state when one is declared; `aborted` means the turn signal ended work; and `suspended` means
|
|
150
190
|
exactly one `pendingCall` is active.
|
|
191
|
+
Only the terminal variant may carry `stateDescription`; the runtime shall omit it when the final state declares none and shall never substitute a state id or derive it from opaque output ([DR-037](../specs/decisions/037-terminal-result-meaning.md)).
|
|
151
192
|
Control-plane exceptions reject the runtime method rather than masquerade as a
|
|
152
193
|
recoverable workflow `failed` result.
|
|
153
194
|
|
|
154
|
-
`PlaybookRuntimeOptions` is host-agnostic and carries only _per-run_ knobs
|
|
195
|
+
`PlaybookRuntimeOptions` is host-agnostic and carries only _per-run_ workflow knobs, strategy overrides the linker exposes, and — where the compiled playbook's policy needs a host seam — host-supplied port-shaped callbacks the linker exposes as option members whose types the artifact itself declares, so the six-member `PlaybookPorts` contract and the shared contract module stay free of host types.
|
|
155
196
|
The link compiler emits a typed options interface per playbook based on the FSM's `CodingInput` (or equivalent).
|
|
156
197
|
The CLI's absence of `--link-option` values does not mean that
|
|
157
198
|
`PlaybookRuntimeOptions` is empty. CLI link options are compile-time inputs;
|
|
@@ -162,15 +203,16 @@ remain a required readonly runtime option passed through to machine input; the
|
|
|
162
203
|
linker shall neither invent an empty catalog nor require it to be baked into a
|
|
163
204
|
CLI link option.
|
|
164
205
|
|
|
165
|
-
|
|
166
|
-
|
|
206
|
+
Concrete player binding and prompt identity are host policy and shall not enter `PlaybookRuntimeOptions`, machine input, or the emitted artifact.
|
|
207
|
+
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.
|
|
208
|
+
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.
|
|
167
209
|
|
|
168
210
|
## PlaybookPorts contract
|
|
169
211
|
|
|
170
212
|
```typescript
|
|
171
213
|
interface PlaybookPorts {
|
|
172
214
|
callPlayer(
|
|
173
|
-
|
|
215
|
+
roleId: string,
|
|
174
216
|
prompt: string,
|
|
175
217
|
signal: AbortSignal,
|
|
176
218
|
options: PlayerCallOptions,
|
|
@@ -289,7 +331,7 @@ emission drain. Absent such a control-plane failure, if the combined signal
|
|
|
289
331
|
has aborted, ordinary abort settlement remains authoritative after the actor
|
|
290
332
|
reaches its error path.
|
|
291
333
|
|
|
292
|
-
Every linked runtime owns a map
|
|
334
|
+
Every standalone linked runtime owns a private continuation map keyed by resolved player id when `roleBindings` is supplied and by local role id otherwise.
|
|
293
335
|
Before reading a resolved direct-Captain or delegated-player result, the
|
|
294
336
|
runtime shall validate, detach, and freeze it through the shared
|
|
295
337
|
`validateCaptainResult` or `validatePlayerResult` helper. The accepted object
|
|
@@ -297,17 +339,18 @@ shape is exact: only the declared status and optional string fields are
|
|
|
297
339
|
allowed, JSON-unsafe members reject, and caller mutation after resolution
|
|
298
340
|
cannot change trace evidence or player continuity. Validation happens before
|
|
299
341
|
adopting a resume token or reading final text.
|
|
300
|
-
The first call to each
|
|
301
|
-
After a resolved call, the runtime shall replace the token when the result carries one
|
|
342
|
+
The first call to each private continuation key in a standalone playbook session shall pass `{ resume: false }`; later calls shall pass the exact stored token, so two sequential roles explicitly bound to one player id share one token even without a supplied `PlayerSessionStore`.
|
|
343
|
+
After a validated resolved call, the runtime shall replace the token when the result carries one, clear it only for an `ok` result that omits one, and preserve it for an `aborted` or `error` result that omits one; a rejected call with no result likewise leaves the prior token unchanged.
|
|
302
344
|
After awaiting a host Captain or player promise, the runtime shall re-check the
|
|
303
345
|
combined invocation/public-boundary signal before validating the result,
|
|
304
346
|
adopting a resume token, or emitting a successful finish. A host promise that
|
|
305
347
|
ignores cancellation and resolves late shall be paired as aborted and shall
|
|
306
348
|
not mutate continuity or masquerade as success.
|
|
307
349
|
The map survives actor reconstruction inside the same runtime and is discarded at `dispose`.
|
|
308
|
-
The runtime shall keep an in-flight set keyed by resolved player id
|
|
309
|
-
|
|
310
|
-
to
|
|
350
|
+
The runtime shall keep an in-flight set keyed by resolved player id when the
|
|
351
|
+
host supplies binding metadata, otherwise by local role id, and reject a
|
|
352
|
+
second concurrent call to the same key before crossing the host port.
|
|
353
|
+
Calls to distinct keys may overlap.
|
|
311
354
|
|
|
312
355
|
`callJudge` returns free-form text.
|
|
313
356
|
The runtime parses it per the state's adjudication strategy (§Captain adjudication).
|
|
@@ -346,7 +389,7 @@ The runtime never speaks to LLMs directly and never touches host types beyond `P
|
|
|
346
389
|
## Playbook trace
|
|
347
390
|
|
|
348
391
|
Every linked runtime shall emit a boundary-complete, ordered trace through `emitTelemetry` topic `playbook.trace`.
|
|
349
|
-
Each payload shall carry `schemaVersion:
|
|
392
|
+
Each payload shall carry `schemaVersion: 3`, the immutable session identity and
|
|
350
393
|
causality, a contiguous one-based `sequence`, a Unix-millisecond `timestamp`, a
|
|
351
394
|
trace `type`, event `payload`, and the runtime-local `turnId` / paired `callId`
|
|
352
395
|
where applicable.
|
|
@@ -371,7 +414,7 @@ type PlaybookTraceType =
|
|
|
371
414
|
| 'session.disposed';
|
|
372
415
|
|
|
373
416
|
interface PlaybookTraceEvent {
|
|
374
|
-
schemaVersion:
|
|
417
|
+
schemaVersion: 3;
|
|
375
418
|
sessionId: string;
|
|
376
419
|
playbookId: string;
|
|
377
420
|
rootSessionId: string;
|
|
@@ -387,7 +430,7 @@ interface PlaybookTraceEvent {
|
|
|
387
430
|
}
|
|
388
431
|
```
|
|
389
432
|
|
|
390
|
-
A composing host may supply `playerSessions` as a frame-local view of player continuation owned by the
|
|
433
|
+
A composing host may supply `playerSessions` as a frame-local view of player continuation owned by the logical Captain session.
|
|
391
434
|
The runtime shall select through that view before allocating or tracing a player call and shall update or clear it from a validated player result before emitting the matching finish trace.
|
|
392
435
|
Snapshot export and restore shall use the same view, failed restore shall leave its prior contents unchanged, and child disposal shall not clear host-owned continuation.
|
|
393
436
|
A host that omits the view retains the runtime's private per-session continuation behavior.
|
|
@@ -401,7 +444,7 @@ The trace types are `session.started`, `boss.input.received`,
|
|
|
401
444
|
`boss.input.settled`, and `session.disposed`.
|
|
402
445
|
Call pairs carry exact prompts and replies, normalized failures, actor and state
|
|
403
446
|
identity, and their boundary-specific options.
|
|
404
|
-
`apply.started` and `apply.finished` are the paired
|
|
447
|
+
`apply.started` and `apply.finished` are the paired apply boundary of a
|
|
405
448
|
executed `apply()` call on a runtime implementing the optional control surface
|
|
406
449
|
(§Control surface): both carry the action id and idempotency `key` (plus the
|
|
407
450
|
singular `stateId` on start when one exists), the pair shares one
|
|
@@ -423,7 +466,8 @@ described top-level `state` and its singular `stateId` when present, as well as
|
|
|
423
466
|
its message and optional data; consumers shall not have to recover state
|
|
424
467
|
identity from a nested ad hoc object.
|
|
425
468
|
Judge results use `reply`; player start and finish payloads both carry the
|
|
426
|
-
|
|
469
|
+
local `roleId`, the resolved `playerId` when host binding metadata is
|
|
470
|
+
available, and the selected `resume`; Captain start and finish payloads both carry
|
|
427
471
|
the exact composed prompt, the boundary's selected `visibility`, the direct
|
|
428
472
|
invocation's `stateId` and `sourceItem`, and no player resume selection or
|
|
429
473
|
resume token — a visible workflow call carries its runtime-owned
|
|
@@ -453,6 +497,15 @@ If a started-boundary sink records the event and then rejects, the runtime
|
|
|
453
497
|
shall make one best-effort normalized error-finish attempt with the same call
|
|
454
498
|
id and then reject the original start error. It shall not retry either event or
|
|
455
499
|
let a failure of that finish attempt replace the start error.
|
|
500
|
+
A start-sink rejection causally identical to the applicable signal reason is
|
|
501
|
+
the cancellation itself, not a control error: no host call begins, the
|
|
502
|
+
best-effort paired finish carries the boundary's canonical aborted evidence —
|
|
503
|
+
`status: 'aborted'` for a host call, or the rejected-before-effect disposition
|
|
504
|
+
and reason for apply — and nothing is latched. An ordinary run boundary settles
|
|
505
|
+
as §Abort prescribes. At the apply boundary the same event remains
|
|
506
|
+
pre-acceptance: `apply` rejects with that exact reason, records no receipt, and
|
|
507
|
+
leaves the key reusable
|
|
508
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
|
|
456
509
|
When a call boundary carries `callId`, that id shall be unique within the
|
|
457
510
|
runtime session. A stable FSM `stateId` is identity metadata in the payload,
|
|
458
511
|
not a call id and shall not be reused as one across repeated invocations.
|
|
@@ -498,51 +551,36 @@ Trace payloads never become Boss-visible status or prompt text.
|
|
|
498
551
|
The link compiler shall accept:
|
|
499
552
|
|
|
500
553
|
- The FSM artifact (path to a `.fsm.ts`).
|
|
501
|
-
- A **player binding** mapping GEARS players (declared in the
|
|
502
|
-
[text2gears](text2gears.md#players) source) to opaque player-identifier
|
|
503
|
-
strings.
|
|
504
|
-
Where no binding is supplied, the linker shall apply the default
|
|
505
|
-
binding — each player to its lowercased name (e.g. `Coder` → `coder`)
|
|
506
|
-
— and record the applied binding in the emitted header.
|
|
507
554
|
- An **adjudication strategy** (default: LLM-judge per state) and a
|
|
508
555
|
**Boss-event mapping** (default: free-text judge classification).
|
|
509
556
|
Both strategies are host-agnostic.
|
|
510
557
|
|
|
511
558
|
The host's identity does not enter compilation; the linked module runs unchanged under any host that implements `PlaybookPorts`.
|
|
512
559
|
|
|
513
|
-
##
|
|
560
|
+
## Role identity
|
|
514
561
|
|
|
515
|
-
Each delegated GEARS state names exactly one player
|
|
516
|
-
(`
|
|
517
|
-
The
|
|
518
|
-
`PlaybookPorts.callPlayer(playerId, …)`.
|
|
519
|
-
The host adapter routes that opaque string to its concrete primitive.
|
|
562
|
+
Each delegated GEARS state names exactly one canonical local role id (`player` actor `invoke.input.role`).
|
|
563
|
+
The linker shall retain that id in `PlaybookPorts.callPlayer(roleId, …)` without selecting a concrete player.
|
|
564
|
+
The host shall bind that local role id explicitly when it constructs the runtime.
|
|
520
565
|
Every direct-Captain and delegated-player invocation shall also carry its
|
|
521
566
|
working leaf's explicit
|
|
522
567
|
`stateId`; a linked runtime shall use that field for call identity and shall
|
|
523
568
|
not infer one leaf from a structured root snapshot.
|
|
524
|
-
Direct `captain` actor states
|
|
525
|
-
|
|
526
|
-
`captain` for them.
|
|
527
|
-
|
|
528
|
-
For composite players declared with aliases (e.g., `Committer = Coder | Reviewer`), the linker shall resolve the alias **per source item**.
|
|
529
|
-
Resolution inspects the `PlayerInput` fields populated at that state:
|
|
530
|
-
|
|
531
|
-
- If only one `<playerName>Player` field is present, bind to that player.
|
|
532
|
-
- If multiple are present, prefer the first-listed alternative in the alias declaration order.
|
|
533
|
-
- If none are present, fall back to the alias's first alternative.
|
|
534
|
-
|
|
535
|
-
Resolution shall be deterministic and recorded in the emitted module so future maintainers can audit it without re-running the linker.
|
|
536
|
-
|
|
537
|
-
The linker shall not invent player identifiers beyond the recorded default
|
|
538
|
-
binding, and shall not silently collapse aliases at the FSM level — composite
|
|
539
|
-
players keep their `player: 'Committer'` value on `PlayerInput`; resolution
|
|
540
|
-
decides only the `callPlayer` invocation.
|
|
569
|
+
Direct `captain` actor states call `PlaybookPorts.callCaptain`; the linker shall not synthesize a local role or concrete player id named `captain` for them.
|
|
570
|
+
The linker shall reject an alias-shaped role declaration rather than choose a runtime identity.
|
|
541
571
|
|
|
542
572
|
## Player prompt composition
|
|
543
573
|
|
|
544
574
|
The runtime shall compose the actual player prompt from the state's
|
|
545
575
|
`PlayerInput`.
|
|
576
|
+
The shared-factory `composePlayerPrompt` seam shall receive an
|
|
577
|
+
invocation-scoped `promptIdentity(roleId)` lookup as its second argument.
|
|
578
|
+
The lookup shall return the current detached session binding's prompt identity,
|
|
579
|
+
or the canonical local role id when bindings are absent, and shall reject an
|
|
580
|
+
undeclared role.
|
|
581
|
+
It shall expose neither the resolved player id nor the binding map, and the
|
|
582
|
+
runtime shall not place the lookup or any value read through it in options,
|
|
583
|
+
machine input, FSM context, or a persisted snapshot.
|
|
546
584
|
`input.prompt` is the GEARS-derived domain prompt body and shall not be mutated, re-flowed, or treated as a place to store framework control instructions.
|
|
547
585
|
A leading `>` inside that body is authored quoted-context content and shall reach the player unchanged.
|
|
548
586
|
|
|
@@ -632,10 +670,14 @@ nonconformant.
|
|
|
632
670
|
The FSM's `events` union enumerates every Boss-originated event.
|
|
633
671
|
The runtime receives Boss input as a free-form string
|
|
634
672
|
(`handleBossInput.text`).
|
|
635
|
-
Where the current ready or reconstructed
|
|
673
|
+
Where the current ready, recoverable-failure (`failed`), or reconstructed
|
|
674
|
+
terminal machine accepts exactly one
|
|
636
675
|
ordinary textual entry event and no Boss question is pending, the runtime
|
|
637
676
|
shall send that event deterministically and attach the exact original text to
|
|
638
|
-
its declared textual payload field without invoking `callJudge
|
|
677
|
+
its declared textual payload field without invoking `callJudge`: each of the
|
|
678
|
+
three is an entry awaiting a fresh intent, so delivered text has exactly one
|
|
679
|
+
meaning there and a judge call could only spend budget or settle the restart
|
|
680
|
+
as no action.
|
|
639
681
|
The default Captain — the controller playbook of
|
|
640
682
|
[gears2fsm "Setup"](gears2fsm.md#setup) — is deterministic at every parked
|
|
641
683
|
entry: the runtime maps each Boss turn from the exact text and the host's
|
|
@@ -687,7 +729,7 @@ classification is recoverable control input, not a public boundary rejection.
|
|
|
687
729
|
If a recovered `BOSS_REPLY` names no question that is currently pending, it is
|
|
688
730
|
such a malformed classification: emit the one recovery status, send no event,
|
|
689
731
|
leave the actor unchanged, and return `no-action` after emissions drain.
|
|
690
|
-
Host-owned runtime options, player bindings, and enabled-playbook catalogs are
|
|
732
|
+
Host-owned runtime options, role-to-player bindings, and enabled-playbook catalogs are
|
|
691
733
|
not Boss-event payload. The classifier schema and parser shall not invite or
|
|
692
734
|
accept them, and classified prose shall never overwrite their machine context.
|
|
693
735
|
Every recovered classifier object shall have exactly `type` plus the declared
|
|
@@ -705,14 +747,14 @@ aborted while the classifier finish emission was pending, return and trace the
|
|
|
705
747
|
same structured `aborted` result against the unchanged actor.
|
|
706
748
|
When the FSM supports a Boss-reply suspension state, the prompt shall inspect
|
|
707
749
|
the actor snapshot context and include each exact pending Boss question,
|
|
708
|
-
question id, and
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
750
|
+
question id, and discriminated Captain-or-role asker so the judge can distinguish a reply from a fresh directive.
|
|
751
|
+
With one pending question, a classified `BOSS_REPLY` that omits its optional id shall be filled with that sole id.
|
|
752
|
+
With several pending questions, the classifier shall require a known id.
|
|
753
|
+
A reply shall re-enter only
|
|
712
754
|
its recorded resume state and preserve the original intent, plan, prior child
|
|
713
755
|
results, and Q+A continuation context.
|
|
714
|
-
The classifier-facing pending-question block contains only `questionId`,
|
|
715
|
-
|
|
756
|
+
The classifier-facing pending-question block contains only `questionId`, `asker`, and `question`.
|
|
757
|
+
Internal `resumeStateId`, source-item identity, and
|
|
716
758
|
other machine-routing fields remain authoritative in snapshot context and
|
|
717
759
|
shall not be serialized into the judge prompt.
|
|
718
760
|
The allowed fresh directives while parked include every applicable root entry
|
|
@@ -911,10 +953,34 @@ The provided actor shall:
|
|
|
911
953
|
`{ guard: <first declared guard>, exitStatus: 0 }`; any nonzero status
|
|
912
954
|
resolves the second declared guard with that status. Guard selection is
|
|
913
955
|
mechanical; the runtime shall not route script output through the judge.
|
|
914
|
-
- Reject
|
|
915
|
-
state's ordinary `onError` path.
|
|
916
|
-
|
|
917
|
-
|
|
956
|
+
- Reject when the command cannot be spawned at all, routing through the
|
|
957
|
+
state's ordinary `onError` path. Beyond spawn failure, the invocation
|
|
958
|
+
rejects only per the abort bullet below or when one of its own script
|
|
959
|
+
emissions rejects; a completed command's exit status itself never rejects.
|
|
960
|
+
- Honor the active turn's abort signal per §Abort: the actor shall reject
|
|
961
|
+
without spawning when the combined signal is already aborted; shall run the
|
|
962
|
+
shell detached as its own process-group leader; and on abort — whenever it
|
|
963
|
+
lands before the invocation settles, including only after the shell's own
|
|
964
|
+
exit — shall deliver
|
|
965
|
+
`SIGTERM` to the entire group, escalate to `SIGKILL` after a bounded grace,
|
|
966
|
+
and settle only after the shell process itself has exited and the group has
|
|
967
|
+
stopped being signalable, confirmed by an `ESRCH` liveness probe, rejecting
|
|
968
|
+
with the signal's reason. The same
|
|
969
|
+
bounded grace caps the post-`SIGKILL` wait for kernel teardown, so an
|
|
970
|
+
unreaped member outside the runtime's control cannot stall settlement. If
|
|
971
|
+
the group remains signalable through that bound, or confirmation fails
|
|
972
|
+
without `ESRCH`, the boundary rejects with a distinct teardown control error
|
|
973
|
+
rather than reporting a clean abort over unconfirmed cleanup. The kill is
|
|
974
|
+
always posted before the actor settles. Abort ownership — the
|
|
975
|
+
listener and its escalation — spans the whole invocation, not the
|
|
976
|
+
spawn-to-exit window
|
|
977
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)). An abort
|
|
978
|
+
observed only after the shell's exit shall additionally reject before guard
|
|
979
|
+
resolution and before starting any script emission not already in flight; an
|
|
980
|
+
emission already started when the abort lands completes through the
|
|
981
|
+
ordinary serialized channel and the rejection follows it. A
|
|
982
|
+
descendant that leaves the process group is beyond the runtime's kill
|
|
983
|
+
scope.
|
|
918
984
|
- Emit, after the child settles and before the invocation resolves, one status
|
|
919
985
|
line `Executed script for <stateId> (exit <status>).` and one telemetry
|
|
920
986
|
event under topic `playbook.script` with payload
|
|
@@ -939,14 +1005,18 @@ XState `.provide(...)` receives the exact declared actor input rather than a
|
|
|
939
1005
|
structurally similar local type.
|
|
940
1006
|
Construct one bridge per runtime and wire every integration hook: allocate ids
|
|
941
1007
|
with `nextCallId`; return the currently active public-boundary signal from
|
|
942
|
-
`getBoundarySignal`;
|
|
943
|
-
|
|
944
|
-
through `
|
|
945
|
-
`drain`;
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1008
|
+
`getBoundarySignal`; capture an immutable cancellation classifier for the
|
|
1009
|
+
invocation's signal identities; compose `resumePlaybookCall.signal` into that
|
|
1010
|
+
classifier through `bindResumeSignal`; pass the applicable classifier through
|
|
1011
|
+
`emitStarted`, `emitFinished`, and `drain`; bind it to the root transition
|
|
1012
|
+
caused by child settlement through `bindActorSettlement`; and pass it through
|
|
1013
|
+
`onControlPlaneError` and `onBackgroundError`. Each receiving latch shall drop
|
|
1014
|
+
only a failure the supplied classifier identifies as exact cancellation and
|
|
1015
|
+
shall retain every distinct cleanup or observer failure for the owning public
|
|
1016
|
+
boundary, the next drain, or disposal rejection as applicable. A stored
|
|
1017
|
+
distinct failure shall never be reclassified against a later boundary. The
|
|
1018
|
+
runtime shall not leave these optional API hooks unwired merely because their
|
|
1019
|
+
TypeScript properties are optional for simpler bridge consumers.
|
|
950
1020
|
On invocation the bridge allocates a runtime-local call id, traces the start,
|
|
951
1021
|
and calls `PlaybookPorts.callPlaybook` with the composed target/text and the
|
|
952
1022
|
bridge signal combined from the XState invocation lifetime, the active public
|
|
@@ -1031,12 +1101,15 @@ registry; linker-time metadata is not authorization to call a target.
|
|
|
1031
1101
|
|
|
1032
1102
|
Disposal shall settle an outstanding call as aborted and drain its finish
|
|
1033
1103
|
trace before `session.disposed`.
|
|
1034
|
-
If registered child abort cleanup rejects
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1104
|
+
If registered child abort cleanup rejects with a failure distinct from every
|
|
1105
|
+
applicable abort reason, the bridge shall emit the paired finish with an error
|
|
1106
|
+
result and reject `abortPending` or disposal with that original cleanup error,
|
|
1107
|
+
or with an aggregate containing every distinct failure when more than one
|
|
1108
|
+
remains;
|
|
1109
|
+
an exact abort-reason rejection is cancellation evidence and shall not be
|
|
1110
|
+
retained as a control failure. Parent disposal shall still drain, emit its one
|
|
1111
|
+
`session.disposed` boundary, and clear the bound session before rejecting with
|
|
1112
|
+
any preserved distinct cleanup error.
|
|
1040
1113
|
Child output and errors must be JSON-safe; a non-JSON-safe result is a
|
|
1041
1114
|
control-plane error.
|
|
1042
1115
|
|
|
@@ -1114,6 +1187,12 @@ The `PlaybookRuntime` shall:
|
|
|
1114
1187
|
transition-trace or telemetry sink failure is part of `init`: initialization
|
|
1115
1188
|
shall reject, stop the actor, and perform the failed-start cleanup below
|
|
1116
1189
|
rather than swallowing it as a later background error.
|
|
1190
|
+
A root-actor error observed during startup — an initial entry action or a
|
|
1191
|
+
synchronously failing initial invocation — is equally part of `init` and
|
|
1192
|
+
`restore`: the boundary shall reject with that original error after the
|
|
1193
|
+
failed-start cleanup, and shall never resolve leaving the errored actor as
|
|
1194
|
+
later background state
|
|
1195
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
|
|
1117
1196
|
Where the FSM input declares `selfPlaybookId`, seed it from the immutable
|
|
1118
1197
|
`session.playbookId`; do not expose a caller option or reuse a working leaf's
|
|
1119
1198
|
`stateId` as the self-call identity.
|
|
@@ -1167,7 +1246,14 @@ The `PlaybookRuntime` shall:
|
|
|
1167
1246
|
transition/status/telemetry queue before returning, just as
|
|
1168
1247
|
`handleBossInput` does. A resume shall not allocate a new Boss-input
|
|
1169
1248
|
`turnId`; retain the original call-start turn id for its matching finish and
|
|
1170
|
-
for the parent continuation caused by that return.
|
|
1249
|
+
for the parent continuation caused by that return.
|
|
1250
|
+
A resume whose signal is already aborted after identity and result
|
|
1251
|
+
validation shall deliver nothing: bind no resume signal, settle no deferred,
|
|
1252
|
+
emit no call finish, and preserve the pending call — the boundary settles
|
|
1253
|
+
`{ outcome: 'aborted' }` with the signal's reason while the suspended state
|
|
1254
|
+
and pending identity survive, so a later resume with the same call id and a
|
|
1255
|
+
fresh signal still delivers
|
|
1256
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)). Every success and
|
|
1171
1257
|
exceptional path shall drain ordered emissions, select the first latched
|
|
1172
1258
|
non-abort control error before considering abort, and clear its boundary
|
|
1173
1259
|
latches in `finally`, so a failed resume cannot leak an emission error into a
|
|
@@ -1191,23 +1277,20 @@ The `PlaybookRuntime` shall:
|
|
|
1191
1277
|
failure cannot skip the parent disposal boundary or leave the runtime bound.
|
|
1192
1278
|
|
|
1193
1279
|
The actor's `lastError` field shall be surfaced via `emitStatus` when the machine enters its `failed` state.
|
|
1194
|
-
Presence of linker-emitted `
|
|
1195
|
-
|
|
1280
|
+
Presence of linker-emitted `roleStates` selects the canonical factory-backed status profile.
|
|
1281
|
+
That profile shall emit the selected Boss event type
|
|
1196
1282
|
before sending that event, exactly `→ <guard>` (with no payload-count or tally
|
|
1197
1283
|
rider) when a settling actor output carries a guard, and
|
|
1198
|
-
`⤷ <
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
that invoke the typed `player` actor; each value carries the exact
|
|
1202
|
-
that state's source-derived `meta.playbook.
|
|
1203
|
-
|
|
1204
|
-
entry, a non-player state, or a
|
|
1205
|
-
|
|
1206
|
-
shall
|
|
1207
|
-
|
|
1208
|
-
and the unglyphed failure line. The new profile is opt-in so adding the seam
|
|
1209
|
-
does not silently reinterpret an already-linked artifact without an ABI or
|
|
1210
|
-
artifact-schema bump.
|
|
1284
|
+
`⤷ <Role>: <label>` only when the entered state appears in the linked module's `roleStates` metadata.
|
|
1285
|
+
It shall emit no raw state-id fallback for any other state.
|
|
1286
|
+
`roleStates` shall be a complete map of the FSM states
|
|
1287
|
+
that invoke the typed `player` actor; each schema-2 value carries the exact
|
|
1288
|
+
local role from that state's source-derived `meta.playbook.role` and the state's exact FSM description as `{ role, label }`.
|
|
1289
|
+
The factory shall reject an
|
|
1290
|
+
incomplete entry, a non-player state, or a role or label that differs from the FSM metadata.
|
|
1291
|
+
Artifact schema `1` and a missing compatibility declaration
|
|
1292
|
+
shall reject before interpretation because their legacy `player` values may
|
|
1293
|
+
encode bindings or aliases rather than canonical local roles.
|
|
1211
1294
|
For the default Captain runtime, an initial `ready` state and a terminal `done`
|
|
1212
1295
|
state shall not emit human status. The terminal response is already visible
|
|
1213
1296
|
Captain prose; a synthetic “entered done” message would present it twice.
|
|
@@ -1223,9 +1306,14 @@ transition first.
|
|
|
1223
1306
|
|
|
1224
1307
|
If a `*.call.started` trace records and then its sink rejects, no host call may
|
|
1225
1308
|
begin. The runtime shall still enqueue exactly one synthetic paired
|
|
1226
|
-
`*.call.finished` trace with `status: 'error'`,
|
|
1309
|
+
`*.call.finished` trace — with `status: 'error'`, or `status: 'aborted'` when
|
|
1310
|
+
the sink rejection is causally identical to the applicable signal reason, in
|
|
1311
|
+
which case nothing is latched and the turn follows abort settlement
|
|
1312
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)) — preserving
|
|
1313
|
+
the original call
|
|
1227
1314
|
id, turn id, actor visibility, state/source identity, and prompt or request
|
|
1228
|
-
metadata from the start boundary.
|
|
1315
|
+
metadata from the start boundary. A distinct rejection shall then follow the
|
|
1316
|
+
same latched
|
|
1229
1317
|
control-error, FSM settlement, and ordered-drain path as any other call-start
|
|
1230
1318
|
failure; the synthetic finish must not replace the original sink error.
|
|
1231
1319
|
|
|
@@ -1245,22 +1333,22 @@ quiescent state with actor status `active`.
|
|
|
1245
1333
|
At a safe capture point it shall return a JSON-safe
|
|
1246
1334
|
`PlaybookRuntimeSnapshot` carrying:
|
|
1247
1335
|
|
|
1248
|
-
- `schemaVersion`: literal `
|
|
1336
|
+
- `schemaVersion`: literal `3`.
|
|
1249
1337
|
- `playbookId`: the bound session's playbook id.
|
|
1250
1338
|
- `machine`: the root actor's `getPersistedSnapshot()` result, passed
|
|
1251
1339
|
through the shared JSON detachment with any raw `Error` context value
|
|
1252
1340
|
(for example FSM `lastError`) normalized to `{ name, message, stack? }`
|
|
1253
1341
|
first. The value is opaque to hosts.
|
|
1254
|
-
- `
|
|
1342
|
+
- `roleResumeTokens`: the local-role resume-token projection as a plain object
|
|
1255
1343
|
(§PlaybookPorts contract).
|
|
1256
1344
|
- `sequences`: the live `trace`, `turn`, `judgeCall`, `playerCall`, and
|
|
1257
1345
|
`playbookCall` counters, plus `captainCall` when the runtime supports direct
|
|
1258
|
-
Captain calls.
|
|
1259
|
-
|
|
1260
|
-
input, where restore uses `trace` as its collision-safe floor.
|
|
1346
|
+
Captain calls.
|
|
1347
|
+
A direct-Captain-capable runtime shall persist it in every schema-version-3 export.
|
|
1261
1348
|
- `state`: the current normalized state descriptor.
|
|
1262
1349
|
- `pendingBossQuestions`: the pending Boss question(s) from FSM context as
|
|
1263
|
-
a list of `{ questionId,
|
|
1350
|
+
a list of `{ questionId, asker, question, sourceItem? }`, where `asker` is
|
|
1351
|
+
`{ kind: 'captain' }` or `{ kind: 'role', roleId }`, empty when the
|
|
1264
1352
|
parked state awaits no reply. This list exists so hosts can surface the
|
|
1265
1353
|
question without parsing status lines or telemetry.
|
|
1266
1354
|
- `suspendedCall`: omitted when no nested call is pending; otherwise the
|
|
@@ -1278,19 +1366,16 @@ unsafe and returns `undefined`.
|
|
|
1278
1366
|
`restore(session, snapshot)` is an alternative to `init` under the same
|
|
1279
1367
|
lifecycle guards (§Session lifecycle): it shall reject when already
|
|
1280
1368
|
initialized, disposing, or disposed, and shall validate
|
|
1281
|
-
schema version `
|
|
1282
|
-
`
|
|
1283
|
-
descriptor-free legacy input; schema version `2` may carry the suspended-call
|
|
1284
|
-
descriptor above.
|
|
1369
|
+
schema version `3` and that `snapshot.playbookId` equals `session.playbookId` before touching state.
|
|
1370
|
+
Runtime snapshot schemas `1` and `2` shall reject before state binding because their token and pending-question fields conflate local roles, concrete players, and Captain identity.
|
|
1285
1371
|
The host supplies the same immutable `PlaybookSession` identity the
|
|
1286
1372
|
snapshot was exported under and recreates the runtime through the same
|
|
1287
1373
|
factory with equivalent options; the runtime does not diff options, and
|
|
1288
1374
|
module identity — that the factory constructing this runtime still
|
|
1289
1375
|
belongs to the snapshot's playbook — is likewise the host's check to
|
|
1290
1376
|
make before calling `restore`.
|
|
1291
|
-
`restore` shall bind the session, restore the
|
|
1292
|
-
sequence counters
|
|
1293
|
-
collision-safe floor for an absent legacy `captainCall`), and the
|
|
1377
|
+
`restore` shall bind the session and its current detached role bindings, restore the local-role token projection, the
|
|
1378
|
+
sequence counters, and the
|
|
1294
1379
|
prior-state descriptor from the snapshot,
|
|
1295
1380
|
prepare the shared nested bridge with the suspended-call descriptor or its
|
|
1296
1381
|
explicit absence, restore a descriptor's call-to-turn map entry, construct
|
|
@@ -1401,11 +1486,18 @@ still describes the state. Two families exist, labeled from source state
|
|
|
1401
1486
|
descriptions:
|
|
1402
1487
|
|
|
1403
1488
|
- **Failure-state retry** — while the singular state id is the recoverable
|
|
1404
|
-
failure state and the
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1489
|
+
failure state and the live snapshot accepts the retry event sourced below,
|
|
1490
|
+
the runtime shall advertise `retry:<EVENT_TYPE>` replaying exactly that
|
|
1491
|
+
event. Where the emitted module's entry-event declaration names the FSM
|
|
1492
|
+
context member the machine's entry action copies the exact Boss text into
|
|
1493
|
+
(DR-034), the retry event is that deterministic entry event built from the
|
|
1494
|
+
live snapshot's member — excluded when the member is absent, not a string,
|
|
1495
|
+
or blank, and never falling back to the record. Where it names no member,
|
|
1496
|
+
the retry event is the recorded last classified event (the event a public
|
|
1497
|
+
Boss boundary sent that drove the run into `failed`, kept with its recorded
|
|
1498
|
+
payload), and there is none while the runtime holds none. The member is
|
|
1499
|
+
declared, never inferred from a context member that happens to match the
|
|
1500
|
+
entry event's text field.
|
|
1409
1501
|
- **Jump entries** — for each registered resumable state id whose
|
|
1410
1502
|
explicit-state-jump event (`BOSS_INTERRUPT` with that `targetId`, optional
|
|
1411
1503
|
textual fields omitted) the live snapshot accepts, guards included, the
|
|
@@ -1460,12 +1552,17 @@ trace and the return agree, and a receipt states what happened to the effect
|
|
|
1460
1552
|
rather than what happened to its telemetry. The published receipt stands, is
|
|
1461
1553
|
returned and replayed verbatim, and the delivery failure travels on the
|
|
1462
1554
|
runtime's emission-failure channel to surface from the next public boundary
|
|
1463
|
-
that drains
|
|
1555
|
+
that drains — unless the delivery failure is causally identical to the apply
|
|
1556
|
+
signal's own abort reason, in which case it evidences the cancellation and is
|
|
1557
|
+
dropped, not latched
|
|
1558
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
|
|
1464
1559
|
|
|
1465
1560
|
The recorded receipts and the recorded last classified event are
|
|
1466
|
-
process-local: the durable runtime snapshot persists neither
|
|
1467
|
-
|
|
1468
|
-
|
|
1561
|
+
process-local: the durable runtime snapshot persists neither. A restored
|
|
1562
|
+
runtime therefore advertises the retry of a declared entry-event source
|
|
1563
|
+
immediately — that payload rides the persisted machine snapshot — while a
|
|
1564
|
+
module declaring no source advertises a retry again only after its next
|
|
1565
|
+
classified event.
|
|
1469
1566
|
|
|
1470
1567
|
## Abort
|
|
1471
1568
|
|
|
@@ -1479,7 +1576,61 @@ the shared `combineAbortSignals`). Classify a rejection as cancellation by its
|
|
|
1479
1576
|
causal identity with the applicable signal reason, not by an `AbortError` name
|
|
1480
1577
|
or by observing only that the signal is also aborted. Signals may carry an
|
|
1481
1578
|
ordinary `Error`, while a distinct transport or sink failure that occurs after
|
|
1482
|
-
abort remains a non-abort control error and takes precedence.
|
|
1579
|
+
abort remains a non-abort control error and takes precedence. Classification
|
|
1580
|
+
lives at each latch or report site, against the boundary signal applicable
|
|
1581
|
+
there — the invocation-lifetime combined signal, and during a resume that
|
|
1582
|
+
boundary's own signal — so a failure causally identical to the applicable
|
|
1583
|
+
reason is the cancellation's own evidence: it is handled there under the phase
|
|
1584
|
+
rules below, never mislabeled as a distinct failure and never carried to an
|
|
1585
|
+
unrelated later boundary
|
|
1586
|
+
([DR-036](../specs/decisions/036-coherent-abort-settlement.md)).
|
|
1587
|
+
A failure already latched as distinct retains that ownership; a later drain
|
|
1588
|
+
shall not reinterpret it against another boundary whose abort signal happens
|
|
1589
|
+
to use the same object as its reason.
|
|
1590
|
+
A public boundary settles on the machine's state at its quiescence point,
|
|
1591
|
+
in this precedence: a suspended pending call, then a distinct actor error,
|
|
1592
|
+
then terminal completion, then a coincident abort, then the recoverable
|
|
1593
|
+
failure state — a completed machine settles `terminal` even when the signal
|
|
1594
|
+
also aborted, because an `aborted` settlement over a terminal machine hides
|
|
1595
|
+
work the next turn would silently restart.
|
|
1596
|
+
An abort observed after the outcome is computed does not rewrite it, and a
|
|
1597
|
+
settlement-channel rejection causally identical to the abort reason is
|
|
1598
|
+
forgiven, so the returned result and the settlement trace state one fact.
|
|
1599
|
+
A boundary entered with an already-aborted signal delivers nothing.
|
|
1600
|
+
That entry refusal precedes the ordinary settlement order: a pre-aborted
|
|
1601
|
+
resume reports `aborted` while preserving its suspended pending call rather
|
|
1602
|
+
than reporting `suspended` for work it did not deliver.
|
|
1603
|
+
Cancellation-coupled channel rejections obey this phase matrix:
|
|
1604
|
+
|
|
1605
|
+
- **Before a host call or effect starts (and before apply acceptance):** an
|
|
1606
|
+
identical start-channel rejection starts no host call or effect and latches
|
|
1607
|
+
no control error. A recorded start receives one best-effort `aborted` finish.
|
|
1608
|
+
An ordinary run boundary then settles by the precedence above; a
|
|
1609
|
+
pre-acceptance `apply` instead rejects with that exact reason, records no
|
|
1610
|
+
receipt, and leaves its key reusable.
|
|
1611
|
+
- **After a host call or effect starts but before its finish or outcome is
|
|
1612
|
+
recorded:** an identical host, cleanup, observer, or in-flight-emission
|
|
1613
|
+
rejection is cancellation evidence. Invocation-owned cleanup completes, a
|
|
1614
|
+
started trace pair receives one `aborted` finish, and the ordinary boundary
|
|
1615
|
+
settles by the precedence above. A distinct rejection remains a control
|
|
1616
|
+
failure, produces the applicable error finish, and takes distinct-error
|
|
1617
|
+
precedence.
|
|
1618
|
+
- **After a call finish is recorded but before the enclosing non-apply outcome
|
|
1619
|
+
is computed:** an identical finish-sink or drain rejection leaves the
|
|
1620
|
+
recorded finish unchanged, emits no corrective second finish, latches
|
|
1621
|
+
nothing, and lets the enclosing boundary settle by the precedence above.
|
|
1622
|
+
- **After apply acceptance but before receipt publication:** every settlement
|
|
1623
|
+
failure, the exact apply abort reason included, is folded into the current
|
|
1624
|
+
`failed` receipt. Acceptance forbids throwing; the replacement receipt is
|
|
1625
|
+
published, returned, and replayed, and the failure is not carried as a later
|
|
1626
|
+
delivery error.
|
|
1627
|
+
- **After a non-apply outcome is computed or an apply receipt is published:**
|
|
1628
|
+
an identical rejection is dropped without rewriting the outcome or receipt
|
|
1629
|
+
and without poisoning a later boundary. A distinct non-apply settlement
|
|
1630
|
+
rejection retains current-boundary control-error precedence; a distinct
|
|
1631
|
+
post-publication apply rejection retains the published receipt and travels
|
|
1632
|
+
on the delivery-failure channel to the next boundary that drains.
|
|
1633
|
+
On abort, the
|
|
1483
1634
|
runtime shall not merely race the imperative
|
|
1484
1635
|
wait and return while an invocation remains live: it shall let the selected
|
|
1485
1636
|
rejection path settle and drive the actor to a quiescent state before returning
|
|
@@ -1543,12 +1694,13 @@ The `playbook.trace` copies are the host-agnostic runtime-boundary record requir
|
|
|
1543
1694
|
## Output
|
|
1544
1695
|
|
|
1545
1696
|
The link compiler emits one TypeScript module per playbook.
|
|
1546
|
-
For an FSM that declares no `type: 'parallel'` state
|
|
1547
|
-
|
|
1697
|
+
For an FSM that declares no `type: 'parallel'` state — necessarily flat
|
|
1698
|
+
under [gears2fsm.md](gears2fsm.md)'s one-state-per-item mapping — it shall
|
|
1699
|
+
emit the thin shared-factory module defined below.
|
|
1548
1700
|
For an FSM that declares a parallel state, it shall emit bespoke linked
|
|
1549
1701
|
machinery satisfying this document's runtime contract and shall not invoke
|
|
1550
|
-
`createXStatePlaybookRuntime`, whose supported domain is single-region
|
|
1551
|
-
under [DR-019](../specs/decisions/019-shared-linked-runtime-factory.md).
|
|
1702
|
+
`createXStatePlaybookRuntime`, whose supported domain is flat single-region
|
|
1703
|
+
FSMs under [DR-019](../specs/decisions/019-shared-linked-runtime-factory.md).
|
|
1552
1704
|
The FSM-interpreter machinery — actor wiring, boundary tracing, Boss-event
|
|
1553
1705
|
mapping, adjudication, script execution, nested-playbook bridging, session
|
|
1554
1706
|
lifecycle, abort handling, and the optional parked-session snapshot
|
|
@@ -1593,17 +1745,19 @@ The thin emitted module:
|
|
|
1593
1745
|
actor.
|
|
1594
1746
|
- Supplies in `spec` only what the factory cannot read from the FSM
|
|
1595
1747
|
artifact's own data: the deterministic textual entry event where
|
|
1596
|
-
§Boss-event mapping prescribes deterministic entry
|
|
1748
|
+
§Boss-event mapping prescribes deterministic entry, naming with it the FSM
|
|
1749
|
+
context member that event's own transition action copies the exact Boss
|
|
1750
|
+
text into wherever the machine keeps one, so the failure-state retry of
|
|
1751
|
+
§Control surface survives a restore; compact `bossEvents`
|
|
1597
1752
|
metadata for each additional Boss-union arm whose exact required/optional
|
|
1598
1753
|
judge fields, runtime-owned text fields, or closed string values disappear
|
|
1599
1754
|
under TypeScript erasure; `placeholderFields` only for authored token/field
|
|
1600
1755
|
exceptions not covered by the canonical kebab-token-to-camel-field mapping
|
|
1601
1756
|
and the canonical `<#>` → `irNumber` special case; the
|
|
1602
|
-
transition-event payload fields the FSM's Boss union declares;
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
source-derived `meta.playbook.player` (an empty map when there is no such
|
|
1757
|
+
transition-event payload fields the FSM's Boss union declares; the
|
|
1758
|
+
complete `roleStates` status map derived from every FSM state that invokes
|
|
1759
|
+
the typed `player` actor, with each `role` copied from that state's
|
|
1760
|
+
source-derived `meta.playbook.role` (an empty map when there is no such
|
|
1607
1761
|
state); the
|
|
1608
1762
|
`verbatimPayloadFields` set derived from annotated result fields above; the
|
|
1609
1763
|
`controlContextFields` projection of §Control surface; and any
|
|
@@ -1641,13 +1795,13 @@ The thin emitted module:
|
|
|
1641
1795
|
fields?: Readonly<Record<string, XStateBossEventFieldSpec>>;
|
|
1642
1796
|
}
|
|
1643
1797
|
|
|
1644
|
-
interface
|
|
1645
|
-
|
|
1798
|
+
interface XStateRoleStateStatus {
|
|
1799
|
+
role: string;
|
|
1646
1800
|
label: string;
|
|
1647
1801
|
}
|
|
1648
1802
|
|
|
1649
1803
|
bossEvents?: readonly XStateBossEventSpec[];
|
|
1650
|
-
|
|
1804
|
+
roleStates?: Readonly<Record<string, XStateRoleStateStatus>>;
|
|
1651
1805
|
placeholderFields?: Readonly<Record<string, string>>;
|
|
1652
1806
|
```
|
|
1653
1807
|
|
|
@@ -1663,8 +1817,8 @@ The thin emitted module:
|
|
|
1663
1817
|
runtime-owned arm to have lost payload detail under erasure shall report
|
|
1664
1818
|
that gap rather than emit the entry.
|
|
1665
1819
|
- Supplies `spec.compat` with the compatibility values current at link time:
|
|
1666
|
-
`{ artifactSchema, runtimeAbi }`, where `artifactSchema` is `
|
|
1667
|
-
schema number of the thin-module format this §Output defines — and
|
|
1820
|
+
`{ artifactSchema, runtimeAbi }`, where `artifactSchema` is `2` — the
|
|
1821
|
+
schema number of the local-role thin-module format this §Output defines — and
|
|
1668
1822
|
`runtimeAbi` is the installed shared engine's `RUNTIME_ABI` self-report.
|
|
1669
1823
|
The linker shall verify that the installed engine lists the emitted
|
|
1670
1824
|
schema in `SUPPORTED_ARTIFACT_SCHEMAS` and treat its absence as a
|
|
@@ -1675,9 +1829,17 @@ The thin emitted module:
|
|
|
1675
1829
|
declaration against the engine instance that actually loads the emitted
|
|
1676
1830
|
module and fails construction on a mismatch, so an artifact linked under
|
|
1677
1831
|
one engine cannot run silently skewed under another. Modules emitted
|
|
1678
|
-
before this contract carry no `compat` member and
|
|
1832
|
+
before this contract carry no `compat` member and shall reject before interpretation.
|
|
1833
|
+
- Requires the containing public registry manifest to advertise the same `artifactSchema: 2`; the Captain host shall reject a missing or disagreeing registry value before constructing this runtime, and a bespoke runtime profile shall advertise the same schema without claiming this shared factory's `runtimeAbi`.
|
|
1679
1834
|
- Default-exports the factory call as `createPlaybookRuntime`, typed
|
|
1680
|
-
`PlaybookRuntimeFactory<PlaybookRuntimeOptions>`.
|
|
1835
|
+
`PlaybookRuntimeFactory<PlaybookRuntimeOptions>`. A registry module loads
|
|
1836
|
+
dynamically inside the host's caught boundary, so its eager module-scope
|
|
1837
|
+
factory call fails fast there. The compiled session Captain module is the
|
|
1838
|
+
exception: the shell and both CLI front ends import it statically, so it
|
|
1839
|
+
shall defer its factory call to the first runtime request — an eager call
|
|
1840
|
+
would turn a future `spec.compat` rejection into an uncaught module-load
|
|
1841
|
+
error that takes even `--help` down, instead of the caught
|
|
1842
|
+
host-construction boundary's setup diagnostic.
|
|
1681
1843
|
- Exposes, under an `_internal` export, the pure helpers verification
|
|
1682
1844
|
needs — at least the prompt composers its own machine uses, which may
|
|
1683
1845
|
re-export the shared defaults when the spec does not override composition —
|
|
@@ -1693,7 +1855,7 @@ The thin emitted module:
|
|
|
1693
1855
|
speaks only `PlaybookPorts` for every agent and host concern; the
|
|
1694
1856
|
`node:child_process` dependency of §Script execution lives in the shared
|
|
1695
1857
|
factory, not in the emitted module.
|
|
1696
|
-
- Records the linker inputs (FSM path
|
|
1858
|
+
- Records the linker inputs (FSM path and strategies) in a
|
|
1697
1859
|
top-of-file header comment so the file is reproducible from the same
|
|
1698
1860
|
inputs.
|
|
1699
1861
|
- Sources the contract types (`PlayerResult`, `PlayerCallOptions`,
|
|
@@ -1781,7 +1943,7 @@ New behavior in any of these areas requires a separate slc spec.
|
|
|
1781
1943
|
|
|
1782
1944
|
## References
|
|
1783
1945
|
|
|
1784
|
-
[1]:
|
|
1785
|
-
[2]:
|
|
1946
|
+
[1]: text2gears.md "First phase: text → GEARS spec items."
|
|
1947
|
+
[2]: gears2fsm.md "Second phase: GEARS items → FSM artifact."
|
|
1786
1948
|
[3]: https://stately.ai/docs/actors "XState actors — `createActor`, snapshots, abort signal handling."
|
|
1787
1949
|
[4]: https://github.com/sindresorhus/p-queue#readme "p-queue concurrency and AbortSignal support."
|