@fungi.computer/braid 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +260 -2
- package/dist/cloudflare/graph-engine.d.ts +25 -0
- package/dist/cloudflare/graph-engine.js +46 -0
- package/dist/cloudflare/graph-program.d.ts +6 -0
- package/dist/cloudflare/graph-program.js +550 -0
- package/dist/cloudflare/index.d.ts +1 -0
- package/dist/cloudflare/index.js +1 -0
- package/dist/code-execution.d.ts +16 -0
- package/dist/code-execution.js +50 -0
- package/dist/code-result.d.ts +11 -0
- package/dist/code-result.js +19 -0
- package/dist/code-sandbox.d.ts +9 -0
- package/dist/code-sandbox.js +13 -0
- package/dist/compile.d.ts +43 -0
- package/dist/compile.js +248 -0
- package/dist/execution.d.ts +194 -0
- package/dist/execution.js +130 -0
- package/dist/expressions.d.ts +16 -0
- package/dist/expressions.js +322 -0
- package/dist/git-access.d.ts +59 -0
- package/dist/git-access.js +106 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/ports.d.ts +58 -0
- package/dist/ports.js +256 -0
- package/dist/review-text.d.ts +15 -0
- package/dist/review-text.js +40 -0
- package/dist/runtime/arming-authority.d.ts +83 -0
- package/dist/runtime/arming-authority.js +90 -0
- package/dist/runtime/bindings.d.ts +25 -0
- package/dist/runtime/bindings.js +41 -0
- package/dist/runtime/capture.d.ts +18 -0
- package/dist/runtime/capture.js +99 -0
- package/dist/runtime/git-pass-contract.d.ts +63 -0
- package/dist/runtime/git-pass-contract.js +36 -0
- package/dist/runtime/git-pass-store.d.ts +32 -0
- package/dist/runtime/git-pass-store.js +82 -0
- package/dist/runtime/git-pass.d.ts +41 -0
- package/dist/runtime/git-pass.js +72 -0
- package/dist/runtime/graph-contract.d.ts +788 -0
- package/dist/runtime/graph-contract.js +122 -0
- package/dist/runtime/graph-dispatch.d.ts +31 -0
- package/dist/runtime/graph-dispatch.js +111 -0
- package/dist/runtime/graph-run-store.d.ts +79 -0
- package/dist/runtime/graph-run-store.js +267 -0
- package/dist/runtime/graph-start.d.ts +90 -0
- package/dist/runtime/graph-start.js +124 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +20 -0
- package/dist/runtime/ports.d.ts +156 -0
- package/dist/runtime/ports.js +1 -0
- package/dist/runtime/run-authority.d.ts +60 -0
- package/dist/runtime/run-authority.js +145 -0
- package/dist/runtime/run-ceiling.d.ts +106 -0
- package/dist/runtime/run-ceiling.js +23 -0
- package/dist/runtime/run-member.d.ts +3 -0
- package/dist/runtime/run-member.js +12 -0
- package/dist/runtime/run-owner.d.ts +1650 -0
- package/dist/runtime/run-owner.js +513 -0
- package/dist/runtime/run-report.d.ts +14 -0
- package/dist/runtime/run-report.js +23 -0
- package/dist/runtime/schedules.d.ts +39 -0
- package/dist/runtime/schedules.js +149 -0
- package/dist/runtime/stored-trigger-rows.d.ts +12 -0
- package/dist/runtime/stored-trigger-rows.js +17 -0
- package/dist/runtime/trigger-owner.d.ts +594 -0
- package/dist/runtime/trigger-owner.js +280 -0
- package/dist/runtime/trigger-plans.d.ts +774 -0
- package/dist/runtime/trigger-plans.js +62 -0
- package/dist/runtime/trigger-router.d.ts +34 -0
- package/dist/runtime/trigger-router.js +143 -0
- package/dist/runtime/trigger-run-reservations.d.ts +28 -0
- package/dist/runtime/trigger-run-reservations.js +114 -0
- package/dist/schema.d.ts +1183 -0
- package/dist/schema.js +239 -0
- package/dist/triggers.d.ts +46 -0
- package/dist/triggers.js +75 -0
- package/dist/validate.d.ts +25 -0
- package/dist/validate.js +686 -0
- package/dist/workflow-id.d.ts +3 -0
- package/dist/workflow-id.js +3 -0
- package/package.json +96 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jamstack Consulting LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,261 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Braid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Braid owns `fungi.workflow/v1` and its graph interpreter. The browser-safe root
|
|
4
|
+
exports validation, compilation, and portable execution contracts.
|
|
5
|
+
`validate(unknown, catalog)` decodes with Effect Schema and returns typed issues
|
|
6
|
+
for the editor. Expected schema and expression-syntax refusals become issues;
|
|
7
|
+
unexpected parser or traversal defects propagate rather than being reported as
|
|
8
|
+
author errors. `compile` returns either issues or a topologically ordered,
|
|
9
|
+
installation-bound plan and SHA-256 `planDigest`. `decodeExecutionPlan` checks
|
|
10
|
+
stored content against that identity and recompiles its domain invariants.
|
|
11
|
+
Compilation grants no authority.
|
|
12
|
+
|
|
13
|
+
`catalogMethods` omits declarations marked `hidden: true` before reading their
|
|
14
|
+
graph ports. Visible method digests remain unchanged; the discovery marker does
|
|
15
|
+
not decide direct-call permissions.
|
|
16
|
+
|
|
17
|
+
## Workers engine
|
|
18
|
+
|
|
19
|
+
`@fungi.computer/braid/cloudflare` exports `GraphWorkflow` and
|
|
20
|
+
`GraphWorkflowBindings`. A host subclasses the base, supplies its
|
|
21
|
+
`GRAPH_WORKFLOWS` binding, and implements `dispatcher(params)` and
|
|
22
|
+
`terminalTriggerRun(params)`. The dispatcher loads the host's admitted plan,
|
|
23
|
+
invokes App methods, records node events, and persists review waits. A host
|
|
24
|
+
admitting Code also overrides `codeExecution()` with its actual Mycelium
|
|
25
|
+
`Sandbox` and an explicitly captured `timeoutMs`. Hosts without this port refuse
|
|
26
|
+
Code with `code_execution_unavailable`. Hub and standalone hosts give Workflow
|
|
27
|
+
data transforms a 30,000 ms budget; this is independent of Agent policy. Local
|
|
28
|
+
workerd hosts set `WORKFLOW_CODE_LOCAL_DEV=true` to report that CPU limits are
|
|
29
|
+
not enforced; celld and deployed Workers forward the budget to the Loader.
|
|
30
|
+
Terminal cleanup runs through the host callback; Braid does not own grants, run
|
|
31
|
+
storage, Git admission, trigger reservations, cancellation requests, or
|
|
32
|
+
outboxes.
|
|
33
|
+
|
|
34
|
+
The root exports `GraphParams`, `NodeDispatcher`, `NodeCall`, `NodeEvent`,
|
|
35
|
+
`GraphReview`, `DispatchResult`, and their wire decoders without importing a
|
|
36
|
+
Workers runtime. App pending calls retain their stable idempotency key while
|
|
37
|
+
each poll has a distinct event identity. Retry attempts remain local to a poll.
|
|
38
|
+
Hosts must re-admit App calls using their own stored authority.
|
|
39
|
+
|
|
40
|
+
The portable root is checked with `skipLibCheck: false`. The Workers build
|
|
41
|
+
retains the Hub's existing `skipLibCheck: true`: Effect 4.0.2's `KeyValueStore`
|
|
42
|
+
declaration refers to DOM `Storage`, which the Workers type environment does not
|
|
43
|
+
provide. No ambient compatibility declaration is added; the native runtime tests
|
|
44
|
+
exercise the actual Workers implementation.
|
|
45
|
+
|
|
46
|
+
The Hub adapter consumes this same engine. Its native graph tests cover branch
|
|
47
|
+
selection, Pending polls, reviews, maps, and terminal cancellation. Those tests
|
|
48
|
+
establish Hub behavior; a standalone host must qualify its own bindings and
|
|
49
|
+
admission ports.
|
|
50
|
+
|
|
51
|
+
## V1 contract
|
|
52
|
+
|
|
53
|
+
- Run input ports use the reserved node `$input`. Each node also emits `after`.
|
|
54
|
+
`after` is reserved for that ordering signal: App input/output schemas and
|
|
55
|
+
Code input/output schemas and transform fields declaring it are refused,
|
|
56
|
+
rather than silently overwritten.
|
|
57
|
+
- `admitRunInput(definition, raw)` fills absent property defaults and validates
|
|
58
|
+
the resulting object with the supported projection. It returns
|
|
59
|
+
`{kind: "accepted", input}` or `{kind: "invalid", issues}`; issues retain the
|
|
60
|
+
validator's instance location and message. Explicit values are never replaced
|
|
61
|
+
by defaults. Hosts record the admitted input, and triggers and graph execution
|
|
62
|
+
use the same admission function.
|
|
63
|
+
- Durations are integer milliseconds, positive and at most seven days. Retry
|
|
64
|
+
`attempts` includes the first attempt (1–5); external effects cannot retry.
|
|
65
|
+
- App and Code `values` and transform fields use Mapping: `{literal: Json}`,
|
|
66
|
+
`{template: string}`, `{from: PortRef, path?: JsonPointer}`,
|
|
67
|
+
`{coalesce: Mapping[]}`, `{object: Record<string, Mapping>}`,
|
|
68
|
+
`{array: Mapping[]}`, or
|
|
69
|
+
`{each: {over: Mapping, body: Mapping, maxItems: 1..50}}`. `each` exposes its
|
|
70
|
+
current value/index as `$item.value` and `$item.index`; nested each
|
|
71
|
+
expressions get their own item scope. The evaluator bounds materialization to
|
|
72
|
+
64 KiB and 65536 visits. A missing required member makes the whole
|
|
73
|
+
construction absent. Mustache owns interpolation parsing (`{{node.port}}`,
|
|
74
|
+
without HTML escaping or execution); JSON Pointer owns path lookup. References
|
|
75
|
+
establish dependencies.
|
|
76
|
+
- Object method inputs/outputs keep their property ports. Non-object values and
|
|
77
|
+
unions use a single `value` port; the dispatcher still sends/receives the raw
|
|
78
|
+
declared App value. A completion union containing required `error` objects
|
|
79
|
+
exposes `ok` / `refused`, each carrying the entire declared record. Both sides
|
|
80
|
+
retain their original schema constraints. App refusals route data; host
|
|
81
|
+
admission refusals still fail the node. Apps do not reshape their APIs.
|
|
82
|
+
- App nodes may declare `gitAccess: {binding, repository, branches}` (a write
|
|
83
|
+
branch ceiling) or `gitAccess: {binding, repository, access: "read"}` (read
|
|
84
|
+
only, no push). This declaration enters the plan digest and contains no
|
|
85
|
+
installation IDs. The member selects installations through
|
|
86
|
+
`bindingsSelectionSchema`; an explicit `{kind: "team-source"}` binding selects
|
|
87
|
+
a registered Team source. Compilation freezes the selected binding, including
|
|
88
|
+
a target separate from the executing App. `resolveGitAccess` forms Agar's
|
|
89
|
+
canonical installation-local or Team-source `RepositoryRef`; resolved scopes
|
|
90
|
+
are `{repository: RepositoryRef, branches}` or
|
|
91
|
+
`{repository: RepositoryRef, access: "read"}`. These data alone grant no
|
|
92
|
+
authority: the host checks the starting human's captured ceiling and live
|
|
93
|
+
policy, at the access's own scope, before minting a pass, and the pass's scope
|
|
94
|
+
is always its admitted access. Read access may name `$trigger.repository`: an
|
|
95
|
+
App-event trigger declares `repository`, a mapping over its event (for example
|
|
96
|
+
`{from: {node: "$input", port: "payload"}, path: "/data/repository/name"}`),
|
|
97
|
+
and the node's binding must be that trigger's App. Arming admits no Git; each
|
|
98
|
+
fire resolves the event's one repository inside the emitting installation, and
|
|
99
|
+
run start admits the grantor's live read access to exactly it. A manual run or
|
|
100
|
+
another trigger has no trigger repository, so the step is refused. Branches
|
|
101
|
+
are unique exact `refs/heads/...` names, bounded to 256 entries; globs and
|
|
102
|
+
revision expressions are refused.
|
|
103
|
+
- Coalesce selects the first present, non-null branch. When every candidate
|
|
104
|
+
comes from an inactive branch, the dependent node skips. An available fallback
|
|
105
|
+
still runs, so alternative review choices merge with OR semantics.
|
|
106
|
+
- Code nodes declare `{id, kind: "code", source, input, output, values}`.
|
|
107
|
+
`source` is a JavaScript function body, including its return; `input` and
|
|
108
|
+
`output` use the same projection grammar and ports as App methods. `values`
|
|
109
|
+
and incoming edges explicitly map the data available to the node. Exact
|
|
110
|
+
source, schemas and mappings enter the plan digest. Source uses the existing
|
|
111
|
+
256,000-byte whole-definition envelope; each schema retains its 20,000-byte
|
|
112
|
+
and depth-16 bounds. Compilation never parses or executes JavaScript and adds
|
|
113
|
+
no required App binding. Its only data capability is `env.INPUT.read({})`,
|
|
114
|
+
returning a fresh copy of its mapped JSON. No Apps or network are available.
|
|
115
|
+
The native step checkpoints the validated completion or a closed refusal,
|
|
116
|
+
including the whole 64 KiB result envelope, with automatic retries disabled.
|
|
117
|
+
Replay reads the checkpoint instead of rerunning JavaScript. Guest errors do
|
|
118
|
+
not expose source, inputs, or raw sandbox causes in run events. Sandbox calls
|
|
119
|
+
run inside an interruptible Effect; cancellation remains the host run owner's
|
|
120
|
+
native instance termination, with no separate cancellation owner.
|
|
121
|
+
- A named `subgraphs` entry has the same input/nodes/edges/output contract as
|
|
122
|
+
the root. Map body inputs are `{item, index}`. Recursive map bodies are
|
|
123
|
+
refused. The entire definition, including bodies, is capped at 64 nodes / 256
|
|
124
|
+
edges.
|
|
125
|
+
- `after` edges order or gate any node. Other connected inputs belong to App or
|
|
126
|
+
Code ports; controls name their references in their own fields. Review's
|
|
127
|
+
selected port carries its first shown item; a condition's selected port is a
|
|
128
|
+
signal.
|
|
129
|
+
- Unreachable means unable to contribute to a graph output or a write/external
|
|
130
|
+
method. Skipped branch outputs may be absent; this is not an output error.
|
|
131
|
+
- The static expanded estimate budgets three durable operations per node plus
|
|
132
|
+
all map items and three operations per item for creation, completion and
|
|
133
|
+
parent notification, capped at 800 (headroom below Cloudflare's 1024).
|
|
134
|
+
- The catalog is keyed by portable App ID and preserves T1's method shape
|
|
135
|
+
(description, input/output, effect, approval, requires), with a catalog-owned
|
|
136
|
+
`digest` per method. Bindings must match the declared App ID. Only used
|
|
137
|
+
methods enter the consent manifest. Used method and event catalogs, including
|
|
138
|
+
event-only emitter installations, enter the plan.
|
|
139
|
+
- `pending: true` opts a method into Agar's shared Pending result. The catalog
|
|
140
|
+
exposes only the completed output ports of the emitted Output/Pending union;
|
|
141
|
+
its digest covers the original input/output declarations and the opt-in flag.
|
|
142
|
+
Ordinary methods omit the flag and keep their existing digests. The Hub maps
|
|
143
|
+
canonical Pending to engine Pending only for opted-in methods, with integer
|
|
144
|
+
`retryAfterMs` from 1000 to 60000. Pending never becomes graph output and
|
|
145
|
+
polls retain the same run/node/iteration key.
|
|
146
|
+
|
|
147
|
+
## Reviews
|
|
148
|
+
|
|
149
|
+
- A review shows ports to a person and waits up to its timeout. Without
|
|
150
|
+
`choices` it approves or rejects (ports `approved` / `rejected`). With
|
|
151
|
+
`choices` (two to eight `{id, label}`), the reviewer picks one; each choice id
|
|
152
|
+
is an output port carrying the first shown item, so branches follow the pick.
|
|
153
|
+
|
|
154
|
+
## Triggers
|
|
155
|
+
|
|
156
|
+
- `triggers` (optional, at most 8, root graph only) declares what may start a
|
|
157
|
+
run besides a person or Agent. For `{id, on: "webhook"}`, the host owns the
|
|
158
|
+
URL, secret and signature scheme, so the format names no provider.
|
|
159
|
+
- Inside a trigger, `$input` is the verified event (`body`, a stable non-secret
|
|
160
|
+
`eventId`, allowlisted `headers`), not the run input. Every port repeats on a
|
|
161
|
+
redelivery, so a redelivery maps to the same input. `when` is an ordinary
|
|
162
|
+
predicate over it; `input` maps it onto the run's declared input with ordinary
|
|
163
|
+
mappings. Without `input`, the body itself must be the run input. Triggers are
|
|
164
|
+
part of the plan identity.
|
|
165
|
+
- `validate` refuses references outside the event, unmapped required run input,
|
|
166
|
+
undeclared fields and mismatched literals (`trigger` issues). `triggerRun`
|
|
167
|
+
returns `start` with input checked against `definition.input`, `ignored` when
|
|
168
|
+
`when` is false, or a typed refusal. `triggerRequestId` turns (scope, trigger,
|
|
169
|
+
event id) into the idempotent start key.
|
|
170
|
+
- A schedule declares
|
|
171
|
+
`{id, on: "schedule", cron: {expression, timeZone}, input?}`. Almanac
|
|
172
|
+
validates the cadence; consecutive occurrences must be at least five minutes
|
|
173
|
+
apart. Its event exposes `scheduledAt` (ISO) and `eventId`; without a mapping
|
|
174
|
+
that event is the run input. The Team arms published definitions as a member,
|
|
175
|
+
rechecks access at each firing, and coalesces missed ticks.
|
|
176
|
+
|
|
177
|
+
- An App event declares
|
|
178
|
+
`{id, on: "event", app, event, eventDigest, when?, input?}`. `catalogEvents`
|
|
179
|
+
pins the original emitted payload schema; rewording its description preserves
|
|
180
|
+
the digest. Its event ports are `payload` (the full envelope) and `eventId`.
|
|
181
|
+
Missing declarations and changed digests refuse compilation; a cached trigger
|
|
182
|
+
checks those digests against the live catalog before starting.
|
|
183
|
+
- Event mappings prove assignability for the payload or required object paths
|
|
184
|
+
within it, plus literals, templates and coalesce. JSON Pointer spelling uses
|
|
185
|
+
the maintained encoder. Optional paths, array indexing and unsupported schema
|
|
186
|
+
assertions remain unproven and are refused. An emitted `const` projects to an
|
|
187
|
+
enum for checking, while the digest retains the original schema. Schedule and
|
|
188
|
+
webhook mapping behavior is unchanged.
|
|
189
|
+
|
|
190
|
+
## Schema scope
|
|
191
|
+
|
|
192
|
+
App projections have the same 20 KB / depth-16 envelope as T1. V1 accepts the
|
|
193
|
+
JSON Schema vocabulary in `PortSchema`: primitive types, standard formats from
|
|
194
|
+
the maintained validator registry and Fungi media annotations, enum/const,
|
|
195
|
+
object properties/required/additionalProperties, array items/counts, string
|
|
196
|
+
lengths/patterns, number bounds (including exclusive bounds), nonempty
|
|
197
|
+
anyOf/oneOf, and title/description/default annotations. Effect decodes that
|
|
198
|
+
vocabulary; `@cfworker/json-schema` validates runtime values without dynamic
|
|
199
|
+
code generation. Standard formats remain text ports and validate instances;
|
|
200
|
+
Fungi image/video/file annotations remain distinct media ports. Assignability is
|
|
201
|
+
conservative subset checking. Unsupported assertions (for example `$ref` or
|
|
202
|
+
conditionals) produce a schema issue rather than a guessed connection. This is
|
|
203
|
+
not a general JSON Schema implication solver.
|
|
204
|
+
|
|
205
|
+
`test/anime.fixture.ts` models beats → map{generate → review → optional
|
|
206
|
+
animate}. Its Prints catalog and digests are explicitly fake; it does not
|
|
207
|
+
establish the shipped Storyboard template's parity, installed App methods,
|
|
208
|
+
grants, costs or production execution.
|
|
209
|
+
|
|
210
|
+
Run `pnpm --dir packages/braid test` for `braid-validate`.
|
|
211
|
+
|
|
212
|
+
`apps/hub/test/graph-declared-ports.native.mjs` uses the actual Prints
|
|
213
|
+
declarations and input parsers with a provider-boundary fake. It exercises both
|
|
214
|
+
animate choices, keep/reject, declared refusal, Pending replay, and raw
|
|
215
|
+
scalar/array method values through the native graph engine; it makes no provider
|
|
216
|
+
calls.
|
|
217
|
+
|
|
218
|
+
## Host runtime
|
|
219
|
+
|
|
220
|
+
`@fungi.computer/braid/runtime` owns the saved graph run, node event and review
|
|
221
|
+
records. Hosts provide the Watchdog SQLite owner to `createGraphWorkflowRuns`;
|
|
222
|
+
the store preserves immutable plans, idempotent starts and accepted review
|
|
223
|
+
answers across owner recreation. `resolveWorkflowNode` and `planNodeCall`
|
|
224
|
+
resolve calls against that saved plan. Hosts retain membership, installation,
|
|
225
|
+
Git and native Workflow admission and delivery. Stored rows alone grant no
|
|
226
|
+
permission to run an App method or answer a review.
|
|
227
|
+
|
|
228
|
+
The runtime entrypoint also owns `createTeamWorkflowTriggers`,
|
|
229
|
+
`createWorkflowSchedules`, `createWorkflowEventRouter`, immutable trigger plans
|
|
230
|
+
and run reservations. Hosts supply publication resolution, existing denial
|
|
231
|
+
errors, live authorization, transaction and alarm ports; the runtime owns saved
|
|
232
|
+
trigger state and occurrence identity. `PublishedDefinitionPort` resolves an
|
|
233
|
+
exact version or commit with host-issued access. A copied document never grants
|
|
234
|
+
that access.
|
|
235
|
+
|
|
236
|
+
A review answer is `{approved: boolean}` or `{choice: string, note?: string}`.
|
|
237
|
+
Choice notes are bounded to 1,000 characters. A completed review retains the
|
|
238
|
+
shown item on its selected outcome port and also emits `choice` (the actual
|
|
239
|
+
selected outcome) and `note` (an empty string when absent). Those two ports and
|
|
240
|
+
`after` are reserved and cannot be named choices. Downstream nodes can use these
|
|
241
|
+
saved outputs, including a subsequent Repeat round.
|
|
242
|
+
|
|
243
|
+
## Bounded Repeat
|
|
244
|
+
|
|
245
|
+
A Repeat node declares `{id, kind: "repeat", body, maxRounds, until}`.
|
|
246
|
+
`maxRounds` is an integer from one to five. The body receives `item` from the
|
|
247
|
+
enclosing graph input, a zero-based `round`, and `previous`: an empty object on
|
|
248
|
+
the first round, then the preceding body output. The stop predicate reads the
|
|
249
|
+
completed body output through `$input`. It cannot reach into another graph.
|
|
250
|
+
|
|
251
|
+
When the predicate becomes true, the node emits the body output on `result`.
|
|
252
|
+
When all rounds finish without satisfying it, the node emits the last output on
|
|
253
|
+
`exhausted`. The other outlet stays inactive. Both paths emit `after`; failed
|
|
254
|
+
children fail the Repeat rather than fabricating a successful result.
|
|
255
|
+
|
|
256
|
+
Validation checks body input and output compatibility, references, recursive
|
|
257
|
+
groups, and the bounded execution budget. The Cloudflare interpreter runs each
|
|
258
|
+
round as a durable child with its own review identity and passes saved feedback
|
|
259
|
+
into the next round. Map items retain their order when their bodies contain a
|
|
260
|
+
Repeat. `apps/hub/test/graph-workflow-repeat.native.mjs` exercises the real
|
|
261
|
+
engine and run owner with a fixture App dispatcher; it calls no media provider.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from "cloudflare:workers";
|
|
2
|
+
import * as S from "effect/Schema";
|
|
3
|
+
import type { CodeExecution } from "../code-execution.js";
|
|
4
|
+
import { type NodeDispatcher, type GraphParams } from "../execution.js";
|
|
5
|
+
import type { Json } from "../schema.js";
|
|
6
|
+
export type GraphWorkflowBindings = {
|
|
7
|
+
GRAPH_WORKFLOWS: Workflow<GraphParams>;
|
|
8
|
+
};
|
|
9
|
+
/** How a root run ended, as the engine observed it; hosts may report it. */
|
|
10
|
+
export type GraphRunEnding = Readonly<{
|
|
11
|
+
kind: "complete";
|
|
12
|
+
output: Readonly<Record<string, Json>>;
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
kind: "failed";
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** Each host supplies App dispatch, run storage, and terminal cleanup. */
|
|
18
|
+
export declare abstract class GraphWorkflow<Env extends GraphWorkflowBindings = GraphWorkflowBindings> extends WorkflowEntrypoint<Env, GraphParams> {
|
|
19
|
+
/** The host scope and run identity select the owner receiving execution facts. */
|
|
20
|
+
protected abstract dispatcher(params: GraphParams): NodeDispatcher;
|
|
21
|
+
protected abstract terminalTriggerRun(params: GraphParams, ending: GraphRunEnding): Promise<void>;
|
|
22
|
+
/** A host may admit data-only Code with its own sandbox and captured budget. */
|
|
23
|
+
protected codeExecution(): CodeExecution | undefined;
|
|
24
|
+
run(event: Readonly<WorkflowEvent<GraphParams>>, step: WorkflowStep): Promise<Readonly<Record<string, S.Json>>>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WorkflowEntrypoint, } from "cloudflare:workers";
|
|
2
|
+
import * as S from "effect/Schema";
|
|
3
|
+
import { graphParamsSchema, } from "../execution.js";
|
|
4
|
+
import { runGraphWorkflow } from "./graph-program.js";
|
|
5
|
+
/** Each host supplies App dispatch, run storage, and terminal cleanup. */
|
|
6
|
+
export class GraphWorkflow extends WorkflowEntrypoint {
|
|
7
|
+
/** A host may admit data-only Code with its own sandbox and captured budget. */
|
|
8
|
+
codeExecution() {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
async run(event, step) {
|
|
12
|
+
const params = S.decodeUnknownSync(graphParamsSchema)(event.payload, {
|
|
13
|
+
onExcessProperty: "error",
|
|
14
|
+
});
|
|
15
|
+
let output;
|
|
16
|
+
try {
|
|
17
|
+
output = await runGraphWorkflow(step, params, event.instanceId, this.env.GRAPH_WORKFLOWS, this.dispatcher(params), this.codeExecution());
|
|
18
|
+
}
|
|
19
|
+
catch (cause) {
|
|
20
|
+
if (params.kind === "map-item")
|
|
21
|
+
await step.do("notify-parent-failed", async () => {
|
|
22
|
+
const parent = await this.env.GRAPH_WORKFLOWS.get(params.parentInstanceId);
|
|
23
|
+
await parent.sendEvent({
|
|
24
|
+
type: params.completionEvent,
|
|
25
|
+
payload: { refusal: "map_item_failed" },
|
|
26
|
+
});
|
|
27
|
+
return null;
|
|
28
|
+
});
|
|
29
|
+
if (params.kind === "run" && params.runId === event.instanceId)
|
|
30
|
+
await step.do("terminal-trigger-run", async () => {
|
|
31
|
+
await this.terminalTriggerRun(params, {
|
|
32
|
+
kind: "failed",
|
|
33
|
+
error: cause instanceof Error ? cause.message : "failed",
|
|
34
|
+
});
|
|
35
|
+
return null;
|
|
36
|
+
});
|
|
37
|
+
throw cause;
|
|
38
|
+
}
|
|
39
|
+
if (params.kind === "run")
|
|
40
|
+
await step.do("terminal-trigger-run", async () => {
|
|
41
|
+
await this.terminalTriggerRun(params, { kind: "complete", output });
|
|
42
|
+
return null;
|
|
43
|
+
});
|
|
44
|
+
return output;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CodeExecution } from "../code-execution.js";
|
|
2
|
+
import type { WorkflowStep } from "cloudflare:workers";
|
|
3
|
+
import { type Json } from "../index.js";
|
|
4
|
+
import { type GraphParams, type NodeDispatcher } from "../execution.js";
|
|
5
|
+
/** Native WorkflowStep owns checkpoints, retries, sleeps and event waits. */
|
|
6
|
+
export declare function runGraphWorkflow(step: WorkflowStep, params: GraphParams, instanceId: string, workflows: Workflow<GraphParams>, dispatcher: NodeDispatcher, codeExecution?: CodeExecution): Promise<Readonly<Record<string, Json>>>;
|