automategraph 0.0.0-stage → 1.5.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,299 @@
1
+ # Changelog
2
+
3
+ All notable changes to the AutomateGraph Execution Engine.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/); the project uses semver.
5
+
6
+ ## [1.5.1] — First registry publish
7
+
8
+ No code changes. The 1.5.0 upload was accepted by the registry but held by the account's
9
+ publish policy (granular tokens with 2FA bypass are being restricted for direct publishing,
10
+ which routes to staged approval), so the version never became installable. 1.5.1 carries the
11
+ identical tarball through the same policy.
12
+
13
+ ## [1.5.0] — Script loading
14
+
15
+ A document can now say where its functions live and have them loaded: `scripts` entries point at
16
+ files, inline source or URLs, and the loader executes them in the same realm. Purely additive —
17
+ without a `scripts` field, nothing changes; the core stays isomorphic and the loader is a
18
+ separate Node-only module.
19
+
20
+ ### Added
21
+
22
+ - **`src/node/graph-scripts.ts`** (Node-only, excluded from the browser build):
23
+ `loadGraphScripts(document, options)` returns
24
+ `{ functions, namespaces, scripts, registry }` — ready to pass as
25
+ `createEngineFromJSON(document, { functions })`. Options: `baseDir` (default
26
+ `process.cwd()`), `collisions` (`"throw"` | `"override"`), `onLog`, and injectable
27
+ `readFile` / `fetchText`.
28
+ - **Script declarations** in the graph document: `scripts: [{ namespace, path | source | url,
29
+ format? }]`. Validated by the isomorphic parser with JSON paths (`scripts[0].path`…): exactly
30
+ one source, identifier-like namespace, unique namespaces per document, known keys only, and
31
+ `parseGraphJSONDocument()` now exposes them. **Validation runs before execution** — a document
32
+ with a broken graph never runs a script.
33
+ - **Two provision strategies**, mixable in one script:
34
+ - EXPORTS — `module.exports = { … }` (CJS), `export const …` / `export default { … }` (ESM);
35
+ non-function exports are ignored and reported in `ignoredExports`;
36
+ - SELF-REGISTRATION — `globalThis.GraphEngine.registerEngineNamespace(namespace, functions)`,
37
+ installed by the loader for the duration of the load only, realm restored afterwards
38
+ (verified by identity, including a pre-existing `GraphEngine` global).
39
+ - **The namespace freeze** — claims are PROCESS-global (`globalThis` + `Symbol.for`), so a
40
+ second claim of the same namespace throws `NAMESPACE_ALREADY_REGISTERED`; a self-registering
41
+ script gets that throw from inside its own body. Claims are made at registration time, so a
42
+ script that registers and then throws leaves the namespace claimed.
43
+ `registeredNamespaces()` inspects the claims; `resetGraphScriptNamespaces()` clears them
44
+ (test/dev hygiene). Namespace mismatch throws `NAMESPACE_MISMATCH`.
45
+ - **Names keep their spelling** — the namespace is a claim slot, never a name prefix.
46
+ Cross-script name collisions throw `FUNCTION_NAME_COLLISION` unless the load opts into
47
+ `{ collisions: "override" }` (which logs a warning).
48
+ - **Format handling** — `.cjs` → `require`, `.mjs` → dynamic `import` (kept real through a
49
+ tsc-CommonJS-safe indirection), `.js`/URL → source-hint detection then CJS, falling back to
50
+ ESM and vice versa; `format: "cjs" | "esm" | "auto"` pins it and disables the fallback.
51
+ - **Errors** — `GraphScriptError` with codes `SCRIPT_LOAD_FAILED`, `SCRIPT_NO_FUNCTIONS`,
52
+ `SCRIPT_EXPORT_INVALID`, `NAMESPACE_ALREADY_REGISTERED`, `NAMESPACE_MISMATCH`,
53
+ `FUNCTION_NAME_COLLISION`; a script-raised registration error surfaces unwrapped, never as a
54
+ generic load failure.
55
+ - **Tests** — `tests/scripts.test.ts` (26) + 15 fixtures under `tests/fixtures/scripts/`;
56
+ suite 256 → 282.
57
+ - **Example** — `examples/json-scripts.ts` + `examples/order-flow.scripted.graph.json` +
58
+ `examples/order-flow.functions.cjs` (`npm run example:scripts`): one document, a path script
59
+ and an inline self-registering script, run end-to-end, then the freeze and the reset.
60
+
61
+ ### Changed
62
+
63
+ - `tsconfig.browser.json` excludes `src/node/**` — the isomorphic guard still covers the whole
64
+ core (engine, storage contract, JSON parser), the Node loader is deliberately outside it.
65
+ - `parseGraphJSONDocument()` added to `src/graph-json.ts`; `parseGraphJSON()` is now its
66
+ `.definition` and keeps its exact v1.4 behaviour.
67
+
68
+ ### Fixed
69
+
70
+ - **The format fallback replayed a script that had already run.** A script that executes and
71
+ throws while loading used to be retried in the other module format — running its body a second
72
+ time (double side effects, and a duplicate namespace claim for self-registering scripts).
73
+ The fallback is now restricted to module-FORMAT failures (`ERR_REQUIRE_ESM`, ESM syntax under
74
+ `require`, module-scope references); a script's own error fails the load immediately, after
75
+ exactly one execution. Guarded by scripts case S16.
76
+
77
+ ## [1.4.0] — Function registry and graph JSON
78
+
79
+ Moves code out of the graph definition: `run` and edge `test` may name a function that lives in
80
+ a registry, and a graph may be a JSON document. Purely additive — every v1.0–v1.3 graph keeps
81
+ its behaviour, because inline functions are still accepted everywhere.
82
+
83
+ ### Added
84
+
85
+ - **Function registry** — `FunctionRegistry` / `createFunctionRegistry()` /
86
+ `FunctionRegistry.from()`: `register`, `extend`, `has`, `get`, `names()` (sorted), `size`,
87
+ `snapshot()`, `info()`, `fingerprint()`. Keys must match `FUNCTION_NAME_PATTERN`
88
+ (`vendor:retry`, `order.chargeCard` are legal); registering a non-function throws.
89
+ - **Engine-level table** — `new GraphExecutionEngine(graph, { functions })` plus
90
+ `engine.registerFunction(name, fn)` at any time, and `engine.registry` for
91
+ `{ fingerprint, names }`.
92
+ - **Graph-level table** — `GraphDefinition.functions`, merged OVER the engine's table
93
+ (a graph may deliberately override a shared function). Mixed graphs — some nodes inline,
94
+ some named — are normal.
95
+ - **Named references** — `NodeDefinition.run` and `NextEdge.test` accept a name. Resolution is
96
+ LAZY (per evaluation), so registering a function after construction, or while a run is in
97
+ flight, still takes effect.
98
+ - **`UNKNOWN_FUNCTION`** — a missing `run` name records an unhandled error, fails the
99
+ activation, schedules nothing, and is PERMANENT: not retried, never routed through
100
+ `onThrow`/`fallback` (those are error recovery; this is a wiring defect). A missing `test`
101
+ name records the error once per node+name, never matches, and lets the rest of the chain
102
+ evaluate. The run settles FAILED because the error stays unhandled.
103
+ - **Observability** — `fn` on `NodeResult` and on `node.started` / `node.succeeded` /
104
+ `node.failed`; `{ fingerprint, names }` on `graph.ready`, `ExecutionReport.registry` and
105
+ every `ExecutionSnapshot` (omitted when no registry is configured). The fingerprint
106
+ `fnr1:<count>:<hash>` covers sorted names + function source text.
107
+ - **`edgeHasMatcher()`** — shared predicate for conditional vs. else edges; it counts a named
108
+ `test`, which fixed a lint regression (below).
109
+ - **`src/graph-json.ts`** — `parseGraphJSON()`, `createEngineFromJSON()`, `isGraphJSON()`,
110
+ `GraphJSONError` (with `issues: [{ path, message }]`). Node `id` defaults to its key; unknown
111
+ properties are rejected at every level; dangling `next`/`onThrow`/`fallback`/entry/terminal
112
+ references are reported with their JSON paths; `functions` inside the document is rejected
113
+ with a pointer to the engine options. The loader is isomorphic (string or object in, no fs).
114
+ - **`nodeFunctions()` / `edgeTests()`** — typed table builders, because a single table holds
115
+ both signatures and a bare literal cannot infer a parameter type from that union.
116
+ - **Tests** — `tests/registry.test.ts` (28) and `tests/graphjson.test.ts` (19); suite 209 → 256.
117
+ - **Examples** — `examples/json-graph.ts` + `examples/order-flow.graph.json`
118
+ (`npm run example:graphjson`): one document, four registries, including the missing-name case.
119
+
120
+ ### Changed
121
+
122
+ - `NodeDefinition.run`, `NextEdgeObject.test`, `GraphDefinition.functions`,
123
+ `EngineOptions.functions`, `NodeResult.fn`, `ExecutionSnapshot.registry`,
124
+ `ExecutionReport.registry` — all additive; `NormalizedEdge` gained `testName`.
125
+ - Validation: `run` must be a function or an identifier-like name; an edge `test` may be a
126
+ function or a name; the graph table's keys and values are checked at construction.
127
+
128
+ ### Fixed
129
+
130
+ - **Lint treated a named `test` as an else edge.** After `test` was split into
131
+ `test`/`testName`, `lintGraph()` still asked `edge.test === undefined` — so every edge after a
132
+ named matcher was reported `UNREACHABLE_EDGE`, and `PARALLEL_EDGE_MATCHER_IGNORED` was missed
133
+ for parallel edges carrying a name. Both now use `edgeHasMatcher()`; regression-guarded by
134
+ registry cases R18/R19.
135
+
136
+ ## [1.3.0] — Typed edges and explicit routing
137
+
138
+ Turns `next` from a list of destinations into an ordered, conditional edge list, and lets a node
139
+ route itself. Purely additive: every v1.0–v1.2 graph keeps its behaviour, because a string entry
140
+ normalizes to an unconditional parallel edge — exactly its old meaning.
141
+
142
+ ### Added
143
+
144
+ - **`NextEdge`** — `next` accepts, in any mix:
145
+ | declared | normalized | meaning |
146
+ |---|---|---|
147
+ | `"audit"` | `{ nextNode, parallel: true }` | unconditional fan-out (v1.0 behaviour) |
148
+ | `{ nextNode: "b", result: 1 }` | `{ …, parallel: false, hasResult: true }` | exclusive, deep equality |
149
+ | `{ nextNode: "c", test: fn }` | `{ …, parallel: false }` | exclusive, predicate |
150
+ | `{ nextNode: "d" }` | `{ …, parallel: false }` | exclusive **else** edge |
151
+ | `{ nextNode: "x", parallel: true }` | unchanged | fires regardless of the exclusive chain |
152
+
153
+ Exclusive edges are evaluated **in declaration order, first match wins**; a matched edge
154
+ suppresses later exclusive edges, parallel edges are never suppressed. No match schedules
155
+ nothing and records no error (silent). `result` uses deep equality (`deepEquals`: `Object.is`
156
+ for primitives — so `NaN` matches `NaN` — element-wise for arrays, own keys for objects,
157
+ `getTime()` for Dates, cycle-safe); `result` and `test` together must both pass.
158
+ - **Explicit routing** — a node can choose its own successor:
159
+ - `return new Next(target, value)` — `value` becomes the node's output, `target` the
160
+ destination. `Next` with an empty target throws `INVALID_NEXT_TARGET`.
161
+ - `context.goto(target)` followed by the normal `return value` — for void/imperative nodes.
162
+ - An explicit route **replaces the whole edge list** for that activation (parallel edges
163
+ included), may target any node in the graph, and is validated at runtime: an unknown target
164
+ records `INVALID_GOTO_TARGET` and schedules nothing.
165
+ - `Next` wins over `goto` when both are used; each retry attempt starts with no route set.
166
+ - **`NodeContext.goto()`**, **`Next`**, **`isNext()`**, **`normalizeNextEdges()`**,
167
+ **`deepEquals()`** exported.
168
+ - **`node.succeeded` payload** gains an optional `route: { nextNode, via: "next-class" | "goto" }`
169
+ when the activation routed itself.
170
+ - **New lint codes**: `UNREACHABLE_EDGE` (error — an exclusive edge declared after an else edge
171
+ can never fire), `PARALLEL_EDGE_MATCHER_IGNORED` (warn — parallel edges fire unconditionally,
172
+ so their matcher is ignored), `TERMINAL_EDGE_IGNORED` (warn — a terminal node latches completion
173
+ before routing, so its edges never run).
174
+ - **Validation** for edge entries: non-empty `nextNode`, `test` must be a function, `parallel`
175
+ must be a boolean, malformed entries rejected, targets must exist.
176
+ - **`tests/edges.test.ts` — 26 cases** wiring the whole contract, including the `a → b/c/d/e/f`
177
+ picture as a single execution (5 seeds) and the finding that a runtime route is invisible to
178
+ static reachability, so the edge should be declared as a fallback.
179
+ - **`examples/branching-edges.ts`** + comparison of the three approaches in `examples/README.md`.
180
+
181
+ ### Changed
182
+
183
+ - `NodeDefinition.next` is now `readonly NextEdge[]` (was `readonly string[]`). Source-compatible:
184
+ string arrays are still valid and behave identically.
185
+ - Terminal nodes: settlement order is unchanged (terminal check → routing), now documented and
186
+ warned about by lint.
187
+ - `lintGraph()` reachability follows declared edges only; runtime `Next`/`goto` targets cannot be
188
+ seen statically.
189
+
190
+ ## [1.2.0] — Success dead-end linting
191
+
192
+ Closes a linting gap found while explaining the C-19 resilience fixture: a non-terminal node
193
+ could declare `onThrow` (or a `fallback`) and never be flagged, even though its **success path
194
+ led nowhere** — the node could only "continue" by failing.
195
+
196
+ ### Added
197
+
198
+ - **`SUCCESS_DEAD_END` lint code (error tier)** — fires when a node is not a terminal node and
199
+ has no `next` target, but does have at least one live error transition (`onThrow`, or `fallback`
200
+ when the effective policy is `FALLBACK`). Message: *"…cannot continue after success (no next
201
+ target); only its error transitions are declared. Add next targets or declare it as a terminal
202
+ node"*. The rule is intentional about the difference: `DEAD_END_NODE` stays for the total case
203
+ (no `next`, no `onThrow`, no live `fallback`), and the two codes are **mutually exclusive** — a
204
+ node with no `next` always produces exactly one of them.
205
+ - **`tests/lint.test.ts` — 13 cases** covering the rule, its boundaries (terminal nodes, nodes
206
+ with `next`, inert fallbacks, live fallbacks, unreachable nodes) and its engine integration in
207
+ both `lint: "warn"` and `lint: "error"` modes. The C-19 fixture and the C-01 order-pipeline
208
+ fixture are asserted directly, so the two graphs discussed in review are now pinned by CI.
209
+ - New report kind `LINT` in the dashboard (badge, filter, inventory table).
210
+
211
+ ### Changed
212
+
213
+ - `lintGraph()` message for `DEAD_END_NODE` now spells out why it fired:
214
+ *"(no next, onThrow or fallback)"*.
215
+ - Lint lines are logged at the **issue** severity (`error` for error-tier codes) even when the
216
+ mode is `"warn"`; the mode only decides whether error-tier issues are enforced. This was already
217
+ the behaviour — it is now covered by a test so it cannot drift.
218
+
219
+ ### Upgrade notes
220
+
221
+ `lint: "error"` users may see graphs rejected that previously passed. The fix is one of:
222
+ add a `next` target, handle the failure inside the node so it always succeeds, or declare the node
223
+ in `terminalNodes` (pair it with `completionPolicy: "ALL_TERMINALS"` / `"DRAIN"` if other branches
224
+ must still complete). Linting is off by default, so default behaviour is unchanged.
225
+
226
+ ## [1.1.0] — Wave 1: correctness, policy and contract
227
+
228
+ Wave 1 turns the 18 audited behaviours into explicit guarantees. **Defaults preserve
229
+ v1.0 semantics** except for the two behaviour changes marked BREAKING below. Everything
230
+ else is additive or opt-in.
231
+
232
+ ### Added
233
+
234
+ - **`CompletionPolicy`** (`GraphDefinition.completionPolicy`): `"FIRST_TERMINAL"` (default,
235
+ v1.0 behaviour) | `"ALL_TERMINALS"` | `"DRAIN"`.
236
+ - **`activation.dropped` event** — every time the scheduler clears pending work it now
237
+ reports the dropped activations and the reason (`logical-completion`, `all-terminals-reached`,
238
+ `fail-fast`, `graph-fail-fast`, `checkpoint-failure`, `step-budget-exceeded`, `cancellation`).
239
+ - **Cancellation drain deadline** (A2): setting `EngineOptions.cancellationTimeoutMs` now also
240
+ bounds the drain. Work still running when the deadline elapses is marked
241
+ `NodeStatus.ABANDONED`, the run settles, and `ExecutionReport.drainTimedOut` /
242
+ `abandonedActivations` report what happened. Unset ⇒ v1.0 behaviour (wait for cooperative drain).
243
+ - **`NodeStatus.ABANDONED`** — a cancellation-deadline outcome distinct from `CANCELLED`.
244
+ - **Real FALLBACK routing** (A5): `NodeDefinition.fallback` is used as the error transition when
245
+ the effective error policy is `FALLBACK` and no `onThrow` is declared. The error is recorded
246
+ as handled; unknown targets are rejected at construction.
247
+ - **Bounded concurrency and step budget** (A8): `EngineOptions.maxConcurrency` (default:
248
+ unbounded) and `EngineOptions.maxActivations` — the latter records `STEP_BUDGET_EXCEEDED`,
249
+ stops scheduling and settles the run, which makes runaway cycles terminate.
250
+ - **Graph linting** (A9): exported `lintGraph(graph)` plus `EngineOptions.lint`
251
+ (`"off"` default | `"warn"` | `"error"`). Issues: `DUPLICATE_ENTRY`, `DUPLICATE_TERMINAL`,
252
+ `UNREACHABLE_NODE`, `UNREACHABLE_TERMINAL`, `DEAD_END_NODE`, `FALLBACK_TARGET_IGNORED`.
253
+ Findings are surfaced on `ExecutionReport.lintIssues`.
254
+ - **`node.cancelled` event** — emitted when an activation settles as cancelled.
255
+ - **Introspection**: `getInFlightActivationIds()`, `getLintIssues()`.
256
+ - **Storage adapter contract** (`src/storage-adapter.ts`): `StorageAdapter`, `StorageCapabilities`,
257
+ shape validation (`storageAdapterViolations`, `assertStorageAdapter`, `defineStorageAdapter`) and
258
+ a canonical JSON wire format (`serializeSnapshot`, `deserializeSnapshot`, `stableStringify`,
259
+ `compareSnapshots`, `SNAPSHOT_FORMAT`/`SNAPSHOT_FORMAT_VERSION`).
260
+ - **Storage conformance suite** (`src/storage-conformance.ts`): `runStorageConformance(fixture)`
261
+ verifies round-trip, missing-key behavior, execution isolation, no aliasing on save/load,
262
+ overwrite, deterministic serialization, JSON stability, failed-save atomicity (when the fixture
263
+ can inject faults), listing, delete idempotency and close.
264
+ - **`InMemoryStorage`** is now a full reference adapter: `capabilities`, `listExecutionIds()`,
265
+ `delete()`, `close()`.
266
+ - **Isomorphic build proof**: `tsconfig.browser.json` (DOM libs, `types: []`, `noEmit`) plus a test
267
+ that asserts the core compiles with no Node APIs. Timers are `setTimeout`-based only.
268
+ - **New test packs**: `tests/upgrade.test.ts` (19 cases) and `tests/conformance.test.ts` (11 cases),
269
+ wired into a kind-driven harness/report. Suite total: **170 tests**.
270
+
271
+ ### Changed (BREAKING)
272
+
273
+ - **A1 — cancellation symmetry.** A node callback that *rejects because of* an abort now settles
274
+ `NodeStatus.CANCELLED` with a **handled** `GRAPH_CANCELLED` error record. Previously it settled
275
+ `FAILED` with an unhandled error, which made cooperative cancellation asymmetric.
276
+ - **A4 — `FAIL_FAST` is sticky.** A node-level `FAIL_FAST` now installs the same persistent stop as
277
+ a graph-level one (it previously flushed the pending queue once, which was a no-op under unbounded
278
+ concurrency). A node-level `CONTINUE` still cannot shield other branches from a graph-level stop.
279
+
280
+ ### Fixed
281
+
282
+ - **A6 — `NodeResult.error`** is now populated for `FAILED` and `TIMED_OUT` activations (the field
283
+ existed but was never assigned).
284
+ - **A10 — dead code and hygiene**: removed the unused `resolveRun` field; in-flight activations are
285
+ tracked and released deterministically (`getInFlightActivationIds()` is empty after a settle);
286
+ `NodeJS.ErrnoException` replaced with a structural cast so the core compiles without Node types.
287
+
288
+ ### Notes
289
+
290
+ - `cancel()` still returns before the graph settles; in-flight work drains cooperatively. Outputs
291
+ produced by abandoned activations are never adopted.
292
+ - `InMemoryStorage.version` moved to `1.1.0` to reflect the adapter surface.
293
+
294
+ ## [1.0.0]
295
+
296
+ - Initial single-file engine: graph validation, event bus, in-memory storage, scheduler with
297
+ unbounded concurrency, retry/error policies, cooperative cancellation, atomic checkpoints.
298
+ - Audit suite: 140 tests (100 unit, 20 complex integration, 20 async-callback) documenting
299
+ 18 engine behaviours, with a self-contained HTML/JSON report and a live preview server.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 ammisaidkaidi
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.