@sublang/playbook 8.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 +3 -3
- package/docs/cli.md +15 -15
- package/docs/configuration.md +13 -8
- package/docs/embedding.md +7 -2
- package/package.json +1 -1
- package/reference/sdlc/captain.playbook/captain.playbook.js +14 -3
- package/reference/sdlc/captain.playbook/captain.playbook.ts +18 -4
- package/reference/sdlc/code.playbook/code.fsm.d.ts +4 -1
- package/reference/sdlc/code.playbook/code.fsm.js +11 -4
- package/reference/sdlc/code.playbook/code.fsm.ts +12 -4
- package/reference/sdlc/code.playbook/code.playbook.js +14 -3
- package/reference/sdlc/code.playbook/code.playbook.ts +13 -3
- package/reference/sdlc/code.playbook/playbook-captain.js +44 -10
- package/reference/sdlc/code.playbook/playbook-captain.ts +47 -10
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +2 -0
- package/reference/sdlc/decide.playbook/decide.playbook.js +299 -117
- package/reference/sdlc/decide.playbook/decide.playbook.ts +395 -131
- package/reference/sdlc/review.playbook/review.playbook.js +14 -3
- package/reference/sdlc/review.playbook/review.playbook.ts +13 -3
- package/slc/gears2fsm.md +19 -2
- package/slc/link.md +184 -42
- package/src/runtime.d.ts +1 -0
- package/src/runtime.ts +1 -0
- package/src/xstate-playbook-runtime.d.ts +13 -3
- package/src/xstate-playbook-runtime.js +732 -251
- package/src/xstate-playbook-runtime.ts +873 -280
- package/src/xstate-runtime.d.ts +17 -7
- package/src/xstate-runtime.js +135 -57
- package/src/xstate-runtime.ts +243 -84
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Adjudication: LLM judge per state; coderOutput and reviewerOutput are
|
|
10
10
|
// carried verbatim
|
|
11
11
|
// Compat: artifact schema 2 / runtime ABI 1
|
|
12
|
-
import {
|
|
12
|
+
import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
|
|
13
13
|
import { reviewMachine, } from './review.fsm.js';
|
|
14
14
|
const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
15
15
|
const CONTINUATION_PREAMBLE = 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
|
|
@@ -71,9 +71,20 @@ export const _internal = {
|
|
|
71
71
|
};
|
|
72
72
|
const runtimeSpec = {
|
|
73
73
|
label: 'REVIEW',
|
|
74
|
-
|
|
74
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
75
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
76
|
+
// which would follow whatever engine loads the module and make the
|
|
77
|
+
// factory's skew check compare that engine with itself.
|
|
78
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
75
79
|
snapshotOptions: snapshotReviewOptions,
|
|
76
|
-
entryEvent: {
|
|
80
|
+
entryEvent: {
|
|
81
|
+
type: 'START_REVIEW',
|
|
82
|
+
textField: 'callerInput',
|
|
83
|
+
// `copyStartInput` copies the entry text here, so the failure-state
|
|
84
|
+
// retry reads it back from the persisted machine snapshot and survives
|
|
85
|
+
// a continued session (DR-034).
|
|
86
|
+
contextField: 'callerInput',
|
|
87
|
+
},
|
|
77
88
|
roleStates: {
|
|
78
89
|
reviewInitial: {
|
|
79
90
|
role: 'reviewer',
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
// Compat: artifact schema 2 / runtime ABI 1
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
RUNTIME_ABI,
|
|
15
14
|
createXStatePlaybookRuntime,
|
|
16
15
|
snapshotJsonValue,
|
|
17
16
|
type PlaybookPlayerInput,
|
|
@@ -156,9 +155,20 @@ export const _internal = {
|
|
|
156
155
|
|
|
157
156
|
const runtimeSpec = {
|
|
158
157
|
label: 'REVIEW',
|
|
159
|
-
|
|
158
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
159
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
160
|
+
// which would follow whatever engine loads the module and make the
|
|
161
|
+
// factory's skew check compare that engine with itself.
|
|
162
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
160
163
|
snapshotOptions: snapshotReviewOptions,
|
|
161
|
-
entryEvent: {
|
|
164
|
+
entryEvent: {
|
|
165
|
+
type: 'START_REVIEW',
|
|
166
|
+
textField: 'callerInput',
|
|
167
|
+
// `copyStartInput` copies the entry text here, so the failure-state
|
|
168
|
+
// retry reads it back from the persisted machine snapshot and survives
|
|
169
|
+
// a continued session (DR-034).
|
|
170
|
+
contextField: 'callerInput',
|
|
171
|
+
},
|
|
162
172
|
roleStates: {
|
|
163
173
|
reviewInitial: {
|
|
164
174
|
role: 'reviewer',
|
package/slc/gears2fsm.md
CHANGED
|
@@ -244,8 +244,9 @@ Each state shall declare:
|
|
|
244
244
|
- a stable `id` (for `#id` targeting and Boss interrupts);
|
|
245
245
|
- an intuitive state key (the property name under `states: { ... }`);
|
|
246
246
|
- a one-line `description` (for inspector tools and documentation);
|
|
247
|
-
- JSON-safe `meta: { playbook: { stateId, description, role? } }`
|
|
248
|
-
|
|
247
|
+
- JSON-safe `meta: { playbook: { stateId, description, role? } }` naming the
|
|
248
|
+
state's public playbook identity per the identity rule below and repeating
|
|
249
|
+
its description so linked runtimes can discover active public
|
|
249
250
|
identities through `snapshot.getMeta()` without private XState nodes. A
|
|
250
251
|
delegated-role state shall also carry the canonical lowercase source role id in
|
|
251
252
|
`meta.playbook.role`; every other state shall omit `role`;
|
|
@@ -258,6 +259,8 @@ Each state shall declare:
|
|
|
258
259
|
and no `role`.
|
|
259
260
|
|
|
260
261
|
The source item ID shall live in `invoke.input.sourceItem`, not in a comment — this keeps the GEARS-to-state mapping machine-readable.
|
|
262
|
+
|
|
263
|
+
Outside a parallel group's regions, a state's `meta.playbook.stateId` shall equal its state key — the one identity a factory-backed linked runtime indexes by.
|
|
261
264
|
A delegated state's `invoke.input.role` shall match the canonical lowercase id of its source item's named role.
|
|
262
265
|
A direct Captain state shall not invent a `Captain` role binding.
|
|
263
266
|
|
|
@@ -748,6 +751,20 @@ an unhandled runtime error.
|
|
|
748
751
|
Every machine shall declare at least one `type: 'final'` state (typically `done`) reachable on completion.
|
|
749
752
|
A never-terminating machine is a defect: the runner has no completion signal.
|
|
750
753
|
|
|
754
|
+
At least one is a floor, not a ceiling. A final state's `description` is the
|
|
755
|
+
machine's published terminal meaning: a host that cannot read the machine's
|
|
756
|
+
output quotes that description to report what the run did. It shall therefore
|
|
757
|
+
be true of every arm that enters the state and of no other terminal outcome.
|
|
758
|
+
Where Source declares more than one terminal outcome — an approval that
|
|
759
|
+
completes the workflow and a failure the workflow reports to its caller
|
|
760
|
+
instead of parking — each outcome shall get its own `type: 'final'` state
|
|
761
|
+
whose description names it. Routing an approval arm and a failure, abort, or
|
|
762
|
+
invalid-result arm into one final state is a defect of the same kind as a
|
|
763
|
+
wrong result field, because the quoting host cannot detect the difference.
|
|
764
|
+
This constrains only published meaning: the declared machine `output` still
|
|
765
|
+
derives its status and fields from typed context, so a caller that does read
|
|
766
|
+
the output is unaffected.
|
|
767
|
+
|
|
751
768
|
Where Source declares a JSON-safe terminal result, the setup types shall
|
|
752
769
|
declare that output and the root machine shall derive it from typed context
|
|
753
770
|
through XState's machine `output` function. A final-state transition alone does
|
package/slc/link.md
CHANGED
|
@@ -151,6 +151,7 @@ type PlaybookRunResult =
|
|
|
151
151
|
| {
|
|
152
152
|
outcome: 'terminal';
|
|
153
153
|
state: PlaybookState;
|
|
154
|
+
stateDescription?: string;
|
|
154
155
|
output?: JsonValue;
|
|
155
156
|
}
|
|
156
157
|
| {
|
|
@@ -184,8 +185,10 @@ differ from both its `rootSessionId` and `parentSessionId`.
|
|
|
184
185
|
Run outcomes are exact: `no-action` means no FSM event was sent;
|
|
185
186
|
`quiescent` means a non-failure parked/idle state; `failed` means the FSM is in
|
|
186
187
|
a recoverable failure state; `terminal` means top-level final with optional
|
|
187
|
-
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
|
|
188
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)).
|
|
189
192
|
Control-plane exceptions reject the runtime method rather than masquerade as a
|
|
190
193
|
recoverable workflow `failed` result.
|
|
191
194
|
|
|
@@ -494,6 +497,15 @@ If a started-boundary sink records the event and then rejects, the runtime
|
|
|
494
497
|
shall make one best-effort normalized error-finish attempt with the same call
|
|
495
498
|
id and then reject the original start error. It shall not retry either event or
|
|
496
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)).
|
|
497
509
|
When a call boundary carries `callId`, that id shall be unique within the
|
|
498
510
|
runtime session. A stable FSM `stateId` is identity metadata in the payload,
|
|
499
511
|
not a call id and shall not be reused as one across repeated invocations.
|
|
@@ -658,10 +670,14 @@ nonconformant.
|
|
|
658
670
|
The FSM's `events` union enumerates every Boss-originated event.
|
|
659
671
|
The runtime receives Boss input as a free-form string
|
|
660
672
|
(`handleBossInput.text`).
|
|
661
|
-
Where the current ready or reconstructed
|
|
673
|
+
Where the current ready, recoverable-failure (`failed`), or reconstructed
|
|
674
|
+
terminal machine accepts exactly one
|
|
662
675
|
ordinary textual entry event and no Boss question is pending, the runtime
|
|
663
676
|
shall send that event deterministically and attach the exact original text to
|
|
664
|
-
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.
|
|
665
681
|
The default Captain — the controller playbook of
|
|
666
682
|
[gears2fsm "Setup"](gears2fsm.md#setup) — is deterministic at every parked
|
|
667
683
|
entry: the runtime maps each Boss turn from the exact text and the host's
|
|
@@ -937,10 +953,34 @@ The provided actor shall:
|
|
|
937
953
|
`{ guard: <first declared guard>, exitStatus: 0 }`; any nonzero status
|
|
938
954
|
resolves the second declared guard with that status. Guard selection is
|
|
939
955
|
mechanical; the runtime shall not route script output through the judge.
|
|
940
|
-
- Reject
|
|
941
|
-
state's ordinary `onError` path.
|
|
942
|
-
|
|
943
|
-
|
|
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.
|
|
944
984
|
- Emit, after the child settles and before the invocation resolves, one status
|
|
945
985
|
line `Executed script for <stateId> (exit <status>).` and one telemetry
|
|
946
986
|
event under topic `playbook.script` with payload
|
|
@@ -965,14 +1005,18 @@ XState `.provide(...)` receives the exact declared actor input rather than a
|
|
|
965
1005
|
structurally similar local type.
|
|
966
1006
|
Construct one bridge per runtime and wire every integration hook: allocate ids
|
|
967
1007
|
with `nextCallId`; return the currently active public-boundary signal from
|
|
968
|
-
`getBoundarySignal`;
|
|
969
|
-
|
|
970
|
-
through `
|
|
971
|
-
`drain`;
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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.
|
|
976
1020
|
On invocation the bridge allocates a runtime-local call id, traces the start,
|
|
977
1021
|
and calls `PlaybookPorts.callPlaybook` with the composed target/text and the
|
|
978
1022
|
bridge signal combined from the XState invocation lifetime, the active public
|
|
@@ -1057,12 +1101,15 @@ registry; linker-time metadata is not authorization to call a target.
|
|
|
1057
1101
|
|
|
1058
1102
|
Disposal shall settle an outstanding call as aborted and drain its finish
|
|
1059
1103
|
trace before `session.disposed`.
|
|
1060
|
-
If registered child abort cleanup rejects
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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.
|
|
1066
1113
|
Child output and errors must be JSON-safe; a non-JSON-safe result is a
|
|
1067
1114
|
control-plane error.
|
|
1068
1115
|
|
|
@@ -1140,6 +1187,12 @@ The `PlaybookRuntime` shall:
|
|
|
1140
1187
|
transition-trace or telemetry sink failure is part of `init`: initialization
|
|
1141
1188
|
shall reject, stop the actor, and perform the failed-start cleanup below
|
|
1142
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)).
|
|
1143
1196
|
Where the FSM input declares `selfPlaybookId`, seed it from the immutable
|
|
1144
1197
|
`session.playbookId`; do not expose a caller option or reuse a working leaf's
|
|
1145
1198
|
`stateId` as the self-call identity.
|
|
@@ -1193,7 +1246,14 @@ The `PlaybookRuntime` shall:
|
|
|
1193
1246
|
transition/status/telemetry queue before returning, just as
|
|
1194
1247
|
`handleBossInput` does. A resume shall not allocate a new Boss-input
|
|
1195
1248
|
`turnId`; retain the original call-start turn id for its matching finish and
|
|
1196
|
-
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
|
|
1197
1257
|
exceptional path shall drain ordered emissions, select the first latched
|
|
1198
1258
|
non-abort control error before considering abort, and clear its boundary
|
|
1199
1259
|
latches in `finally`, so a failed resume cannot leak an emission error into a
|
|
@@ -1246,9 +1306,14 @@ transition first.
|
|
|
1246
1306
|
|
|
1247
1307
|
If a `*.call.started` trace records and then its sink rejects, no host call may
|
|
1248
1308
|
begin. The runtime shall still enqueue exactly one synthetic paired
|
|
1249
|
-
`*.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
|
|
1250
1314
|
id, turn id, actor visibility, state/source identity, and prompt or request
|
|
1251
|
-
metadata from the start boundary.
|
|
1315
|
+
metadata from the start boundary. A distinct rejection shall then follow the
|
|
1316
|
+
same latched
|
|
1252
1317
|
control-error, FSM settlement, and ordered-drain path as any other call-start
|
|
1253
1318
|
failure; the synthetic finish must not replace the original sink error.
|
|
1254
1319
|
|
|
@@ -1421,11 +1486,18 @@ still describes the state. Two families exist, labeled from source state
|
|
|
1421
1486
|
descriptions:
|
|
1422
1487
|
|
|
1423
1488
|
- **Failure-state retry** — while the singular state id is the recoverable
|
|
1424
|
-
failure state and the
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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.
|
|
1429
1501
|
- **Jump entries** — for each registered resumable state id whose
|
|
1430
1502
|
explicit-state-jump event (`BOSS_INTERRUPT` with that `targetId`, optional
|
|
1431
1503
|
textual fields omitted) the live snapshot accepts, guards included, the
|
|
@@ -1480,12 +1552,17 @@ trace and the return agree, and a receipt states what happened to the effect
|
|
|
1480
1552
|
rather than what happened to its telemetry. The published receipt stands, is
|
|
1481
1553
|
returned and replayed verbatim, and the delivery failure travels on the
|
|
1482
1554
|
runtime's emission-failure channel to surface from the next public boundary
|
|
1483
|
-
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)).
|
|
1484
1559
|
|
|
1485
1560
|
The recorded receipts and the recorded last classified event are
|
|
1486
|
-
process-local: the durable runtime snapshot persists neither
|
|
1487
|
-
|
|
1488
|
-
|
|
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.
|
|
1489
1566
|
|
|
1490
1567
|
## Abort
|
|
1491
1568
|
|
|
@@ -1499,7 +1576,61 @@ the shared `combineAbortSignals`). Classify a rejection as cancellation by its
|
|
|
1499
1576
|
causal identity with the applicable signal reason, not by an `AbortError` name
|
|
1500
1577
|
or by observing only that the signal is also aborted. Signals may carry an
|
|
1501
1578
|
ordinary `Error`, while a distinct transport or sink failure that occurs after
|
|
1502
|
-
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
|
|
1503
1634
|
runtime shall not merely race the imperative
|
|
1504
1635
|
wait and return while an invocation remains live: it shall let the selected
|
|
1505
1636
|
rejection path settle and drive the actor to a quiescent state before returning
|
|
@@ -1563,12 +1694,13 @@ The `playbook.trace` copies are the host-agnostic runtime-boundary record requir
|
|
|
1563
1694
|
## Output
|
|
1564
1695
|
|
|
1565
1696
|
The link compiler emits one TypeScript module per playbook.
|
|
1566
|
-
For an FSM that declares no `type: 'parallel'` state
|
|
1567
|
-
|
|
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.
|
|
1568
1700
|
For an FSM that declares a parallel state, it shall emit bespoke linked
|
|
1569
1701
|
machinery satisfying this document's runtime contract and shall not invoke
|
|
1570
|
-
`createXStatePlaybookRuntime`, whose supported domain is single-region
|
|
1571
|
-
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).
|
|
1572
1704
|
The FSM-interpreter machinery — actor wiring, boundary tracing, Boss-event
|
|
1573
1705
|
mapping, adjudication, script execution, nested-playbook bridging, session
|
|
1574
1706
|
lifecycle, abort handling, and the optional parked-session snapshot
|
|
@@ -1613,7 +1745,10 @@ The thin emitted module:
|
|
|
1613
1745
|
actor.
|
|
1614
1746
|
- Supplies in `spec` only what the factory cannot read from the FSM
|
|
1615
1747
|
artifact's own data: the deterministic textual entry event where
|
|
1616
|
-
§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`
|
|
1617
1752
|
metadata for each additional Boss-union arm whose exact required/optional
|
|
1618
1753
|
judge fields, runtime-owned text fields, or closed string values disappear
|
|
1619
1754
|
under TypeScript erasure; `placeholderFields` only for authored token/field
|
|
@@ -1697,7 +1832,14 @@ The thin emitted module:
|
|
|
1697
1832
|
before this contract carry no `compat` member and shall reject before interpretation.
|
|
1698
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`.
|
|
1699
1834
|
- Default-exports the factory call as `createPlaybookRuntime`, typed
|
|
1700
|
-
`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.
|
|
1701
1843
|
- Exposes, under an `_internal` export, the pure helpers verification
|
|
1702
1844
|
needs — at least the prompt composers its own machine uses, which may
|
|
1703
1845
|
re-export the shared defaults when the spec does not override composition —
|
|
@@ -1801,7 +1943,7 @@ New behavior in any of these areas requires a separate slc spec.
|
|
|
1801
1943
|
|
|
1802
1944
|
## References
|
|
1803
1945
|
|
|
1804
|
-
[1]:
|
|
1805
|
-
[2]:
|
|
1946
|
+
[1]: text2gears.md "First phase: text → GEARS spec items."
|
|
1947
|
+
[2]: gears2fsm.md "Second phase: GEARS items → FSM artifact."
|
|
1806
1948
|
[3]: https://stately.ai/docs/actors "XState actors — `createActor`, snapshots, abort signal handling."
|
|
1807
1949
|
[4]: https://github.com/sindresorhus/p-queue#readme "p-queue concurrency and AbortSignal support."
|
package/src/runtime.d.ts
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -168,6 +168,15 @@ export interface XStatePlaybookRuntimeSpec<TOptions> {
|
|
|
168
168
|
entryEvent?: {
|
|
169
169
|
type: string;
|
|
170
170
|
textField: string;
|
|
171
|
+
/**
|
|
172
|
+
* DR-034: the FSM context member this machine's entry action copies the
|
|
173
|
+
* exact Boss text into. Where it is named, the failure-state retry
|
|
174
|
+
* builds its payload from that member of the live snapshot instead of
|
|
175
|
+
* from the process-local recorded event, so the action derives the same
|
|
176
|
+
* before and after `restore`. Absent: the recorded event stays the
|
|
177
|
+
* source and the action lives only as long as the process.
|
|
178
|
+
*/
|
|
179
|
+
contextField?: string;
|
|
171
180
|
};
|
|
172
181
|
/**
|
|
173
182
|
* Exact flat Boss-event contracts whose non-text fields the judge may
|
|
@@ -313,9 +322,10 @@ export declare function stateDescriptionsFromMachine(machine: AnyStateMachine):
|
|
|
313
322
|
* (literal and dynamic) — and implements the full runtime lifecycle including
|
|
314
323
|
* the optional parked-session snapshot capability (DR-014).
|
|
315
324
|
*
|
|
316
|
-
* Scope: machines
|
|
317
|
-
*
|
|
318
|
-
*
|
|
325
|
+
* Scope: flat single-region machines — no parallel state, no compound
|
|
326
|
+
* child states, and every root state's `meta.playbook.stateId` equal to its
|
|
327
|
+
* state key — so each snapshot exposes exactly one playbook state id.
|
|
328
|
+
* Parallel-region FSMs keep their own linked runtimes.
|
|
319
329
|
*/
|
|
320
330
|
export declare function createXStatePlaybookRuntime<TOptions>(machine: AnyStateMachine, spec: XStatePlaybookRuntimeSpec<TOptions>): PlaybookRuntimeFactory<TOptions>;
|
|
321
331
|
export {};
|