@fluidgrids/workflows 2026.1027.0 → 2026.1028.1

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 (168) hide show
  1. package/README.md +170 -18
  2. package/dist/activation-BBh1p82z.d.cts +57 -0
  3. package/dist/activation-DnhG7SmF.d.ts +57 -0
  4. package/dist/assemble-BLqQ8lSu.d.cts +39 -0
  5. package/dist/assemble-DWgaXHd-.d.ts +39 -0
  6. package/dist/chunk-2DP2Y6EU.cjs +832 -0
  7. package/dist/chunk-2J6TF7YI.cjs +86 -0
  8. package/dist/chunk-3FOTFQ3J.js +1622 -0
  9. package/dist/chunk-4LYSEANA.js +199 -0
  10. package/dist/{chunk-KUTMY2IO.cjs → chunk-57MSZ24T.cjs} +162 -98
  11. package/dist/{chunk-BWSLWF2E.js → chunk-5HDSBTBX.js} +99 -32
  12. package/dist/chunk-623FVQSN.cjs +251 -0
  13. package/dist/{chunk-4DPEGDPW.js → chunk-6FSHT5IL.js} +4 -4
  14. package/dist/{chunk-REV7ZOA4.js → chunk-7AAZGVKD.js} +1 -35
  15. package/dist/chunk-7NQ37GZU.js +73 -0
  16. package/dist/chunk-ABFZWYHT.js +612 -0
  17. package/dist/{chunk-SS4LT2IP.js → chunk-BD7ZPG53.js} +5 -10
  18. package/dist/chunk-BO6OVILK.js +79 -0
  19. package/dist/chunk-C5OLWZ42.js +245 -0
  20. package/dist/chunk-CBL5JSCC.cjs +75 -0
  21. package/dist/{chunk-LSKSBVL4.cjs → chunk-CPDK3K5N.cjs} +6 -6
  22. package/dist/chunk-CSNWNFKU.cjs +1635 -0
  23. package/dist/chunk-DJYMDLV5.js +227 -0
  24. package/dist/{chunk-X4HKU3MX.js → chunk-E2VK7J2C.js} +3 -1
  25. package/dist/chunk-FMSW2HDF.cjs +267 -0
  26. package/dist/chunk-FQIHX7YI.cjs +80 -0
  27. package/dist/chunk-FR7KYWRO.cjs +41 -0
  28. package/dist/{chunk-AU3YW7FZ.cjs → chunk-GBQ33SIZ.cjs} +46 -51
  29. package/dist/{chunk-RRQ5HOBF.cjs → chunk-GVTZMA5C.cjs} +4 -4
  30. package/dist/chunk-H7KEVW2M.js +816 -0
  31. package/dist/chunk-HR3BN4C4.cjs +27 -0
  32. package/dist/chunk-IISBRUWJ.js +13 -0
  33. package/dist/chunk-JD2VQIDZ.cjs +15 -0
  34. package/dist/{chunk-PL73GJR4.js → chunk-JZ4TOOLN.js} +1 -1
  35. package/dist/chunk-L24YIZY7.cjs +207 -0
  36. package/dist/chunk-LNUQE6D6.js +39 -0
  37. package/dist/{chunk-BYJ7QTLY.cjs → chunk-NEIUQASE.cjs} +13 -13
  38. package/dist/chunk-P6TA5EZT.cjs +634 -0
  39. package/dist/chunk-PDHLTDZB.js +72 -0
  40. package/dist/chunk-PMCM2OH4.js +46 -0
  41. package/dist/chunk-PZCVF76Q.cjs +868 -0
  42. package/dist/{chunk-GO34UHSY.js → chunk-QAN5MZEM.js} +3 -3
  43. package/dist/{chunk-27HASETB.cjs → chunk-QKZL3ONM.cjs} +3 -1
  44. package/dist/chunk-SAENRNBJ.cjs +52 -0
  45. package/dist/{chunk-LMX35GNQ.js → chunk-SMCANK3F.js} +5 -4
  46. package/dist/{chunk-IZQNUNSU.cjs → chunk-SX2PNKKT.cjs} +18 -17
  47. package/dist/{chunk-LRM6JXJA.cjs → chunk-TBDXTV22.cjs} +0 -37
  48. package/dist/chunk-VOKMAAJP.js +848 -0
  49. package/dist/chunk-XB53G2DK.js +25 -0
  50. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  51. package/dist/codegen/index.cjs +9 -7
  52. package/dist/codegen/index.d.cts +2 -2
  53. package/dist/codegen/index.d.ts +2 -2
  54. package/dist/codegen/index.js +6 -5
  55. package/dist/file-BOoHZgoQ.d.cts +1557 -0
  56. package/dist/file-yuPCFU5b.d.ts +1557 -0
  57. package/dist/history/index.cjs +287 -0
  58. package/dist/history/index.d.cts +157 -0
  59. package/dist/history/index.d.ts +157 -0
  60. package/dist/history/index.js +5 -0
  61. package/dist/index.cjs +134 -36
  62. package/dist/index.d.cts +137 -9
  63. package/dist/index.d.ts +137 -9
  64. package/dist/index.js +8 -3
  65. package/dist/ir/index.cjs +161 -165
  66. package/dist/ir/index.d.cts +32 -12
  67. package/dist/ir/index.d.ts +32 -12
  68. package/dist/ir/index.js +54 -61
  69. package/dist/lower-BuSvAFqi.d.cts +129 -0
  70. package/dist/lower-C_Qv_34S.d.ts +129 -0
  71. package/dist/lower-types-nofRXw9c.d.cts +15 -0
  72. package/dist/lower-types-nofRXw9c.d.ts +15 -0
  73. package/dist/{manifest-Duf-gHdO.d.cts → manifest-0OLw0i7k.d.cts} +1 -1
  74. package/dist/{manifest-Bp9ZJlav.d.ts → manifest-C8N6HDYl.d.ts} +1 -1
  75. package/dist/node-evaluator-CJNP4GHX.js +12 -0
  76. package/dist/node-evaluator-DMKPKUWA.cjs +22 -0
  77. package/dist/quickjs/index.cjs +256 -78
  78. package/dist/quickjs/index.d.cts +48 -7
  79. package/dist/quickjs/index.d.ts +48 -7
  80. package/dist/quickjs/index.js +254 -79
  81. package/dist/roundtrip/index.cjs +404 -129
  82. package/dist/roundtrip/index.d.cts +43 -8
  83. package/dist/roundtrip/index.d.ts +43 -8
  84. package/dist/roundtrip/index.js +260 -19
  85. package/dist/runtime/index.cjs +90 -0
  86. package/dist/runtime/index.d.cts +297 -0
  87. package/dist/runtime/index.d.ts +297 -0
  88. package/dist/runtime/index.js +8 -0
  89. package/dist/testing/index.cjs +1057 -0
  90. package/dist/testing/index.d.cts +305 -0
  91. package/dist/testing/index.d.ts +305 -0
  92. package/dist/testing/index.js +1037 -0
  93. package/dist/{types-Dr2Fn1Ax.d.ts → types-BGoPzgcl.d.cts} +1 -1
  94. package/dist/{types-Dr2Fn1Ax.d.cts → types-BGoPzgcl.d.ts} +1 -1
  95. package/dist/types-CpBBjaGX.d.cts +380 -0
  96. package/dist/types-DV-HX_Gt.d.ts +380 -0
  97. package/dist/{assemble-DMOuaxRI.d.ts → workflow-Bpr3vnA_.d.ts} +60 -162
  98. package/dist/workflow-C2a05WUJ.d.ts +444 -0
  99. package/dist/{assemble-BJo9TECS.d.cts → workflow-FXUkpIBc.d.cts} +60 -162
  100. package/dist/workflow-J-yYixny.d.cts +444 -0
  101. package/fixtures/README.md +128 -0
  102. package/fixtures/histories/activity-failure-fails-run.history.json +163 -0
  103. package/fixtures/histories/activity-retry-then-success.history.json +147 -0
  104. package/fixtures/histories/cancel-activity-abandon.history.json +126 -0
  105. package/fixtures/histories/cancel-activity-compensate.history.json +307 -0
  106. package/fixtures/histories/cancel-concurrent-non-cancellable.history.json +234 -0
  107. package/fixtures/histories/cancel-concurrent-scope-timeout.history.json +184 -0
  108. package/fixtures/histories/cancel-condition-after-cancel.history.json +200 -0
  109. package/fixtures/histories/cancel-during-timer.history.json +123 -0
  110. package/fixtures/histories/cancel-saga-compensations.history.json +325 -0
  111. package/fixtures/histories/cancel-scope-timeout.history.json +148 -0
  112. package/fixtures/histories/child-abandon-outlives-parent.history.json +191 -0
  113. package/fixtures/histories/child-cancel-default.history.json +234 -0
  114. package/fixtures/histories/child-cancel-request.history.json +206 -0
  115. package/fixtures/histories/child-durable-child-side.history.json +124 -0
  116. package/fixtures/histories/child-durable-result.history.json +163 -0
  117. package/fixtures/histories/child-failure-caught.history.json +166 -0
  118. package/fixtures/histories/child-graph-by-key.history.json +138 -0
  119. package/fixtures/histories/child-terminated-by-parent-close.history.json +85 -0
  120. package/fixtures/histories/continue-as-new-first-run.history.json +146 -0
  121. package/fixtures/histories/continue-as-new-history-limit.history.json +489 -0
  122. package/fixtures/histories/continue-as-new-last-run.history.json +147 -0
  123. package/fixtures/histories/continue-as-new-suggested.history.json +352 -0
  124. package/fixtures/histories/details-and-context.history.json +176 -0
  125. package/fixtures/histories/deterministic-values.history.json +128 -0
  126. package/fixtures/histories/engine-builtins.history.json +172 -0
  127. package/fixtures/histories/gather-activities.history.json +291 -0
  128. package/fixtures/histories/gather-all-settled.history.json +219 -0
  129. package/fixtures/histories/gather-mixed.history.json +281 -0
  130. package/fixtures/histories/input-during-running-task.history.json +111 -0
  131. package/fixtures/histories/nondeterminism-reordered.history.json +218 -0
  132. package/fixtures/histories/patched-deprecated.history.json +271 -0
  133. package/fixtures/histories/patched-new-run.history.json +270 -0
  134. package/fixtures/histories/patched-old-history.history.json +190 -0
  135. package/fixtures/histories/race-activity-wins.history.json +152 -0
  136. package/fixtures/histories/race-signal-beats-child.history.json +161 -0
  137. package/fixtures/histories/race-timeout-wins.history.json +118 -0
  138. package/fixtures/histories/side-effect-recorded.history.json +153 -0
  139. package/fixtures/histories/signal-buffered-at-start.history.json +157 -0
  140. package/fixtures/histories/signal-default-handler.history.json +187 -0
  141. package/fixtures/histories/signal-handler.history.json +191 -0
  142. package/fixtures/histories/signal-parent-child-side.history.json +132 -0
  143. package/fixtures/histories/signal-parent.history.json +202 -0
  144. package/fixtures/histories/signal-run-delivered.history.json +128 -0
  145. package/fixtures/histories/signal-run-undelivered.history.json +134 -0
  146. package/fixtures/histories/signal-wait.history.json +111 -0
  147. package/fixtures/histories/task-failure-code-error.history.json +137 -0
  148. package/fixtures/histories/task-failure-then-transient-attempt.history.json +140 -0
  149. package/fixtures/histories/timer-condition-met.history.json +201 -0
  150. package/fixtures/histories/timer-condition-timeout.history.json +118 -0
  151. package/fixtures/histories/timer-sleep-once.history.json +118 -0
  152. package/fixtures/histories/timer-sleep-sequence.history.json +210 -0
  153. package/fixtures/histories/update-accepted-completed.history.json +149 -0
  154. package/fixtures/histories/update-with-start-rejected-validation.history.json +91 -0
  155. package/package.json +85 -20
  156. package/schema/history-file.schema.json +2544 -0
  157. package/schema/ir-v2.schema.json +16 -0
  158. package/schema/run-event.schema.json +2374 -0
  159. package/schema/workflow-command.schema.json +1095 -0
  160. package/schema/workflow-task.schema.json +3560 -0
  161. package/dist/chunk-265J2FCH.cjs +0 -33
  162. package/dist/chunk-LUXCXFOE.cjs +0 -269
  163. package/dist/chunk-S34NJE3A.js +0 -262
  164. package/dist/chunk-SRQRLWOS.js +0 -31
  165. package/dist/lower-DcOY8ux0.d.cts +0 -49
  166. package/dist/lower-Dw5grNrI.d.ts +0 -49
  167. package/dist/node-evaluator-2GTUZVPK.cjs +0 -17
  168. package/dist/node-evaluator-RMYEWCXZ.js +0 -8
package/README.md CHANGED
@@ -1,18 +1,23 @@
1
1
  # @fluidgrids/workflows
2
2
 
3
- Author [FluidGrids](https://fluidgrids.ai) workflows as code. This package is
4
- Phase A of the code-first workflows spec set (BOFF-9902 / BOFF-9904,
5
- `fluidgrids-specs/product/specs/code-first/`):
3
+ Author [FluidGrids](https://fluidgrids.ai) workflows as code. This package
4
+ implements Phases A and B of the code-first workflows spec set (BOFF-9902,
5
+ BOFF-9904, BOFF-9905; `fluidgrids-specs/product/specs/code-first/`):
6
6
 
7
7
  - the workflow IR, schema version 2: types, JSON Schema 2020-12, normalisation,
8
8
  validation, hashing, diff, interface derivation and the export envelope;
9
9
  - the tier-1 graph builder: TypeScript that constructs a canvas workflow;
10
+ - tier-2 durable workflow functions (`workflow()`), executed by replay;
11
+ - the durable-function contract shared with the service, the workers and the
12
+ Python SDK: the runtime ABI, the history, command and task JSON Schemas, the
13
+ command-to-event state machine, the retry policy and the activity allow-list;
14
+ - the QuickJS workflow task runner, bundles with vendored pure-ESM
15
+ dependencies and the source archive;
16
+ - a test environment, replay and the replay conformance fixtures;
10
17
  - lift (IR to TypeScript or Python) and lower (TypeScript to IR), with a
11
18
  fidelity report computed by actually lowering the code;
12
19
  - typed node clients generated from a node registry snapshot.
13
20
 
14
- Durable (tier-2) workflow functions are Phase B and are not in this package yet.
15
-
16
21
  ## Install
17
22
 
18
23
  ```bash
@@ -21,14 +26,18 @@ bun add @fluidgrids/workflows
21
26
 
22
27
  ## Entry points
23
28
 
24
- | Import | Contents | Runs in |
25
- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
26
- | `@fluidgrids/workflows` | `defineWorkflow`, `triggers`, `nodes`, `edge`, `expr`, `and`, `or`, `op`, `trigger`, `toIR` | anywhere; also bundled into workflow code |
27
- | `@fluidgrids/workflows/ir` | IR v2 types, `IR_V2_SCHEMA`, `normalizeDefinition`, `validate`, `irHash`, `canonicalJson`, `diffDefinitions`, `deriveInterface`, envelope helpers | anywhere, browser included |
28
- | `@fluidgrids/workflows/roundtrip` | `liftToTypeScript`, `liftToPython`, `lower`, `exportWorkflowCode`, `checkFidelity` | Node or Bun |
29
- | `@fluidgrids/workflows/codegen` | `generateNodeClients(manifests, 'ts' \| 'py')` | anywhere |
30
- | `@fluidgrids/workflows/quickjs` | `createQuickJSEvaluator()` for lowering untrusted code | Node or Bun |
31
- | `@fluidgrids/workflows/schema/ir-v2.schema.json` | the JSON Schema | data |
29
+ | Import | Contents | Runs in |
30
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
31
+ | `@fluidgrids/workflows` | `defineWorkflow`, `triggers`, `nodes`, `edge`, `expr`, `and`, `or`, `op`, `trigger`, `toIR`; `workflow`, `defineSignal`, `defineQuery`, `defineUpdate`, the failure classes | anywhere; also bundled into workflow code |
32
+ | `@fluidgrids/workflows/history` | `EVENT_TYPES`, `COMMAND_TYPES`, `TASK_FAILURE_CAUSES`, the JSON Schemas, `validateCommands`, `eventsForCommands`, `projectRunState`, `splitActivations`, `computeRetryDelay`, `ACTIVITY_NODE_ALLOWLIST`, `assertActivityNodeType`, history files | Node or Bun |
33
+ | `@fluidgrids/workflows/runtime` | the runtime ABI (`createWorkflowRuntime`, `installWorkflowRuntime`), the scheduler, the determinism shims | anywhere; bundled into workflow code |
34
+ | `@fluidgrids/workflows/testing` | `TestWorkflowEnvironment`, `replay`, `assertHistory`, `expectIR`, `simulate`, the `vm` task runner | Node or Bun |
35
+ | `@fluidgrids/workflows/ir` | IR v2 types, `IR_V2_SCHEMA`, `normalizeDefinition`, `validate`, `irHash`, `canonicalJson`, `diffDefinitions`, `deriveInterface`, envelope helpers | anywhere, browser included |
36
+ | `@fluidgrids/workflows/roundtrip` | `liftToTypeScript`, `liftToPython`, `lower`, `exportWorkflowCode`, `checkFidelity`, `buildBundle`, `vendorDependencies`, `packSources`, `unpackSources` | Node or Bun |
37
+ | `@fluidgrids/workflows/codegen` | `generateNodeClients(manifests, 'ts' \| 'py')` | anywhere |
38
+ | `@fluidgrids/workflows/quickjs` | `createQuickJSEvaluator()` for lowering untrusted code, `createWorkflowTaskRunner()` for workflow tasks | Node or Bun |
39
+ | `@fluidgrids/workflows/schema/*.schema.json` | `ir-v2`, `run-event`, `workflow-command`, `workflow-task`, `history-file` | data |
40
+ | `@fluidgrids/workflows/fixtures/histories/*` | the replay conformance histories | data |
32
41
 
33
42
  ## Tier 1: the graph builder
34
43
 
@@ -79,8 +88,16 @@ calls, so the same source always gives the same IR.
79
88
  becomes `{{ $json.x }}`. Comparisons (`gte`, `eq`, `contains`, `isEmpty`, …,
80
89
  `and`, `or`) become the condition tree `internal:if` evaluates.
81
90
  - `connection: 'name'` resolves to a connection id when the workflow is pushed.
91
+ - Lowering must not read the clock or a random source while the workflow is
92
+ defined: `Date.now()`, `new Date()` / `Date()` without arguments,
93
+ `Math.random()`, `crypto.getRandomValues()` / `crypto.randomUUID()` and an
94
+ argument-less `Intl.DateTimeFormat` `format()` throw a `DeterminismViolation`
95
+ error in both evaluators. Use `wf.now` and other references for run-time values.
82
96
  - Positions come from the layout sidecar `workflows/<key>.layout.json`, or the
83
97
  automatic layout the AI draft generator uses.
98
+ - An edge without an explicit `id` gets `e-<source>-<target>[-<sourceHandle>][-<targetHandle>]`.
99
+ Ids may contain `-`, so two edges can share that default; the later ones get
100
+ `-2`, `-3`, … in declaration order. Only a repeated explicit id is an error.
84
101
  - `edge(source, { id, type, targetHandle, label, mapping, extra })`,
85
102
  `wf.connect`, `wf.edgeOrderIs` and the `extra` option on every node keep any
86
103
  stored definition expressible exactly.
@@ -116,14 +133,23 @@ const { buildId, workflows } = await lower(files, { connections });
116
133
  evaluates the bundle in an isolate. `buildId` is the SHA-256 of the bundle; it
117
134
  is identical with `esbuild` and `esbuild-wasm` of the same version, so the CLI
118
135
  and the service agree. Workflow code may import project files and
119
- `@fluidgrids/workflows` only.
136
+ `@fluidgrids/workflows` only. The sources are limited to `maxSourceBytes`
137
+ (default 1 MiB) before esbuild runs and the bundle to `maxBundleBytes`
138
+ (default 1.5 MiB) before it is evaluated; a `LowerError` with
139
+ `reason: 'too-large'` reports either.
120
140
  - **Evaluators**: the default is a fresh Node `vm` context (for the CLI, which
121
141
  runs the developer's own code). Services use `createQuickJSEvaluator()` from
122
142
  `@fluidgrids/workflows/quickjs` (quickjs-emscripten 0.32.0, the engine the
123
- workers use), which bounds memory and time.
143
+ workers use), which bounds memory exactly. Both evaluate in a worker thread
144
+ that is terminated at `timeoutMs`, so the budget covers parsing and compiling
145
+ the bundle, promise jobs and anything the code leaves behind, and the calling
146
+ process never runs or inspects a value from the isolate. Failures are
147
+ `LowerError`s with a `reason` (`timeout`, `determinism`, `out-of-memory`, …).
124
148
  - **Fidelity** is `lossless` when `irHash(lower(lift(ir))) == irHash(ir)`. Items
125
149
  report what is kept as plain strings or exact forms: `expression-verbatim`,
126
- `legacy-syntax`, `unknown-node`, `connection-id-only`, `raw-node`, `key-added`;
150
+ `legacy-syntax`, `unknown-node`, `connection-id-only`, `connection-ambiguous`
151
+ (a connection name that more than one connection id has, so the id is kept),
152
+ `raw-node`, `key-added`;
127
153
  `layout-omitted`, `pinned-data-omitted`, `roundtrip-mismatch` and
128
154
  `lowering-failed` make an export lossy.
129
155
 
@@ -133,8 +159,12 @@ const { buildId, workflows } = await lower(files, { connections });
133
159
  `config` on internal nodes) into the canonical node fields following the
134
160
  executor's own reading order, so no executor-visible value changes
135
161
  (`executorView` computes what the executor reads, for checking). It is pure
136
- and idempotent. The one mirror it keeps is `data.executionSettings.runOn`,
137
- because today's executor reads `runOn` only there.
162
+ and idempotent. It keeps the mirrors some executor path reads from the data
163
+ bag only: `data.executionSettings.runOn`, `data.operation_name` (the agent tool
164
+ binding) and an external node's `data.version` (node-state rows). It does not
165
+ promote an external node's `data.state`, which the sub-node paths do not read;
166
+ `validate --strict` reports it. Keys named `__proto__` are ordinary keys
167
+ everywhere (copies, hashes, lift and lower).
138
168
  - `irHash` is `sha256:` + the SHA-256 of the RFC 8785 canonical JSON of the
139
169
  normalised definition without `authoring`.
140
170
  - `validate(definition, { strict, isTaskQueueKnown })` applies the service's
@@ -159,6 +189,127 @@ manifest's `input_fields` / `output_fields`; trigger operations also appear
159
189
  under `triggers`. Internal nodes ship in the package as `nodes.internal.*` and
160
190
  `triggers.*`.
161
191
 
192
+ ## Tier 2: durable workflow functions
193
+
194
+ A tier-2 workflow is an `async` function that the engine runs by **replay**:
195
+ from the top on every workflow task, with recorded results fed back, until it
196
+ returns or awaits something not yet in its history. Everything that touches
197
+ the world is a command (`ctx.run` for node activities, timers, child runs,
198
+ signals to other runs); the function itself has no I/O.
199
+
200
+ ```ts
201
+ import { workflow, defineSignal, ApplicationError } from '@fluidgrids/workflows';
202
+ import { nodes as n } from './.fluidgrids/nodes';
203
+
204
+ export const approve = defineSignal<{ by: string }>('approve');
205
+
206
+ export const refundOrder = workflow(
207
+ 'refund-order',
208
+ { signals: [approve] },
209
+ async (ctx, order: { id: string }) => {
210
+ const decision = await Promise.race([
211
+ ctx.signalReceived(approve),
212
+ ctx.sleep('2d').then(() => undefined),
213
+ ]);
214
+ if (!decision) throw new ApplicationError('Not approved in time', { type: 'PERMANENT' });
215
+ const refund = await ctx.run(n.stripe.refund, {
216
+ args: { chargeId: order.id },
217
+ retry: { maxRetries: 5, retryDelay: 1000 },
218
+ });
219
+ return { refunded: refund.id, approvedBy: decision.by };
220
+ }
221
+ );
222
+ ```
223
+
224
+ - `ctx.run`, `ctx.sleep`, `ctx.condition`, `ctx.signalReceived` / `ctx.onSignal`
225
+ / `ctx.setDefaultSignalHandler`, `ctx.child` / `ctx.startChild`,
226
+ `ctx.signalRun` / `ctx.signalParent` / `ctx.getRunHandle`, `ctx.sideEffect`,
227
+ `ctx.patched` / `ctx.deprecatePatch`, `ctx.now` / `ctx.random` / `ctx.uuid`,
228
+ `ctx.setCurrentDetails`, cancellation scopes, `ctx.saga()`, and the parity
229
+ commands `ctx.upsertContext` and `ctx.continueAsNew`. User activities, local
230
+ activities, custom queries, updates and search attributes are Phase C and fail
231
+ the workflow task with `UNSUPPORTED_COMMAND`.
232
+ - Only store and registry nodes and `internal:http`, `internal:datatable`,
233
+ `internal:set` and `internal:code` may run as activities
234
+ (`ACTIVITY_NODE_ALLOWLIST`).
235
+ - An `ApplicationError` (or any failure class, or a class listed in
236
+ `failureErrorTypes`) fails the run; any other throw, a determinism violation
237
+ or a Phase C call fails only the workflow task, which the engine retries.
238
+ - A cancellation scope function receives a `ctx` bound to its scope; use it
239
+ inside the function. The outer `ctx` joins the scope only while the function
240
+ runs synchronously (`ctx.nonCancellable(() => ctx.run(...))` works), never
241
+ after an await, so concurrent branches never share a scope by accident.
242
+ `saga.compensate()` runs each compensation in its own non-cancellable scope
243
+ and passes it that scope's `ctx` (`saga.add((ctx) => ...)`).
244
+ - `ctx.condition` in a scope that is already cancelled rejects with
245
+ `CancelledFailure`; `ctx.allHandlersFinished()` ignores cancellation.
246
+ - A child whose options omit `cancellationType` uses
247
+ `WAIT_CANCELLATION_COMPLETED`: a cancelled scope sends
248
+ `REQUEST_CANCEL_EXTERNAL_RUN` with a `{ childId }` target (enabled with the
249
+ core commands) and the awaitable rejects once the child has ended.
250
+ - `ctx.random()` and `ctx.uuid()` are seeded from `randomnessSeed` in the
251
+ `WORKFLOW_STARTED` payload (the service writes the run id), falling back to
252
+ `info.runId`, so a history replays on its own.
253
+ - Inside the isolates, local time is UTC on every host, the `toLocale*` and
254
+ `localeCompare` methods are locale-independent, `Intl` is absent and
255
+ `WeakRef` / `FinalizationRegistry` throw, so QuickJS and `vm` run code the
256
+ same way. The in-process test environment runs on the host's built-ins.
257
+ - In tier-1 code, `wf.codeWorkflow(fn, options)` embeds a function as an
258
+ `internal:code-workflow` node. A function that declares a trigger, or that no
259
+ tier-1 workflow embeds, lowers to a workflow of its own.
260
+
261
+ ### The runtime ABI and the task runner
262
+
263
+ Every bundle installs `globalThis.__fluidgridsRuntime = { protocol: 1,
264
+ sdkVersion, start(requestJson), step(), complete() }`. A host calls `start`,
265
+ drains promise jobs while `step()` returns `"more"`, drains once more and reads
266
+ the `WorkflowTaskResult` JSON from `complete()`. `createWorkflowTaskRunner()`
267
+ (`@fluidgrids/workflows/quickjs`) does this in QuickJS on a pool of worker
268
+ threads with a heap cap, a stack cap (default 256 KiB, about 1,300 frames,
269
+ because QuickJS's C frames also use the host thread's stack) and a wall-clock
270
+ budget, verifies bundles against their `buildId`, and replaces threads that
271
+ time out or end a task with a fatal engine error before they get another task.
272
+
273
+ ### Testing
274
+
275
+ ```ts
276
+ import { TestWorkflowEnvironment, replay } from '@fluidgrids/workflows/testing';
277
+
278
+ const env = await TestWorkflowEnvironment.create();
279
+ env.mock(n.stripe.refund, async () => ({ id: 're_1' }));
280
+ const handle = await env.start(refundOrder, { input: { id: 'ch_1' } });
281
+ await handle.signal(approve, { by: 'ana' });
282
+ expect(await handle.result()).toEqual({ refunded: 're_1', approvedBy: 'ana' });
283
+ await replay(refundOrder, handle.history()); // throws DeterminismViolation on a mismatch
284
+ ```
285
+
286
+ The test environment appends exactly the events the service appends
287
+ (`eventsForCommands`) and retries activities with `computeRetryDelay`. Its
288
+ documented differences from the service: workflow tasks run in this process
289
+ without the determinism shims (pass `taskExecutor` to run them in an isolate), a
290
+ failed workflow task stops the run instead of being retried, activity
291
+ start-to-close and heartbeat timeouts are not timed (a mock may throw a
292
+ `TimeoutFailure`), and graph children are mocks (`mockChild`).
293
+
294
+ `simulate(definition, { trigger, mocks, workflows })` walks a tier-1 graph in
295
+ process. It is a reduced form: no expressions are evaluated, a node without a
296
+ mock passes its input through, routing follows the handle a mock returns (or
297
+ every edge), loops run their body once, and error policies, retries, waits and
298
+ merges are not modelled.
299
+
300
+ ### Replay conformance fixtures
301
+
302
+ `fixtures/histories/` holds the recorded histories of spec 10 §3 (timers,
303
+ signals, cancellation, child runs, race, gather, continue-as-new, patched,
304
+ side effects, activities, task failures, activation boundaries, and updates as
305
+ parse-only fixtures), with their workflow sources in `fixtures/histories/src/`
306
+ and the scenarios that recorded them in `fixtures/histories/scenarios.ts`.
307
+ `bun run check:histories` re-records every history, replays it in QuickJS and in
308
+ `vm`, requires both results to equal the recorded `expected`, and replays every
309
+ completed workflow task to exactly the commands it recorded, then does all of
310
+ it again under `TZ=Asia/Kolkata`; `tests/histories.test.ts` adds the in-process
311
+ replay. `--write` re-records.
312
+
162
313
  ## Shared fixtures
163
314
 
164
315
  `fixtures/` holds the conformance fixtures shared with the Python SDK: every
@@ -175,6 +326,7 @@ bun run test:coverage # with the 85% gate
175
326
  bun run lint
176
327
  bun run typecheck # src, tests and the fixture projects
177
328
  bun run check:fixtures # fixtures are current; add --ruff to run ruff format --check
329
+ bun run check:histories # replay conformance in QuickJS and vm; --write re-records
178
330
  bun run build # tsup: ESM + CJS + .d.ts
179
331
  bun run sanity # everything above
180
332
  ```
@@ -0,0 +1,57 @@
1
+ import { U as RunEventRecord } from './types-CpBBjaGX.cjs';
2
+
3
+ /**
4
+ * Split a history into activations (C-HIST, spec 04 §2).
5
+ *
6
+ * An activation is what one workflow task saw and did: the input events it
7
+ * delivered to the code, the clock it ran with, and (for completed tasks) the
8
+ * command events it appended. Replaying a history means running the code
9
+ * through the same activations in order and checking that each completed one
10
+ * issues the commands it recorded; the last, open activation then produces the
11
+ * new commands.
12
+ *
13
+ * The rules:
14
+ * 1. A boundary is a `WORKFLOW_TASK_STARTED` followed (input events may
15
+ * interleave) by `WORKFLOW_TASK_COMPLETED` of the same `taskId`. A Started
16
+ * followed by Failed or TimedOut is not a boundary; its inputs carry over.
17
+ * 2. Activation k delivers the inputs appended before its Started, up to the
18
+ * history length the task was leased with (`payload.historyLength`, written
19
+ * on transient attempts, else the Started event's own seq).
20
+ * 3. Inputs appended after that (while the task ran) belong to activation k+1.
21
+ * 4. Command events after Completed(k) belong to activation k.
22
+ * 5. The clock of activation k is `payload.startedAt` of its Started event
23
+ * when present, else the event time. The open activation uses `nowMs`.
24
+ * 6. The history length activation k reports is its cutoff (rule 2); the open
25
+ * activation reports the leased length.
26
+ */
27
+
28
+ interface Activation {
29
+ /** Input events to deliver, in history order. */
30
+ inputs: RunEventRecord[];
31
+ /** Command events the task appended; undefined for the open (new) activation. */
32
+ recorded: RunEventRecord[] | undefined;
33
+ /** The workflow clock during the activation. */
34
+ timeMs: number;
35
+ /** The completed task's id. */
36
+ taskId?: string;
37
+ /**
38
+ * The history length the task saw when it started (what `ctx.info.historyLength`
39
+ * and `continueAsNewSuggested` report during the activation, so replay sees
40
+ * the same values the original task did).
41
+ */
42
+ historyLength: number;
43
+ }
44
+ /**
45
+ * The activations of `history`. With `includeOpen`, the last entry is the
46
+ * open activation (the task being executed), even when it has no inputs.
47
+ */
48
+ declare function splitActivations(history: readonly RunEventRecord[], nowMs: number, includeOpen: boolean): Activation[];
49
+ /**
50
+ * Input events that no completed workflow task has delivered yet, in history
51
+ * order, including those that arrived while a task was running (rule 3). The
52
+ * service schedules a new workflow task when this is not empty after a task
53
+ * completes or an input is appended.
54
+ */
55
+ declare function unseenInputs(history: readonly RunEventRecord[]): RunEventRecord[];
56
+
57
+ export { type Activation as A, splitActivations as s, unseenInputs as u };
@@ -0,0 +1,57 @@
1
+ import { U as RunEventRecord } from './types-DV-HX_Gt.js';
2
+
3
+ /**
4
+ * Split a history into activations (C-HIST, spec 04 §2).
5
+ *
6
+ * An activation is what one workflow task saw and did: the input events it
7
+ * delivered to the code, the clock it ran with, and (for completed tasks) the
8
+ * command events it appended. Replaying a history means running the code
9
+ * through the same activations in order and checking that each completed one
10
+ * issues the commands it recorded; the last, open activation then produces the
11
+ * new commands.
12
+ *
13
+ * The rules:
14
+ * 1. A boundary is a `WORKFLOW_TASK_STARTED` followed (input events may
15
+ * interleave) by `WORKFLOW_TASK_COMPLETED` of the same `taskId`. A Started
16
+ * followed by Failed or TimedOut is not a boundary; its inputs carry over.
17
+ * 2. Activation k delivers the inputs appended before its Started, up to the
18
+ * history length the task was leased with (`payload.historyLength`, written
19
+ * on transient attempts, else the Started event's own seq).
20
+ * 3. Inputs appended after that (while the task ran) belong to activation k+1.
21
+ * 4. Command events after Completed(k) belong to activation k.
22
+ * 5. The clock of activation k is `payload.startedAt` of its Started event
23
+ * when present, else the event time. The open activation uses `nowMs`.
24
+ * 6. The history length activation k reports is its cutoff (rule 2); the open
25
+ * activation reports the leased length.
26
+ */
27
+
28
+ interface Activation {
29
+ /** Input events to deliver, in history order. */
30
+ inputs: RunEventRecord[];
31
+ /** Command events the task appended; undefined for the open (new) activation. */
32
+ recorded: RunEventRecord[] | undefined;
33
+ /** The workflow clock during the activation. */
34
+ timeMs: number;
35
+ /** The completed task's id. */
36
+ taskId?: string;
37
+ /**
38
+ * The history length the task saw when it started (what `ctx.info.historyLength`
39
+ * and `continueAsNewSuggested` report during the activation, so replay sees
40
+ * the same values the original task did).
41
+ */
42
+ historyLength: number;
43
+ }
44
+ /**
45
+ * The activations of `history`. With `includeOpen`, the last entry is the
46
+ * open activation (the task being executed), even when it has no inputs.
47
+ */
48
+ declare function splitActivations(history: readonly RunEventRecord[], nowMs: number, includeOpen: boolean): Activation[];
49
+ /**
50
+ * Input events that no completed workflow task has delivered yet, in history
51
+ * order, including those that arrived while a task was running (rule 3). The
52
+ * service schedules a new workflow task when this is not empty after a task
53
+ * completes or an input is appended.
54
+ */
55
+ declare function unseenInputs(history: readonly RunEventRecord[]): RunEventRecord[];
56
+
57
+ export { type Activation as A, splitActivations as s, unseenInputs as u };
@@ -0,0 +1,39 @@
1
+ import { P as Position, J as JsonObject, d as ConnectionRef, i as WorkflowAuthoring, b as WorkflowDefinition } from './types-BGoPzgcl.cjs';
2
+ import { T as Tier1Recording, x as Tier1Workflow } from './workflow-FXUkpIBc.cjs';
3
+
4
+ declare const LAYOUT_FORMAT = "fluidgrids.layout";
5
+ declare const LAYOUT_FORMAT_VERSION = 1;
6
+ /**
7
+ * The layout sidecar (`workflows/<key>.layout.json`): canvas positions, sticky
8
+ * notes and `meta.layout`, kept out of the code so it stays readable.
9
+ */
10
+ interface LayoutSidecar {
11
+ format: typeof LAYOUT_FORMAT;
12
+ formatVersion: typeof LAYOUT_FORMAT_VERSION;
13
+ positions: Record<string, Position>;
14
+ stickyNotes?: JsonObject[];
15
+ metaLayout?: JsonObject;
16
+ }
17
+ /** How connection names in code resolve to connection references. */
18
+ type ConnectionResolver = Readonly<Record<string, ConnectionRef>> | ((name: string) => ConnectionRef | undefined);
19
+ interface AssembleOptions {
20
+ layout?: LayoutSidecar;
21
+ connections?: ConnectionResolver;
22
+ /** TEST-only pinned data (`workflows/<key>.pinned.json`). */
23
+ pinnedData?: JsonObject;
24
+ /** Provenance; defaults to `{ mode: 'canvas', generator: 'sdk-ts', generatorVersion }`. */
25
+ authoring?: WorkflowAuthoring;
26
+ }
27
+ /** Connection names used in code that the target workspace does not have. */
28
+ declare class UnresolvedConnectionsError extends Error {
29
+ readonly names: string[];
30
+ constructor(names: string[]);
31
+ }
32
+ /** Validate a parsed layout sidecar. */
33
+ declare function parseLayoutSidecar(value: unknown): LayoutSidecar;
34
+ /** Assemble IR v2 from a recording. */
35
+ declare function assembleRecording(recording: Tier1Recording, options?: AssembleOptions): WorkflowDefinition;
36
+ /** Lower a tier-1 workflow to IR v2 (spec 10 §1 `toIR`). */
37
+ declare function toIR(workflow: Tier1Workflow, options?: AssembleOptions): WorkflowDefinition;
38
+
39
+ export { type AssembleOptions as A, type ConnectionResolver as C, LAYOUT_FORMAT as L, UnresolvedConnectionsError as U, LAYOUT_FORMAT_VERSION as a, type LayoutSidecar as b, assembleRecording as c, parseLayoutSidecar as p, toIR as t };
@@ -0,0 +1,39 @@
1
+ import { P as Position, J as JsonObject, d as ConnectionRef, i as WorkflowAuthoring, b as WorkflowDefinition } from './types-BGoPzgcl.js';
2
+ import { T as Tier1Recording, x as Tier1Workflow } from './workflow-Bpr3vnA_.js';
3
+
4
+ declare const LAYOUT_FORMAT = "fluidgrids.layout";
5
+ declare const LAYOUT_FORMAT_VERSION = 1;
6
+ /**
7
+ * The layout sidecar (`workflows/<key>.layout.json`): canvas positions, sticky
8
+ * notes and `meta.layout`, kept out of the code so it stays readable.
9
+ */
10
+ interface LayoutSidecar {
11
+ format: typeof LAYOUT_FORMAT;
12
+ formatVersion: typeof LAYOUT_FORMAT_VERSION;
13
+ positions: Record<string, Position>;
14
+ stickyNotes?: JsonObject[];
15
+ metaLayout?: JsonObject;
16
+ }
17
+ /** How connection names in code resolve to connection references. */
18
+ type ConnectionResolver = Readonly<Record<string, ConnectionRef>> | ((name: string) => ConnectionRef | undefined);
19
+ interface AssembleOptions {
20
+ layout?: LayoutSidecar;
21
+ connections?: ConnectionResolver;
22
+ /** TEST-only pinned data (`workflows/<key>.pinned.json`). */
23
+ pinnedData?: JsonObject;
24
+ /** Provenance; defaults to `{ mode: 'canvas', generator: 'sdk-ts', generatorVersion }`. */
25
+ authoring?: WorkflowAuthoring;
26
+ }
27
+ /** Connection names used in code that the target workspace does not have. */
28
+ declare class UnresolvedConnectionsError extends Error {
29
+ readonly names: string[];
30
+ constructor(names: string[]);
31
+ }
32
+ /** Validate a parsed layout sidecar. */
33
+ declare function parseLayoutSidecar(value: unknown): LayoutSidecar;
34
+ /** Assemble IR v2 from a recording. */
35
+ declare function assembleRecording(recording: Tier1Recording, options?: AssembleOptions): WorkflowDefinition;
36
+ /** Lower a tier-1 workflow to IR v2 (spec 10 §1 `toIR`). */
37
+ declare function toIR(workflow: Tier1Workflow, options?: AssembleOptions): WorkflowDefinition;
38
+
39
+ export { type AssembleOptions as A, type ConnectionResolver as C, LAYOUT_FORMAT as L, UnresolvedConnectionsError as U, LAYOUT_FORMAT_VERSION as a, type LayoutSidecar as b, assembleRecording as c, parseLayoutSidecar as p, toIR as t };