taskflow-core 0.1.8 → 0.2.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 +80 -25
- package/dist/deterministic.d.ts +8 -0
- package/dist/deterministic.d.ts.map +1 -1
- package/dist/deterministic.js +23 -1
- package/dist/deterministic.js.map +1 -1
- package/dist/exec/driver.d.ts +47 -0
- package/dist/exec/driver.d.ts.map +1 -0
- package/dist/exec/driver.js +397 -0
- package/dist/exec/driver.js.map +1 -0
- package/dist/exec/events.d.ts +76 -0
- package/dist/exec/events.d.ts.map +1 -0
- package/dist/exec/events.js +108 -0
- package/dist/exec/events.js.map +1 -0
- package/dist/exec/fold.d.ts +45 -0
- package/dist/exec/fold.d.ts.map +1 -0
- package/dist/exec/fold.js +122 -0
- package/dist/exec/fold.js.map +1 -0
- package/dist/exec/index.d.ts +17 -0
- package/dist/exec/index.d.ts.map +1 -0
- package/dist/exec/index.js +17 -0
- package/dist/exec/index.js.map +1 -0
- package/dist/exec/kernel-policy.d.ts +35 -0
- package/dist/exec/kernel-policy.d.ts.map +1 -0
- package/dist/exec/kernel-policy.js +184 -0
- package/dist/exec/kernel-policy.js.map +1 -0
- package/dist/exec/step-kinds.d.ts +41 -0
- package/dist/exec/step-kinds.d.ts.map +1 -0
- package/dist/exec/step-kinds.js +574 -0
- package/dist/exec/step-kinds.js.map +1 -0
- package/dist/exec/step.d.ts +93 -0
- package/dist/exec/step.d.ts.map +1 -0
- package/dist/exec/step.js +436 -0
- package/dist/exec/step.js.map +1 -0
- package/dist/flowir/canonical-hash.d.ts +101 -0
- package/dist/flowir/canonical-hash.d.ts.map +1 -0
- package/dist/flowir/canonical-hash.js +219 -0
- package/dist/flowir/canonical-hash.js.map +1 -0
- package/dist/flowir/compile.d.ts +49 -0
- package/dist/flowir/compile.d.ts.map +1 -0
- package/dist/flowir/compile.js +219 -0
- package/dist/flowir/compile.js.map +1 -0
- package/dist/flowir/cond.d.ts +76 -0
- package/dist/flowir/cond.d.ts.map +1 -0
- package/dist/flowir/cond.js +214 -0
- package/dist/flowir/cond.js.map +1 -0
- package/dist/flowir/index.d.ts +20 -23
- package/dist/flowir/index.d.ts.map +1 -1
- package/dist/flowir/index.js +47 -35
- package/dist/flowir/index.js.map +1 -1
- package/dist/flowir/meta.d.ts +4 -4
- package/dist/flowir/meta.d.ts.map +1 -1
- package/dist/flowir/phasefp.d.ts.map +1 -1
- package/dist/flowir/phasefp.js +13 -1
- package/dist/flowir/phasefp.js.map +1 -1
- package/dist/flowir/schema.d.ts +260 -0
- package/dist/flowir/schema.d.ts.map +1 -0
- package/dist/flowir/schema.js +234 -0
- package/dist/flowir/schema.js.map +1 -0
- package/dist/flowir/translate.d.ts.map +1 -1
- package/dist/flowir/translate.js +3 -0
- package/dist/flowir/translate.js.map +1 -1
- package/dist/host/runner-types.d.ts +3 -0
- package/dist/host/runner-types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/rates.d.ts +121 -0
- package/dist/rates.d.ts.map +1 -0
- package/dist/rates.js +187 -0
- package/dist/rates.js.map +1 -0
- package/dist/replay.d.ts +43 -50
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +629 -19
- package/dist/replay.js.map +1 -1
- package/dist/runner-core.d.ts +24 -0
- package/dist/runner-core.d.ts.map +1 -1
- package/dist/runner-core.js +225 -32
- package/dist/runner-core.js.map +1 -1
- package/dist/runtime/phases/approval.d.ts +23 -0
- package/dist/runtime/phases/approval.d.ts.map +1 -0
- package/dist/runtime/phases/approval.js +38 -0
- package/dist/runtime/phases/approval.js.map +1 -0
- package/dist/runtime/phases/expand.d.ts +29 -0
- package/dist/runtime/phases/expand.d.ts.map +1 -0
- package/dist/runtime/phases/expand.js +126 -0
- package/dist/runtime/phases/expand.js.map +1 -0
- package/dist/runtime/phases/parallel.d.ts +26 -0
- package/dist/runtime/phases/parallel.d.ts.map +1 -0
- package/dist/runtime/phases/parallel.js +15 -0
- package/dist/runtime/phases/parallel.js.map +1 -0
- package/dist/runtime/phases/race.d.ts +31 -0
- package/dist/runtime/phases/race.d.ts.map +1 -0
- package/dist/runtime/phases/race.js +220 -0
- package/dist/runtime/phases/race.js.map +1 -0
- package/dist/runtime/phases/script.d.ts +38 -0
- package/dist/runtime/phases/script.d.ts.map +1 -0
- package/dist/runtime/phases/script.js +139 -0
- package/dist/runtime/phases/script.js.map +1 -0
- package/dist/runtime.d.ts +29 -16
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +554 -292
- package/dist/runtime.js.map +1 -1
- package/dist/schema.d.ts +36 -4
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +86 -10
- package/dist/schema.js.map +1 -1
- package/dist/store.d.ts +6 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js.map +1 -1
- package/dist/trace.d.ts +4 -1
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +33 -26
- package/dist/trace.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-hash.d.ts","sourceRoot":"","sources":["../../src/flowir/canonical-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8EtD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAoBrD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEzD;AAMD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAGjD"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Genuine content-addressed hash over canonical FlowIR
|
|
3
|
+
*
|
|
4
|
+
* This is pi-taskflow's **own** content-addressed hash (Q5=own) over the
|
|
5
|
+
* canonical {@link FlowIR} type contract defined in `./schema.ts`. Unlike
|
|
6
|
+
* `./hash.ts`'s `flowDefHash` — which fingerprints the *DSL Taskflow
|
|
7
|
+
* definition* (vendored from overstory, async Web Crypto, truncated to 16
|
|
8
|
+
* bytes / 32 hex) — this module hashes the *compiled IR* synchronously with
|
|
9
|
+
* `node:crypto` SHA-256 (full 64-hex-char digest). The two answer different
|
|
10
|
+
* questions:
|
|
11
|
+
*
|
|
12
|
+
* - `flowDefHash(def)` — "did the *flow definition* change?" (DSL-level)
|
|
13
|
+
* - `hashFlowIR(ir)` — "is this *compiled IR* the same IR?" (IR-level)
|
|
14
|
+
*
|
|
15
|
+
* Wired into `compileTaskflowToIR` via `hashFlowIR` (`ir:<64-hex>`).
|
|
16
|
+
* The IR-level hash is the content-addressed key the event-sourced kernel
|
|
17
|
+
* (batch 2, `flowir/compile.ts`) will use to deduplicate compiled graphs and
|
|
18
|
+
* to key per-phase fingerprints (`hashNode`) independent of surface formatting.
|
|
19
|
+
*
|
|
20
|
+
* ## Canonicalization guarantees
|
|
21
|
+
*
|
|
22
|
+
* {@link canonicalizeFlowIR} produces a DETERMINISTIC serialization that is
|
|
23
|
+
* invariant under the cosmetic differences two logically-equivalent IRs may
|
|
24
|
+
* differ by:
|
|
25
|
+
*
|
|
26
|
+
* - **Object key order** — all object keys are stable-sorted (UTF-16 code
|
|
27
|
+
* units), so reordering fields on a node or on the IR object does not
|
|
28
|
+
* change the canonical form.
|
|
29
|
+
* - **Node array order** — `nodes` is re-sorted by `id` (stable), so two IRs
|
|
30
|
+
* that list the same nodes in different order canonicalize identically.
|
|
31
|
+
* Other arrays (`inject`, `emits`, `deps`, `edges`) keep their order —
|
|
32
|
+
* their ordering is semantically meaningful (declared-read order, edge
|
|
33
|
+
* declaration order) and must be preserved.
|
|
34
|
+
* - **Condition spelling** — a node's `when` guard is canonicalized via
|
|
35
|
+
* {@link normalizeCond} (`./cond.ts`), which collapses whitespace, operator
|
|
36
|
+
* spacing, and redundant enclosing parens while preserving string-literal
|
|
37
|
+
* contents. Equivalent conditions canonicalize identically.
|
|
38
|
+
* - **Insignificant formatting** — `undefined` optional fields are dropped,
|
|
39
|
+
* so their presence/absence does not change the hash.
|
|
40
|
+
*
|
|
41
|
+
* Two logically-equivalent FlowIRs (key reorder, whitespace, equivalent
|
|
42
|
+
* condition spelling) MUST canonicalize to the same string.
|
|
43
|
+
*
|
|
44
|
+
* ## Domain-separated digests
|
|
45
|
+
*
|
|
46
|
+
* Return values are prefixed:
|
|
47
|
+
* - `hashFlowIR` → `ir:<64-hex>`
|
|
48
|
+
* - `hashNode` → `node:<64-hex>`
|
|
49
|
+
*
|
|
50
|
+
* so IR-level and node-level digests never collide in a shared cache key
|
|
51
|
+
* namespace.
|
|
52
|
+
*
|
|
53
|
+
* ## Purity
|
|
54
|
+
*
|
|
55
|
+
* Zero runtime deps besides `node:crypto`. No IO, no `Date`, no randomness,
|
|
56
|
+
* never throws. The hash is synchronous (unlike `flowDefHash`).
|
|
57
|
+
*
|
|
58
|
+
* Exported from the FlowIR barrel (`./index.ts`) for consumers; **not yet
|
|
59
|
+
* wired** into runtime/cache — `usedFallbackHash` is untouched. Wiring
|
|
60
|
+
* happens in batch 2.
|
|
61
|
+
*
|
|
62
|
+
* @see ./schema.ts for the canonical FlowIR type contract.
|
|
63
|
+
* @see ./cond.ts for condition normalization (`normalizeCond`).
|
|
64
|
+
* @see ./hash.ts for the DSL-level `flowDefHash` (vendored from overstory).
|
|
65
|
+
*/
|
|
66
|
+
import { createHash } from "node:crypto";
|
|
67
|
+
import { normalizeCond } from "./cond.js";
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Canonical serializer (key-sorted, undefined-dropped, array-order-preserving)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* Deterministic serialization of a plain-JSON-compatible value: recursively
|
|
73
|
+
* key-sorts objects (UTF-16 code units), drops `undefined` values, preserves
|
|
74
|
+
* array order, and uses `JSON.stringify` for primitives/null. This mirrors
|
|
75
|
+
* the canonical-JSON convention used by `./hash.ts`'s `canonicalJson` (itself
|
|
76
|
+
* vendored from overstory) so the two hashes share the same primitive
|
|
77
|
+
* encoding — but this serializer is local to the IR hash so the IR-level
|
|
78
|
+
* contract can evolve independently of the overstory-vendored DSL hash.
|
|
79
|
+
*
|
|
80
|
+
* The value passed in MUST be pre-canonicalized (nodes sorted by id, `when`
|
|
81
|
+
* normalized) — this function does not know about FlowIR semantics, it only
|
|
82
|
+
* guarantees key-order independence and `undefined`-dropping.
|
|
83
|
+
*/
|
|
84
|
+
function canonicalSerialize(value) {
|
|
85
|
+
if (value === null)
|
|
86
|
+
return "null";
|
|
87
|
+
if (typeof value === "boolean")
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
if (typeof value === "number")
|
|
90
|
+
return JSON.stringify(value);
|
|
91
|
+
if (typeof value === "string")
|
|
92
|
+
return JSON.stringify(value);
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return `[${value.map((item) => canonicalSerialize(item)).join(",")}]`;
|
|
95
|
+
}
|
|
96
|
+
if (typeof value === "object") {
|
|
97
|
+
const record = value;
|
|
98
|
+
const keys = Object.keys(record)
|
|
99
|
+
.filter((key) => record[key] !== undefined)
|
|
100
|
+
.sort();
|
|
101
|
+
const body = keys.map((key) => `${JSON.stringify(key)}:${canonicalSerialize(record[key])}`);
|
|
102
|
+
return `{${body.join(",")}}`;
|
|
103
|
+
}
|
|
104
|
+
// undefined / function / symbol / bigint at this layer — not representable.
|
|
105
|
+
return "null";
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Canonical node object (drops undefined, normalizes `when`)
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* Project a {@link FlowIRNode} into a plain canonical object form: fields are
|
|
112
|
+
* emitted in a fixed order (order is irrelevant — `canonicalSerialize`
|
|
113
|
+
* re-sorts keys, but emitting canonically makes the intermediate form
|
|
114
|
+
* human-inspectable), `when` is normalized via {@link normalizeCond} so
|
|
115
|
+
* equivalent condition spellings collapse, and `undefined` optionals are
|
|
116
|
+
* omitted so their presence/absence does not affect the hash.
|
|
117
|
+
*
|
|
118
|
+
* `inject`/`emits`/`deps` arrays are preserved verbatim (order is semantic —
|
|
119
|
+
* declared-read order matters for fingerprinting).
|
|
120
|
+
*/
|
|
121
|
+
function canonicalNodeObject(node) {
|
|
122
|
+
const obj = {
|
|
123
|
+
id: node.id,
|
|
124
|
+
kind: node.kind,
|
|
125
|
+
inject: node.inject,
|
|
126
|
+
emits: node.emits,
|
|
127
|
+
};
|
|
128
|
+
if (node.when !== undefined) {
|
|
129
|
+
obj.when = normalizeCond(node.when).canonical;
|
|
130
|
+
}
|
|
131
|
+
if (node.task !== undefined)
|
|
132
|
+
obj.task = node.task;
|
|
133
|
+
if (node.condRef !== undefined)
|
|
134
|
+
obj.condRef = node.condRef;
|
|
135
|
+
if (node.deps !== undefined)
|
|
136
|
+
obj.deps = node.deps;
|
|
137
|
+
if (node.join !== undefined)
|
|
138
|
+
obj.join = node.join;
|
|
139
|
+
if (node.timeout !== undefined)
|
|
140
|
+
obj.timeout = node.timeout;
|
|
141
|
+
if (node.payload !== undefined)
|
|
142
|
+
obj.payload = node.payload;
|
|
143
|
+
return obj;
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Public canonicalization
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Deterministic, order- and whitespace-independent canonical serialization of
|
|
150
|
+
* a {@link FlowIR}.
|
|
151
|
+
*
|
|
152
|
+
* - `nodes` is re-sorted by `id` (stable) so node-list order does not affect
|
|
153
|
+
* the result.
|
|
154
|
+
* - Each node's `when` guard is canonicalized via {@link normalizeCond}.
|
|
155
|
+
* - All object keys are stable-sorted; `undefined` optionals are dropped.
|
|
156
|
+
* - `inject`/`emits`/`deps`/`edges` arrays keep their order (semantic).
|
|
157
|
+
*
|
|
158
|
+
* Two logically-equivalent FlowIRs MUST canonicalize to the same string.
|
|
159
|
+
* Never throws.
|
|
160
|
+
*/
|
|
161
|
+
export function canonicalizeFlowIR(ir) {
|
|
162
|
+
// Stable sort by node id — node-list order is not semantic.
|
|
163
|
+
const sortedNodes = [...ir.nodes].sort((a, b) => {
|
|
164
|
+
if (a.id < b.id)
|
|
165
|
+
return -1;
|
|
166
|
+
if (a.id > b.id)
|
|
167
|
+
return 1;
|
|
168
|
+
return 0;
|
|
169
|
+
});
|
|
170
|
+
const obj = {
|
|
171
|
+
name: ir.name,
|
|
172
|
+
nodes: sortedNodes.map((n) => canonicalNodeObject(n)),
|
|
173
|
+
};
|
|
174
|
+
if (ir.version !== undefined)
|
|
175
|
+
obj.version = ir.version;
|
|
176
|
+
if (ir.edges !== undefined)
|
|
177
|
+
obj.edges = ir.edges;
|
|
178
|
+
if (ir.args !== undefined)
|
|
179
|
+
obj.args = ir.args;
|
|
180
|
+
if (ir.budget !== undefined)
|
|
181
|
+
obj.budget = ir.budget;
|
|
182
|
+
if (ir.concurrency !== undefined)
|
|
183
|
+
obj.concurrency = ir.concurrency;
|
|
184
|
+
if (ir.meta !== undefined)
|
|
185
|
+
obj.meta = ir.meta;
|
|
186
|
+
return canonicalSerialize(obj);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Deterministic canonical serialization of a single {@link FlowIRNode}, for
|
|
190
|
+
* per-node content addressing. Same canonicalization rules as
|
|
191
|
+
* {@link canonicalizeFlowIR} applied to one node (`when` normalized, keys
|
|
192
|
+
* sorted, `undefined` optionals dropped, `inject`/`emits`/`deps` order
|
|
193
|
+
* preserved). Never throws.
|
|
194
|
+
*/
|
|
195
|
+
export function canonicalizeNode(node) {
|
|
196
|
+
return canonicalSerialize(canonicalNodeObject(node));
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Content-addressed hashing (node:crypto SHA-256, domain-prefixed)
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Domain-separated content hash of a compiled FlowIR.
|
|
203
|
+
* Returns `ir:<64 lowercase hex SHA-256 chars>` over {@link canonicalizeFlowIR}.
|
|
204
|
+
* Synchronous. Never throws.
|
|
205
|
+
*/
|
|
206
|
+
export function hashFlowIR(ir) {
|
|
207
|
+
const hex = createHash("sha256").update(canonicalizeFlowIR(ir), "utf8").digest("hex");
|
|
208
|
+
return `ir:${hex}`;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Domain-separated per-node content hash.
|
|
212
|
+
* Returns `node:<64 lowercase hex SHA-256 chars>` over {@link canonicalizeNode}.
|
|
213
|
+
* Synchronous. Never throws.
|
|
214
|
+
*/
|
|
215
|
+
export function hashNode(node) {
|
|
216
|
+
const hex = createHash("sha256").update(canonicalizeNode(node), "utf8").digest("hex");
|
|
217
|
+
return `node:${hex}`;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=canonical-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-hash.js","sourceRoot":"","sources":["../../src/flowir/canonical-hash.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genuine Taskflow → FlowIR compiler (RFC §5.2, S0).
|
|
3
|
+
*
|
|
4
|
+
* This graduates the stub `translateTaskflow` into a **canonical** IR emitter:
|
|
5
|
+
* 1:1 phase→node projection (native multi-node lowering deferred per §12),
|
|
6
|
+
* declared inject/emits, explicit edges, normalized `when` via {@link normalizeCond},
|
|
7
|
+
* and richer optional node fields (`task`, `deps`, `join`, `timeout`, `condRef`).
|
|
8
|
+
*
|
|
9
|
+
* Pure, synchronous, never throws — diagnostics live in the return value so
|
|
10
|
+
* `/tf ir` on a broken flow still yields a structured report.
|
|
11
|
+
*
|
|
12
|
+
* @see docs/rfc-0.2.0-architecture.md §5
|
|
13
|
+
* @see ./translate.ts (stub; still used for sidecar field list parity)
|
|
14
|
+
*/
|
|
15
|
+
import { type Taskflow } from "../schema.ts";
|
|
16
|
+
import type { FlowIR as CanonicalFlowIR } from "./schema.ts";
|
|
17
|
+
import type { CompileError, CompileWarning, FlowIR, TaskflowIRMeta } from "./meta.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Result of {@link compileTaskflowToFlowIR}: canonical IR + declared meta + diagnostics.
|
|
20
|
+
* `usedFallbackHash` is **false** when the IR is well-formed enough to content-address
|
|
21
|
+
* (always, unless hard errors prevent IR emission).
|
|
22
|
+
*
|
|
23
|
+
* Named separately from Mermaid `compileTaskflow` in `../compile.ts` (diagram).
|
|
24
|
+
*/
|
|
25
|
+
export interface CompileTaskflowToFlowIRResult {
|
|
26
|
+
/** Canonical FlowIR (superset of the stub projection). */
|
|
27
|
+
canonical: CanonicalFlowIR;
|
|
28
|
+
/** Stub-compatible IR projection (same nodes without requiring consumers to import schema.ts). */
|
|
29
|
+
ir: FlowIR;
|
|
30
|
+
meta: TaskflowIRMeta;
|
|
31
|
+
warnings: CompileWarning[];
|
|
32
|
+
errors: CompileError[];
|
|
33
|
+
/** False once the genuine compiler owns the hash (S0). */
|
|
34
|
+
usedFallbackHash: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compile a (desugared) Taskflow into canonical FlowIR.
|
|
38
|
+
*
|
|
39
|
+
* - One node per phase (1:1).
|
|
40
|
+
* - `inject` = `{steps.*}` refs ∪ `dependsOn` (minus self).
|
|
41
|
+
* - `emits` = `[phase.id]`.
|
|
42
|
+
* - `edges` synthesized from inject for graph consumers.
|
|
43
|
+
* - `when` kept as source text; `condRef` = normalizeCond(canonical) when present.
|
|
44
|
+
* - Optional fields: task, deps, join, timeout copied when set.
|
|
45
|
+
*
|
|
46
|
+
* Never throws.
|
|
47
|
+
*/
|
|
48
|
+
export declare function compileTaskflowToFlowIR(def: Taskflow): CompileTaskflowToFlowIRResult;
|
|
49
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/flowir/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAwD,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEnG,OAAO,KAAK,EACX,MAAM,IAAI,eAAe,EAIzB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACX,YAAY,EACZ,cAAc,EAEd,MAAM,EAEN,cAAc,EACd,MAAM,WAAW,CAAC;AA4EnB;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B;IAC7C,0DAA0D;IAC1D,SAAS,EAAE,eAAe,CAAC;IAC3B,kGAAkG;IAClG,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,0DAA0D;IAC1D,gBAAgB,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,QAAQ,GAAG,6BAA6B,CA+HpF"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genuine Taskflow → FlowIR compiler (RFC §5.2, S0).
|
|
3
|
+
*
|
|
4
|
+
* This graduates the stub `translateTaskflow` into a **canonical** IR emitter:
|
|
5
|
+
* 1:1 phase→node projection (native multi-node lowering deferred per §12),
|
|
6
|
+
* declared inject/emits, explicit edges, normalized `when` via {@link normalizeCond},
|
|
7
|
+
* and richer optional node fields (`task`, `deps`, `join`, `timeout`, `condRef`).
|
|
8
|
+
*
|
|
9
|
+
* Pure, synchronous, never throws — diagnostics live in the return value so
|
|
10
|
+
* `/tf ir` on a broken flow still yields a structured report.
|
|
11
|
+
*
|
|
12
|
+
* @see docs/rfc-0.2.0-architecture.md §5
|
|
13
|
+
* @see ./translate.ts (stub; still used for sidecar field list parity)
|
|
14
|
+
*/
|
|
15
|
+
import { collectRefs, PHASE_TYPES } from "../schema.js";
|
|
16
|
+
import { normalizeCond } from "./cond.js";
|
|
17
|
+
// Keep in sync with translate.ts SIDECAR_PHASE_FIELDS (round-trip lossless).
|
|
18
|
+
const SIDECAR_PHASE_FIELDS = [
|
|
19
|
+
"agent",
|
|
20
|
+
"task",
|
|
21
|
+
"over",
|
|
22
|
+
"as",
|
|
23
|
+
"branches",
|
|
24
|
+
"from",
|
|
25
|
+
"use",
|
|
26
|
+
"def",
|
|
27
|
+
"with",
|
|
28
|
+
"run",
|
|
29
|
+
"input",
|
|
30
|
+
"timeout",
|
|
31
|
+
"until",
|
|
32
|
+
"maxIterations",
|
|
33
|
+
"convergence",
|
|
34
|
+
"reflexion",
|
|
35
|
+
"variants",
|
|
36
|
+
"judge",
|
|
37
|
+
"judgeAgent",
|
|
38
|
+
"mode",
|
|
39
|
+
"dependsOn",
|
|
40
|
+
"join",
|
|
41
|
+
"when",
|
|
42
|
+
"retry",
|
|
43
|
+
"output",
|
|
44
|
+
"expect",
|
|
45
|
+
"model",
|
|
46
|
+
"thinking",
|
|
47
|
+
"tools",
|
|
48
|
+
"cwd",
|
|
49
|
+
"final",
|
|
50
|
+
"optional",
|
|
51
|
+
"idempotent",
|
|
52
|
+
"concurrency",
|
|
53
|
+
"context",
|
|
54
|
+
"contextLimit",
|
|
55
|
+
"onBlock",
|
|
56
|
+
"eval",
|
|
57
|
+
"score",
|
|
58
|
+
"cache",
|
|
59
|
+
"shareContext",
|
|
60
|
+
"cancelLosers",
|
|
61
|
+
"expandMode",
|
|
62
|
+
"maxNodes",
|
|
63
|
+
];
|
|
64
|
+
const NODE_FIELD_KEYS = new Set(["task", "dependsOn", "join", "when", "timeout"]);
|
|
65
|
+
const VALID_KINDS = new Set(PHASE_TYPES);
|
|
66
|
+
function sidecarForPhase(phase) {
|
|
67
|
+
const out = {};
|
|
68
|
+
const rec = phase;
|
|
69
|
+
for (const k of SIDECAR_PHASE_FIELDS) {
|
|
70
|
+
if (k in rec && rec[k] !== undefined)
|
|
71
|
+
out[k] = rec[k];
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function payloadForPhase(phase) {
|
|
76
|
+
const sidecar = sidecarForPhase(phase);
|
|
77
|
+
for (const key of NODE_FIELD_KEYS)
|
|
78
|
+
delete sidecar[key];
|
|
79
|
+
return Object.keys(sidecar).length > 0 ? sidecar : undefined;
|
|
80
|
+
}
|
|
81
|
+
function asKind(type) {
|
|
82
|
+
const k = (type ?? "agent");
|
|
83
|
+
if (VALID_KINDS.has(k))
|
|
84
|
+
return k;
|
|
85
|
+
// Unknown kinds still project as agent for IR shape; validation is source of truth.
|
|
86
|
+
return "agent";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compile a (desugared) Taskflow into canonical FlowIR.
|
|
90
|
+
*
|
|
91
|
+
* - One node per phase (1:1).
|
|
92
|
+
* - `inject` = `{steps.*}` refs ∪ `dependsOn` (minus self).
|
|
93
|
+
* - `emits` = `[phase.id]`.
|
|
94
|
+
* - `edges` synthesized from inject for graph consumers.
|
|
95
|
+
* - `when` kept as source text; `condRef` = normalizeCond(canonical) when present.
|
|
96
|
+
* - Optional fields: task, deps, join, timeout copied when set.
|
|
97
|
+
*
|
|
98
|
+
* Never throws.
|
|
99
|
+
*/
|
|
100
|
+
export function compileTaskflowToFlowIR(def) {
|
|
101
|
+
const warnings = [];
|
|
102
|
+
const errors = [];
|
|
103
|
+
const declaredDeps = {};
|
|
104
|
+
const sidecarPhases = {};
|
|
105
|
+
const knownIds = new Set((def.phases ?? []).map((p) => p.id));
|
|
106
|
+
if (!def.name || typeof def.name !== "string") {
|
|
107
|
+
errors.push({ code: "missing-name", message: "Taskflow.name is required" });
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(def.phases) || def.phases.length === 0) {
|
|
110
|
+
errors.push({ code: "empty-phases", message: "Taskflow.phases must be a non-empty array" });
|
|
111
|
+
}
|
|
112
|
+
const nodes = [];
|
|
113
|
+
const edges = [];
|
|
114
|
+
for (const phase of def.phases ?? []) {
|
|
115
|
+
if (!phase?.id) {
|
|
116
|
+
errors.push({ code: "missing-phase-id", message: "Phase missing id" });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const refs = collectRefs(phase);
|
|
120
|
+
const reads = new Set(refs.steps.filter((id) => id !== phase.id));
|
|
121
|
+
for (const d of phase.dependsOn ?? []) {
|
|
122
|
+
if (d !== phase.id)
|
|
123
|
+
reads.add(d);
|
|
124
|
+
}
|
|
125
|
+
for (const d of phase.from ?? []) {
|
|
126
|
+
if (d !== phase.id)
|
|
127
|
+
reads.add(d);
|
|
128
|
+
}
|
|
129
|
+
const inject = Array.from(reads);
|
|
130
|
+
declaredDeps[phase.id] = { reads: inject, writes: [phase.id] };
|
|
131
|
+
for (const r of refs.steps) {
|
|
132
|
+
if (r !== phase.id && !knownIds.has(r)) {
|
|
133
|
+
warnings.push({
|
|
134
|
+
phaseId: phase.id,
|
|
135
|
+
message: `references {steps.${r}.*} but no phase '${r}' exists`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const kind = asKind(phase.type);
|
|
140
|
+
if (phase.type && !VALID_KINDS.has(phase.type)) {
|
|
141
|
+
warnings.push({
|
|
142
|
+
phaseId: phase.id,
|
|
143
|
+
message: `unknown phase type '${phase.type}' — projected as kind 'agent'`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const node = {
|
|
147
|
+
id: phase.id,
|
|
148
|
+
kind,
|
|
149
|
+
inject,
|
|
150
|
+
emits: [phase.id],
|
|
151
|
+
};
|
|
152
|
+
if (phase.when !== undefined) {
|
|
153
|
+
node.when = phase.when;
|
|
154
|
+
const n = normalizeCond(phase.when);
|
|
155
|
+
// condRef is a stable, hashable form of the guard (not a storage key).
|
|
156
|
+
node.condRef = n.canonical || undefined;
|
|
157
|
+
}
|
|
158
|
+
if (typeof phase.task === "string")
|
|
159
|
+
node.task = phase.task;
|
|
160
|
+
if (phase.dependsOn && phase.dependsOn.length > 0)
|
|
161
|
+
node.deps = [...phase.dependsOn];
|
|
162
|
+
if (phase.join === "all" || phase.join === "any")
|
|
163
|
+
node.join = phase.join;
|
|
164
|
+
if (typeof phase.timeout === "number")
|
|
165
|
+
node.timeout = phase.timeout;
|
|
166
|
+
const payload = payloadForPhase(phase);
|
|
167
|
+
if (payload)
|
|
168
|
+
node.payload = payload;
|
|
169
|
+
for (const from of inject) {
|
|
170
|
+
edges.push({ from, to: phase.id });
|
|
171
|
+
}
|
|
172
|
+
sidecarPhases[phase.id] = sidecarForPhase(phase);
|
|
173
|
+
nodes.push(node);
|
|
174
|
+
}
|
|
175
|
+
const canonical = {
|
|
176
|
+
name: def.name || "unnamed",
|
|
177
|
+
version: typeof def.version === "number" ? def.version : 1,
|
|
178
|
+
nodes,
|
|
179
|
+
edges: edges.length > 0 ? edges : undefined,
|
|
180
|
+
args: def.args,
|
|
181
|
+
budget: def.budget,
|
|
182
|
+
concurrency: def.concurrency,
|
|
183
|
+
meta: {
|
|
184
|
+
source: "taskflow-core",
|
|
185
|
+
irVersion: 1,
|
|
186
|
+
// These flow-level switches change agent discovery / runtime inputs and
|
|
187
|
+
// therefore MUST participate in the content-addressed cache identity.
|
|
188
|
+
// Keeping them in canonical meta preserves the 1:1 node projection while
|
|
189
|
+
// preventing two semantically different flows from sharing an IR hash.
|
|
190
|
+
annotations: {
|
|
191
|
+
agentScope: def.agentScope ?? "user",
|
|
192
|
+
contextSharing: def.contextSharing ?? false,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
// Stub-compatible projection: strip fields meta.FlowIR doesn't declare but keep inject/emits/when/kind.
|
|
197
|
+
const ir = {
|
|
198
|
+
name: canonical.name,
|
|
199
|
+
nodes: nodes.map((n) => ({
|
|
200
|
+
id: n.id,
|
|
201
|
+
kind: n.kind,
|
|
202
|
+
inject: n.inject,
|
|
203
|
+
emits: n.emits,
|
|
204
|
+
when: n.when,
|
|
205
|
+
})),
|
|
206
|
+
args: def.args,
|
|
207
|
+
budget: def.budget,
|
|
208
|
+
concurrency: def.concurrency,
|
|
209
|
+
};
|
|
210
|
+
const meta = {
|
|
211
|
+
sourceFlowName: def.name || "unnamed",
|
|
212
|
+
declaredDeps,
|
|
213
|
+
sidecar: { phases: sidecarPhases },
|
|
214
|
+
};
|
|
215
|
+
// Genuine compiler owns the IR hash when we produced nodes.
|
|
216
|
+
const usedFallbackHash = nodes.length === 0 || errors.some((e) => e.code === "empty-phases");
|
|
217
|
+
return { canonical, ir, meta, warnings, errors, usedFallbackHash };
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/flowir/compile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical normalization of `when` / `until` / `eval` condition expressions.
|
|
3
|
+
*
|
|
4
|
+
* This is the **condition-IR seam** of the overstory-convergence roadmap's
|
|
5
|
+
* FlowIR compiler: it lifts a raw condition string into a stable
|
|
6
|
+
* {@link NormalizedCond} descriptor that two future consumers share:
|
|
7
|
+
*
|
|
8
|
+
* - **Hashing** (consumer a): structurally-equivalent conditions MUST
|
|
9
|
+
* normalize to the same `canonical` string so they fold identically into
|
|
10
|
+
* the FlowIR content hash (whitespace, operator spacing, and redundant
|
|
11
|
+
* enclosing parens do not change meaning → must not change the hash).
|
|
12
|
+
* - **Deterministic replay** (consumer b): the `when-guard` decision is
|
|
13
|
+
* re-evaluated from the recorded expression; `refs` tells replay exactly
|
|
14
|
+
* which `steps.*` / `args.*` / `env.*` inputs the guard read so it can
|
|
15
|
+
* rebind them from the event log without token spend.
|
|
16
|
+
*
|
|
17
|
+
* **Single source of truth — no semantic drift.** The condition grammar
|
|
18
|
+
* (tokenizer + recursive-descent parser + comparison/truthiness semantics)
|
|
19
|
+
* lives in {@link ../interpolate.ts}. This module REUSES that parser by
|
|
20
|
+
* delegating parse-validation (and fail-open semantics) to
|
|
21
|
+
* {@link tryEvaluateCondition} — it does NOT duplicate or reimplement the
|
|
22
|
+
* tokenizer/parser. If the parser accepts an expression, it is structurally
|
|
23
|
+
* valid and we normalize its surface text; if the parser rejects it, we
|
|
24
|
+
* fail open (see {@link normalizeCond}) so a broken guard never silently
|
|
25
|
+
* drops a phase — matching the project's fail-open-for-guards invariant.
|
|
26
|
+
*
|
|
27
|
+
* The `canonical` form is a surface normalization (whitespace removal +
|
|
28
|
+
* redundant enclosing-paren stripping with string-literal contents
|
|
29
|
+
* preserved), not a full AST rewrite: for valid expressions whitespace only
|
|
30
|
+
* separates tokens, so removing it (after protecting quoted literals) yields
|
|
31
|
+
* a stable concatenation that is invariant under the cosmetic differences
|
|
32
|
+
* hashing cares about. Reference extraction runs on the **literal-protected**
|
|
33
|
+
* surface so `{steps.*}` text inside string literals is never treated as a
|
|
34
|
+
* real dependency.
|
|
35
|
+
*
|
|
36
|
+
* Pure module: no IO, no `Date`, no randomness, never throws.
|
|
37
|
+
*
|
|
38
|
+
* @see docs/rfc-0.2.0-architecture.md §5.4 (`flowir/cond.ts` — 条件归一化)
|
|
39
|
+
* @see ../interpolate.ts for the shared condition parser (tokenize / CondParser / compare / tryEvaluateCondition)
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* A condition expression lifted into a stable, hashable descriptor.
|
|
43
|
+
*
|
|
44
|
+
* - `source` — the original expression text, verbatim (never normalized).
|
|
45
|
+
* - `canonical` — a stable normalized surface form: whitespace-collapsed,
|
|
46
|
+
* operator-spacing-invariant, redundant enclosing parens stripped, with
|
|
47
|
+
* quoted-string contents preserved. Structurally-equivalent expressions
|
|
48
|
+
* produce byte-identical `canonical` strings.
|
|
49
|
+
* - `refs` — the `steps.<id>…` / `args.<name>…` / `env.<VAR>…` references
|
|
50
|
+
* the expression reads (in first-occurrence order, de-duplicated). Empty
|
|
51
|
+
* when the expression failed to parse (fail-open). Placeholders that only
|
|
52
|
+
* appear inside string literals are **not** counted as refs.
|
|
53
|
+
*/
|
|
54
|
+
export interface NormalizedCond {
|
|
55
|
+
source: string;
|
|
56
|
+
canonical: string;
|
|
57
|
+
refs: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Normalize a `when` / `until` / `eval` condition expression.
|
|
61
|
+
*
|
|
62
|
+
* The expression is parsed (validated) by reusing the shared condition
|
|
63
|
+
* parser in {@link ../interpolate.ts} via {@link tryEvaluateCondition}; on a
|
|
64
|
+
* parse error this **fails open** — returning `{ source, canonical:
|
|
65
|
+
* source.trim(), refs: [] }` — so a malformed guard is never silently
|
|
66
|
+
* dropped and never crashes a compile/replay pass. This mirrors the
|
|
67
|
+
* project's fail-open-for-guards invariant (`when` parse errors → phase
|
|
68
|
+
* still runs). Never throws.
|
|
69
|
+
*
|
|
70
|
+
* On success, `canonical` is the whitespace/paren-normalized surface form
|
|
71
|
+
* (stable for hashing) and `refs` are the `steps.*` / `args.*` / `env.*`
|
|
72
|
+
* references the expression reads (for replay rebinding). Placeholders that
|
|
73
|
+
* only appear inside string literals are excluded from `refs`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function normalizeCond(expr: string): NormalizedCond;
|
|
76
|
+
//# sourceMappingURL=cond.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cond.d.ts","sourceRoot":"","sources":["../../src/flowir/cond.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAQH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;CACf;AA6ID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAsB1D"}
|