@acpus/core 0.6.0 → 0.7.0-alpha.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/README.md +128 -4
- package/dist/.tsbuildinfo +1 -0
- package/dist/graph/builder.d.ts +103 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +209 -0
- package/dist/graph/builder.js.map +1 -0
- package/dist/graph/lowering.d.ts +8 -0
- package/dist/graph/lowering.d.ts.map +1 -0
- package/dist/graph/lowering.js +45 -0
- package/dist/graph/lowering.js.map +1 -0
- package/dist/graph/refs.d.ts +11 -0
- package/dist/graph/refs.d.ts.map +1 -0
- package/dist/graph/refs.js +12 -0
- package/dist/graph/refs.js.map +1 -0
- package/dist/graph/scope.d.ts +21 -0
- package/dist/graph/scope.d.ts.map +1 -0
- package/dist/graph/scope.js +22 -0
- package/dist/graph/scope.js.map +1 -0
- package/dist/index.d.ts +11 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -15
- package/dist/index.js.map +1 -1
- package/dist/internal/symbols.d.ts +9 -0
- package/dist/internal/symbols.d.ts.map +1 -0
- package/dist/internal/symbols.js +9 -0
- package/dist/internal/symbols.js.map +1 -0
- package/dist/internal/type-utils.d.ts +7 -0
- package/dist/internal/type-utils.d.ts.map +1 -0
- package/dist/internal/type-utils.js +8 -0
- package/dist/internal/type-utils.js.map +1 -0
- package/dist/ir/types.d.ts +218 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js.map +1 -0
- package/dist/ir/validator.d.ts +3 -0
- package/dist/ir/validator.d.ts.map +1 -0
- package/dist/ir/validator.js +671 -0
- package/dist/ir/validator.js.map +1 -0
- package/dist/ir.d.ts +3 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +2 -0
- package/dist/ir.js.map +1 -0
- package/dist/nodes/composite/fanout.d.ts +24 -0
- package/dist/nodes/composite/fanout.d.ts.map +1 -0
- package/dist/nodes/composite/fanout.js +24 -0
- package/dist/nodes/composite/fanout.js.map +1 -0
- package/dist/nodes/composite/if.d.ts +12 -0
- package/dist/nodes/composite/if.d.ts.map +1 -0
- package/dist/nodes/composite/if.js +13 -0
- package/dist/nodes/composite/if.js.map +1 -0
- package/dist/nodes/composite/loop.d.ts +17 -0
- package/dist/nodes/composite/loop.d.ts.map +1 -0
- package/dist/nodes/composite/loop.js +22 -0
- package/dist/nodes/composite/loop.js.map +1 -0
- package/dist/nodes/composite/parallel.d.ts +29 -0
- package/dist/nodes/composite/parallel.d.ts.map +1 -0
- package/dist/nodes/composite/parallel.js +18 -0
- package/dist/nodes/composite/parallel.js.map +1 -0
- package/dist/nodes/composite/shared.d.ts +38 -0
- package/dist/nodes/composite/shared.d.ts.map +1 -0
- package/dist/nodes/composite/shared.js +2 -0
- package/dist/nodes/composite/shared.js.map +1 -0
- package/dist/nodes/composite/switch.d.ts +22 -0
- package/dist/nodes/composite/switch.d.ts.map +1 -0
- package/dist/nodes/composite/switch.js +12 -0
- package/dist/nodes/composite/switch.js.map +1 -0
- package/dist/nodes/control/assert.d.ts +10 -0
- package/dist/nodes/control/assert.d.ts.map +1 -0
- package/dist/nodes/control/assert.js +14 -0
- package/dist/nodes/control/assert.js.map +1 -0
- package/dist/nodes/leaf/agent.d.ts +63 -0
- package/dist/nodes/leaf/agent.d.ts.map +1 -0
- package/dist/nodes/leaf/agent.js +56 -0
- package/dist/nodes/leaf/agent.js.map +1 -0
- package/dist/nodes/leaf/shared.d.ts +11 -0
- package/dist/nodes/leaf/shared.d.ts.map +1 -0
- package/dist/nodes/leaf/shared.js +2 -0
- package/dist/nodes/leaf/shared.js.map +1 -0
- package/dist/nodes/leaf/signal.d.ts +27 -0
- package/dist/nodes/leaf/signal.d.ts.map +1 -0
- package/dist/nodes/leaf/signal.js +18 -0
- package/dist/nodes/leaf/signal.js.map +1 -0
- package/dist/nodes/leaf/task.d.ts +71 -0
- package/dist/nodes/leaf/task.d.ts.map +1 -0
- package/dist/nodes/leaf/task.js +88 -0
- package/dist/nodes/leaf/task.js.map +1 -0
- package/dist/runtime/dollar.d.ts +42 -0
- package/dist/runtime/dollar.d.ts.map +1 -0
- package/dist/runtime/dollar.js +82 -0
- package/dist/runtime/dollar.js.map +1 -0
- package/dist/runtime/secret.d.ts +10 -0
- package/dist/runtime/secret.d.ts.map +1 -0
- package/dist/runtime/secret.js +12 -0
- package/dist/runtime/secret.js.map +1 -0
- package/dist/runtime/task-context.d.ts +28 -0
- package/dist/runtime/task-context.d.ts.map +1 -0
- package/dist/runtime/task-context.js +2 -0
- package/dist/runtime/task-context.js.map +1 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +3 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema/index.d.ts +6 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +3 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/lower.d.ts +25 -0
- package/dist/schema/lower.d.ts.map +1 -0
- package/dist/schema/lower.js +198 -0
- package/dist/schema/lower.js.map +1 -0
- package/dist/schema/validate.d.ts +18 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +22 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/schema/zod.d.ts +17 -0
- package/dist/schema/zod.d.ts.map +1 -0
- package/dist/schema/zod.js +15 -0
- package/dist/schema/zod.js.map +1 -0
- package/dist/schema.d.ts +3 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/template/template.d.ts +5 -0
- package/dist/template/template.d.ts.map +1 -0
- package/dist/template/template.js +10 -0
- package/dist/template/template.js.map +1 -0
- package/dist/workflow.d.ts +3 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +2 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +35 -20
- package/dist/agent-overrides.d.ts +0 -33
- package/dist/agent-overrides.d.ts.map +0 -1
- package/dist/agent-overrides.js +0 -178
- package/dist/agent-overrides.js.map +0 -1
- package/dist/cel-ast.d.ts +0 -39
- package/dist/cel-ast.d.ts.map +0 -1
- package/dist/cel-ast.js +0 -157
- package/dist/cel-ast.js.map +0 -1
- package/dist/cel-environment.d.ts +0 -6
- package/dist/cel-environment.d.ts.map +0 -1
- package/dist/cel-environment.js +0 -44
- package/dist/cel-environment.js.map +0 -1
- package/dist/compiler.d.ts +0 -4
- package/dist/compiler.d.ts.map +0 -1
- package/dist/compiler.js +0 -702
- package/dist/compiler.js.map +0 -1
- package/dist/composite-contract.d.ts +0 -46
- package/dist/composite-contract.d.ts.map +0 -1
- package/dist/composite-contract.js +0 -97
- package/dist/composite-contract.js.map +0 -1
- package/dist/diagnostics.d.ts +0 -9
- package/dist/diagnostics.d.ts.map +0 -1
- package/dist/diagnostics.js +0 -16
- package/dist/diagnostics.js.map +0 -1
- package/dist/duration.d.ts +0 -13
- package/dist/duration.d.ts.map +0 -1
- package/dist/duration.js +0 -32
- package/dist/duration.js.map +0 -1
- package/dist/expression-scope.d.ts +0 -23
- package/dist/expression-scope.d.ts.map +0 -1
- package/dist/expression-scope.js +0 -545
- package/dist/expression-scope.js.map +0 -1
- package/dist/expressions-shared.d.ts +0 -19
- package/dist/expressions-shared.d.ts.map +0 -1
- package/dist/expressions-shared.js +0 -27
- package/dist/expressions-shared.js.map +0 -1
- package/dist/expressions.d.ts +0 -9
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js +0 -95
- package/dist/expressions.js.map +0 -1
- package/dist/hash.d.ts +0 -19
- package/dist/hash.d.ts.map +0 -1
- package/dist/hash.js +0 -120
- package/dist/hash.js.map +0 -1
- package/dist/hook-validation.d.ts +0 -9
- package/dist/hook-validation.d.ts.map +0 -1
- package/dist/hook-validation.js +0 -73
- package/dist/hook-validation.js.map +0 -1
- package/dist/hooks.d.ts +0 -150
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -18
- package/dist/hooks.js.map +0 -1
- package/dist/schedule.d.ts +0 -3
- package/dist/schedule.d.ts.map +0 -1
- package/dist/schedule.js +0 -21
- package/dist/schedule.js.map +0 -1
- package/dist/schema/dsl.d.ts +0 -34
- package/dist/schema/dsl.d.ts.map +0 -1
- package/dist/schema/dsl.js +0 -231
- package/dist/schema/dsl.js.map +0 -1
- package/dist/schema/helpers.d.ts +0 -24
- package/dist/schema/helpers.d.ts.map +0 -1
- package/dist/schema/helpers.js +0 -61
- package/dist/schema/helpers.js.map +0 -1
- package/dist/schema-validator.d.ts +0 -29
- package/dist/schema-validator.d.ts.map +0 -1
- package/dist/schema-validator.js +0 -666
- package/dist/schema-validator.js.map +0 -1
- package/dist/source-resolver.d.ts +0 -22
- package/dist/source-resolver.d.ts.map +0 -1
- package/dist/source-resolver.js +0 -43
- package/dist/source-resolver.js.map +0 -1
- package/dist/types.d.ts +0 -126
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/workflow-schema.d.ts +0 -10
- package/dist/workflow-schema.d.ts.map +0 -1
- package/dist/workflow-schema.js +0 -523
- package/dist/workflow-schema.js.map +0 -1
- /package/dist/{types.js → ir/types.js} +0 -0
package/dist/compiler.js
DELETED
|
@@ -1,702 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { Ajv } from "ajv";
|
|
3
|
-
import { parse as parseYaml } from "yaml";
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
5
|
-
import { DiagnosticBag } from "./diagnostics.js";
|
|
6
|
-
import { outputMergeFor, keyTemplateForKind } from "./composite-contract.js";
|
|
7
|
-
import { createExpressionCollector } from "./expressions.js";
|
|
8
|
-
import { validateScopedExpressions } from "./expression-scope.js";
|
|
9
|
-
import { createSchedule } from "./schedule.js";
|
|
10
|
-
import { compileSchemaDsl } from "./schema/index.js";
|
|
11
|
-
import { isRecord } from "./schema/helpers.js";
|
|
12
|
-
import { parseDurationMs } from "./duration.js";
|
|
13
|
-
import { validateWithSchema } from "./schema-validator.js";
|
|
14
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
15
|
-
const GENERATED_ID_PREFIX = "$";
|
|
16
|
-
const SAFE_AUTHOR_ID = /^[A-Za-z_][A-Za-z0-9_-]*$/u;
|
|
17
|
-
export function compileWorkflow(source, options = {}) {
|
|
18
|
-
const diagnostics = new DiagnosticBag();
|
|
19
|
-
const sourcePath = normalizeSourcePath(options.sourcePath);
|
|
20
|
-
const parsed = parseSource(source, diagnostics);
|
|
21
|
-
if (!isWorkflowSpec(parsed)) {
|
|
22
|
-
diagnostics.error("SPEC_SHAPE", "Workflow spec must be a YAML object with version, name, and workflow.steps.", "$");
|
|
23
|
-
return result(false, diagnostics);
|
|
24
|
-
}
|
|
25
|
-
const compileOptions = { ...options, sourcePath };
|
|
26
|
-
const expanded = expandIncludes(parsed, compileOptions, diagnostics, new Set());
|
|
27
|
-
validateWithSchema(expanded, diagnostics);
|
|
28
|
-
const stepIds = collectStepIds(expanded.workflow.steps, diagnostics);
|
|
29
|
-
const context = {
|
|
30
|
-
diagnostics,
|
|
31
|
-
stepIds,
|
|
32
|
-
agents: isRecord(expanded.agents) ? expanded.agents : {},
|
|
33
|
-
sourcePath
|
|
34
|
-
};
|
|
35
|
-
validateSpec(expanded, context);
|
|
36
|
-
const rootChildren = compileSteps(expanded.workflow.steps, ["workflow"], "$.workflow.steps", context);
|
|
37
|
-
const root = {
|
|
38
|
-
id: "workflow",
|
|
39
|
-
kind: "pipeline",
|
|
40
|
-
nodePath: ["workflow"],
|
|
41
|
-
keyTemplate: keyTemplate(["workflow"]),
|
|
42
|
-
children: rootChildren,
|
|
43
|
-
metadata: {
|
|
44
|
-
implicit: true
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const expressionCollector = createExpressionCollector(diagnostics, stepIds);
|
|
48
|
-
expressionCollector.visit(expanded.input ?? {}, "$.input");
|
|
49
|
-
expressionCollector.visit(expanded.agents ?? {}, "$.agents");
|
|
50
|
-
expressionCollector.visit(expanded.workflow.steps, "$.workflow.steps");
|
|
51
|
-
expressionCollector.visit(expanded.outputs ?? {}, "$.outputs");
|
|
52
|
-
// Scope-aware validation over the compiled IR: field paths against declared
|
|
53
|
-
// schemas, local-root scope, step visibility, and shell-safety in cmd. This
|
|
54
|
-
// consumes the schemas already compiled onto node metadata. Fail-quiet.
|
|
55
|
-
validateScopedExpressions({
|
|
56
|
-
root,
|
|
57
|
-
inputSchema: isRecord(expanded.input) ? expanded.input : {},
|
|
58
|
-
outputs: isRecord(expanded.outputs) ? expanded.outputs : {},
|
|
59
|
-
agents: isRecord(expanded.agents) ? expanded.agents : {},
|
|
60
|
-
allStepIds: stepIds,
|
|
61
|
-
diagnostics
|
|
62
|
-
});
|
|
63
|
-
const ok = !diagnostics.hasErrors(options.strict);
|
|
64
|
-
if (!ok) {
|
|
65
|
-
return result(false, diagnostics);
|
|
66
|
-
}
|
|
67
|
-
const ir = {
|
|
68
|
-
irVersion: 1,
|
|
69
|
-
astVersion: 1,
|
|
70
|
-
source: {
|
|
71
|
-
path: sourcePath,
|
|
72
|
-
digest: digest(source)
|
|
73
|
-
},
|
|
74
|
-
name: expanded.name,
|
|
75
|
-
description: expanded.description,
|
|
76
|
-
input: expanded.input ?? {},
|
|
77
|
-
agents: expanded.agents ?? {},
|
|
78
|
-
root,
|
|
79
|
-
outputs: expanded.outputs ?? {},
|
|
80
|
-
expressions: expressionCollector.expressions
|
|
81
|
-
};
|
|
82
|
-
return {
|
|
83
|
-
ok: true,
|
|
84
|
-
diagnostics: diagnostics.diagnostics,
|
|
85
|
-
ir,
|
|
86
|
-
schedule: createSchedule(ir)
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function normalizeSourcePath(sourcePath) {
|
|
90
|
-
if (!sourcePath)
|
|
91
|
-
return undefined;
|
|
92
|
-
try {
|
|
93
|
-
return realpathSync.native(sourcePath);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return sourcePath;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
export function lintWorkflow(source, options = {}) {
|
|
100
|
-
const compiled = compileWorkflow(source, options);
|
|
101
|
-
return {
|
|
102
|
-
ok: compiled.ok,
|
|
103
|
-
diagnostics: compiled.diagnostics
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function parseSource(source, diagnostics) {
|
|
107
|
-
try {
|
|
108
|
-
return parseYaml(source);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
diagnostics.error("YAML_PARSE", `Invalid YAML: ${errorMessage(error)}`, "$");
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function expandIncludes(spec, options, diagnostics, seen) {
|
|
116
|
-
const steps = [];
|
|
117
|
-
for (const [index, step] of spec.workflow.steps.entries()) {
|
|
118
|
-
if (typeof step.include === "string") {
|
|
119
|
-
if (!options.includeResolver) {
|
|
120
|
-
diagnostics.error("INCLUDE_RESOLVER", "Include is present but no include resolver was provided.", `$.workflow.steps[${index}].include`);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (seen.has(step.include)) {
|
|
124
|
-
diagnostics.error("INCLUDE_CYCLE", `Include cycle detected for '${step.include}'.`, `$.workflow.steps[${index}].include`);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
let includedSource;
|
|
128
|
-
try {
|
|
129
|
-
includedSource = options.includeResolver(step.include, options.sourcePath);
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
diagnostics.error("INCLUDE_RESOLUTION", errorMessage(error), `$.workflow.steps[${index}].include`);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const includedParsed = parseSource(includedSource, diagnostics);
|
|
136
|
-
if (!isWorkflowSpec(includedParsed)) {
|
|
137
|
-
diagnostics.error("INCLUDE_SHAPE", `Included spec '${step.include}' is not a valid workflow spec.`, `$.workflow.steps[${index}].include`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const nextSeen = new Set(seen);
|
|
141
|
-
nextSeen.add(step.include);
|
|
142
|
-
const expanded = expandIncludes(includedParsed, options, diagnostics, nextSeen);
|
|
143
|
-
steps.push(...expanded.workflow.steps);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
steps.push(step);
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
...spec,
|
|
150
|
-
workflow: {
|
|
151
|
-
...spec.workflow,
|
|
152
|
-
steps
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function validateSpec(spec, context) {
|
|
157
|
-
const { diagnostics } = context;
|
|
158
|
-
// Compile input flat-map to JSON Schema
|
|
159
|
-
if (isRecord(spec.input)) {
|
|
160
|
-
const { schema, errors } = compileSchemaDsl(spec.input, { strictObjectKeys: false });
|
|
161
|
-
for (const err of errors) {
|
|
162
|
-
diagnostics.error("INPUT_SHAPE", err.message, `$.input.${err.field}`);
|
|
163
|
-
}
|
|
164
|
-
if (errors.length === 0) {
|
|
165
|
-
validateJsonSchema(schema, "$.input", context);
|
|
166
|
-
}
|
|
167
|
-
// Replace input with compiled schema in the spec so the IR stores the JSON Schema
|
|
168
|
-
spec.input = schema;
|
|
169
|
-
}
|
|
170
|
-
// Semantic checks not covered by JSON Schema
|
|
171
|
-
for (const [agentName, agent] of Object.entries(spec.agents ?? {})) {
|
|
172
|
-
if (!isRecord(agent))
|
|
173
|
-
continue; // Schema handles shape errors
|
|
174
|
-
// AGENT_REF: check that referenced agents actually exist (cross-reference)
|
|
175
|
-
// This is done in validateAgentStep at step level
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function compileSteps(steps, parentPath, path, context) {
|
|
179
|
-
return steps.map((step, index) => compileStep(step, parentPath, `${path}[${index}]`, context));
|
|
180
|
-
}
|
|
181
|
-
function compileStep(step, parentPath, path, context) {
|
|
182
|
-
const id = typeof step.id === "string" && step.id.length > 0 ? step.id : `missing_${parentPath.length}_${path}`;
|
|
183
|
-
// Defensive: Schema already validates id; keep as fallback
|
|
184
|
-
if (typeof step.id !== "string" || step.id.length === 0) {
|
|
185
|
-
context.diagnostics.error("STEP_ID", "Every workflow step must define a non-empty string id.", `${path}.id`);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
validateAuthorId(step.id, "Step", `${path}.id`, context);
|
|
189
|
-
}
|
|
190
|
-
const nodePath = [...parentPath, id];
|
|
191
|
-
const base = {
|
|
192
|
-
id,
|
|
193
|
-
nodePath,
|
|
194
|
-
keyTemplate: keyTemplate(nodePath)
|
|
195
|
-
};
|
|
196
|
-
if (step.session_key !== undefined && step.run !== "agent") {
|
|
197
|
-
context.diagnostics.error("STEP_SHAPE", "session_key is supported only on run: agent steps.", `${path}.session_key`);
|
|
198
|
-
}
|
|
199
|
-
if (step.run === "agent") {
|
|
200
|
-
validateAgentStep(step, path, context);
|
|
201
|
-
validateStepTimeout(step, path, context);
|
|
202
|
-
const metadata = pickMetadata(step, ["run", "use", "prompt", "cwd", "session_key", "output", "retry", "timeout", "on_error", "policy"]);
|
|
203
|
-
// Snapshot the referenced agent definition into the node so the runtime can
|
|
204
|
-
// route to the right executor and build the acpx invocation. `type` defaults
|
|
205
|
-
// to "builtin".
|
|
206
|
-
const agentName = typeof step.use === "string" ? step.use : undefined;
|
|
207
|
-
const agentSpec = agentName ? context.agents[agentName] : undefined;
|
|
208
|
-
if (isRecord(agentSpec)) {
|
|
209
|
-
metadata.agent = { ...agentSpec, type: agentSpec.type ?? "builtin" };
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
...base,
|
|
213
|
-
kind: "run.agent",
|
|
214
|
-
metadata
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
if (step.run === "program") {
|
|
218
|
-
validateProgramStep(step, path, context);
|
|
219
|
-
validateStepTimeout(step, path, context);
|
|
220
|
-
const metadata = pickMetadata(step, ["run", "cmd", "env", "cwd", "capture", "expect", "output", "retry", "timeout", "on_error"]);
|
|
221
|
-
// Normalize the default `expect.exit_code` so omitting it and explicitly
|
|
222
|
-
// declaring `[0]` produce the same IR (and therefore the same Node
|
|
223
|
-
// Definition Hash). Without this, authors who add `expect: { exit_code: [0] }`
|
|
224
|
-
// for clarity would silently invalidate Forked Run inheritance.
|
|
225
|
-
const expect = metadata.expect;
|
|
226
|
-
if (isRecord(expect) && Array.isArray(expect.exit_code) && expect.exit_code.length === 1 && expect.exit_code[0] === 0) {
|
|
227
|
-
delete expect.exit_code;
|
|
228
|
-
if (Object.keys(expect).length === 0)
|
|
229
|
-
delete metadata.expect;
|
|
230
|
-
}
|
|
231
|
-
return {
|
|
232
|
-
...base,
|
|
233
|
-
kind: "run.program",
|
|
234
|
-
metadata
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
if (Array.isArray(step.pipeline)) {
|
|
238
|
-
return {
|
|
239
|
-
...base,
|
|
240
|
-
kind: "pipeline",
|
|
241
|
-
children: compileSteps(asSteps(step.pipeline, `${path}.pipeline`, context), nodePath, `${path}.pipeline`, context),
|
|
242
|
-
metadata: pickMetadata(step, ["outputs"])
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (Array.isArray(step.parallel)) {
|
|
246
|
-
return {
|
|
247
|
-
...base,
|
|
248
|
-
kind: "parallel",
|
|
249
|
-
keyTemplate: keyTemplateForKind("parallel", keyTemplate(nodePath)),
|
|
250
|
-
outputMerge: outputMergeFor("parallel"),
|
|
251
|
-
branches: compileParallelBranches(step.parallel, nodePath, `${path}.parallel`, context),
|
|
252
|
-
metadata: pickMetadata(step, ["max_concurrency", "join"])
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
if (isRecord(step.fanout)) {
|
|
256
|
-
const fanout = step.fanout;
|
|
257
|
-
validateFanout(fanout, path, context);
|
|
258
|
-
const pipelineId = generatedDoId("do");
|
|
259
|
-
const pipelinePath = [...nodePath, pipelineId];
|
|
260
|
-
const children = Array.isArray(fanout.do) ? compileSteps(asSteps(fanout.do, `${path}.fanout.do`, context), pipelinePath, `${path}.fanout.do`, context) : [];
|
|
261
|
-
if (!Array.isArray(fanout.do)) {
|
|
262
|
-
context.diagnostics.error("FANOUT_DO", "fanout.do must be an array of steps.", `${path}.fanout.do`);
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
...base,
|
|
266
|
-
kind: "fanout",
|
|
267
|
-
keyTemplate: keyTemplateForKind("fanout", keyTemplate(nodePath)),
|
|
268
|
-
outputMerge: outputMergeFor("fanout"),
|
|
269
|
-
children: [generatedPipeline(pipelineId, children, pipelinePath, `${path}.fanout.do`)],
|
|
270
|
-
metadata: pickMetadata(fanout, ["over", "key", "max_concurrency", "join", "quorum", "success_criteria"])
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (isRecord(step.if)) {
|
|
274
|
-
const ifSpec = step.if;
|
|
275
|
-
let condition;
|
|
276
|
-
if (ifSpec.condition !== undefined) {
|
|
277
|
-
if (typeof ifSpec.condition === "boolean") {
|
|
278
|
-
condition = String(ifSpec.condition);
|
|
279
|
-
}
|
|
280
|
-
else if (typeof ifSpec.condition === "string") {
|
|
281
|
-
condition = ifSpec.condition;
|
|
282
|
-
}
|
|
283
|
-
else if (typeof ifSpec.condition !== "string") {
|
|
284
|
-
const typeDesc = ifSpec.condition === null ? "null" : typeof ifSpec.condition;
|
|
285
|
-
context.diagnostics.error("IF_CONDITION_TYPE", `if.condition must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.if.condition`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const branches = [];
|
|
289
|
-
const thenPipelineId = generatedDoId("then");
|
|
290
|
-
const thenPipelinePath = [...nodePath, thenPipelineId];
|
|
291
|
-
branches.push({
|
|
292
|
-
id: "then",
|
|
293
|
-
when: condition,
|
|
294
|
-
whenPath: `${path}.if.condition`,
|
|
295
|
-
child: generatedPipeline(thenPipelineId, Array.isArray(ifSpec.then) ? compileSteps(asSteps(ifSpec.then, `${path}.if.then`, context), thenPipelinePath, `${path}.if.then`, context) : [], thenPipelinePath, `${path}.if.then`)
|
|
296
|
-
});
|
|
297
|
-
if (Array.isArray(ifSpec.else)) {
|
|
298
|
-
const elsePipelineId = generatedDoId("else");
|
|
299
|
-
const elsePipelinePath = [...nodePath, elsePipelineId];
|
|
300
|
-
branches.push({
|
|
301
|
-
id: "else",
|
|
302
|
-
child: generatedPipeline(elsePipelineId, compileSteps(asSteps(ifSpec.else, `${path}.if.else`, context), elsePipelinePath, `${path}.if.else`, context), elsePipelinePath, `${path}.if.else`)
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
return {
|
|
306
|
-
...base,
|
|
307
|
-
kind: "if",
|
|
308
|
-
outputMerge: outputMergeFor("if"),
|
|
309
|
-
branches,
|
|
310
|
-
metadata: {}
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
if (isRecord(step.switch)) {
|
|
314
|
-
const switchSpec = step.switch;
|
|
315
|
-
const branches = [];
|
|
316
|
-
if (Array.isArray(switchSpec.cases)) {
|
|
317
|
-
switchSpec.cases.forEach((caseSpec, caseIndex) => {
|
|
318
|
-
if (!isRecord(caseSpec)) {
|
|
319
|
-
context.diagnostics.error("SWITCH_CASE", "switch.cases entries must be objects.", `${path}.switch.cases[${caseIndex}]`);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
// Coerce `when`: boolean → string, string → pass through, else error.
|
|
323
|
-
let when;
|
|
324
|
-
if (caseSpec.when !== undefined) {
|
|
325
|
-
if (typeof caseSpec.when === "boolean") {
|
|
326
|
-
when = String(caseSpec.when);
|
|
327
|
-
}
|
|
328
|
-
else if (typeof caseSpec.when === "string") {
|
|
329
|
-
when = caseSpec.when;
|
|
330
|
-
}
|
|
331
|
-
else if (typeof caseSpec.when !== "string") {
|
|
332
|
-
const typeDesc = caseSpec.when === null ? "null" : typeof caseSpec.when;
|
|
333
|
-
context.diagnostics.error("SWITCH_WHEN_TYPE", `switch.case.when must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.switch.cases[${caseIndex}].when`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const pipelineId = generatedDoId(`case_${caseIndex + 1}`);
|
|
337
|
-
const pipelinePath = [...nodePath, pipelineId];
|
|
338
|
-
branches.push({
|
|
339
|
-
id: `case_${caseIndex + 1}`,
|
|
340
|
-
when,
|
|
341
|
-
whenPath: `${path}.switch.cases[${caseIndex}].when`,
|
|
342
|
-
child: generatedPipeline(pipelineId, Array.isArray(caseSpec.do) ? compileSteps(asSteps(caseSpec.do, `${path}.switch.cases[${caseIndex}].do`, context), pipelinePath, `${path}.switch.cases[${caseIndex}].do`, context) : [], pipelinePath, `${path}.switch.cases[${caseIndex}].do`)
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
context.diagnostics.error("SWITCH_CASES", "switch.cases must be an array.", `${path}.switch.cases`);
|
|
348
|
-
}
|
|
349
|
-
if (isRecord(switchSpec.default)) {
|
|
350
|
-
const pipelineId = generatedDoId("default");
|
|
351
|
-
const pipelinePath = [...nodePath, pipelineId];
|
|
352
|
-
branches.push({
|
|
353
|
-
id: "default",
|
|
354
|
-
child: generatedPipeline(pipelineId, Array.isArray(switchSpec.default.do) ? compileSteps(asSteps(switchSpec.default.do, `${path}.switch.default.do`, context), pipelinePath, `${path}.switch.default.do`, context) : [], pipelinePath, `${path}.switch.default.do`)
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
return {
|
|
358
|
-
...base,
|
|
359
|
-
kind: "switch",
|
|
360
|
-
outputMerge: outputMergeFor("switch"),
|
|
361
|
-
branches,
|
|
362
|
-
metadata: {}
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
if (isRecord(step.loop)) {
|
|
366
|
-
const loop = step.loop;
|
|
367
|
-
// Coerce `until`: boolean → string, string → pass through, else error.
|
|
368
|
-
if (loop.until !== undefined) {
|
|
369
|
-
if (typeof loop.until === "boolean") {
|
|
370
|
-
loop.until = String(loop.until);
|
|
371
|
-
}
|
|
372
|
-
else if (typeof loop.until !== "string") {
|
|
373
|
-
const typeDesc = loop.until === null ? "null" : typeof loop.until;
|
|
374
|
-
context.diagnostics.error("LOOP_UNTIL_TYPE", `loop.until must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.loop.until`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const pipelineId = generatedDoId("do");
|
|
378
|
-
const pipelinePath = [...nodePath, pipelineId];
|
|
379
|
-
return {
|
|
380
|
-
...base,
|
|
381
|
-
kind: "loop",
|
|
382
|
-
keyTemplate: keyTemplateForKind("loop", keyTemplate(nodePath)),
|
|
383
|
-
outputMerge: outputMergeFor("loop"),
|
|
384
|
-
children: [
|
|
385
|
-
generatedPipeline(pipelineId, Array.isArray(loop.do) ? compileSteps(asSteps(loop.do, `${path}.loop.do`, context), pipelinePath, `${path}.loop.do`, context) : [], pipelinePath, `${path}.loop.do`)
|
|
386
|
-
],
|
|
387
|
-
metadata: pickMetadata(loop, ["until", "max_iterations"])
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
if (isRecord(step.guard)) {
|
|
391
|
-
const guard = step.guard;
|
|
392
|
-
if (guard.when !== undefined) {
|
|
393
|
-
if (typeof guard.when === "boolean") {
|
|
394
|
-
guard.when = String(guard.when);
|
|
395
|
-
}
|
|
396
|
-
else if (typeof guard.when !== "string") {
|
|
397
|
-
const typeDesc = guard.when === null ? "null" : typeof guard.when;
|
|
398
|
-
context.diagnostics.error("GUARD_WHEN_TYPE", `guard.when must be a boolean or CEL expression string, got ${typeDesc}.`, `${path}.guard.when`);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return {
|
|
402
|
-
...base,
|
|
403
|
-
kind: "guard",
|
|
404
|
-
metadata: pickMetadata(guard, ["when", "then", "else", "message"])
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
if (step.run === "signal") {
|
|
408
|
-
// validateSignalStep owns signal timeout validation (SIGNAL_TIMEOUT); do not
|
|
409
|
-
// also run validateStepTimeout or an invalid duration emits a duplicate
|
|
410
|
-
// STEP_TIMEOUT for the same field.
|
|
411
|
-
validateSignalStep(step, path, context);
|
|
412
|
-
return {
|
|
413
|
-
...base,
|
|
414
|
-
kind: "run.signal",
|
|
415
|
-
metadata: pickMetadata(step, ["run", "prompt", "output", "timeout", "on_timeout", "default"])
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
if (typeof step.subworkflow === "string") {
|
|
419
|
-
return {
|
|
420
|
-
...base,
|
|
421
|
-
kind: "subworkflow",
|
|
422
|
-
metadata: pickMetadata(step, ["subworkflow", "input"])
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
// Defensive: Schema already validates step kind; keep as fallback
|
|
426
|
-
context.diagnostics.error("STEP_KIND", "Step must define one of run: agent, run: program, run: signal, parallel, fanout, if, switch, loop, guard, subworkflow, or include.", path);
|
|
427
|
-
return {
|
|
428
|
-
...base,
|
|
429
|
-
kind: "run.program",
|
|
430
|
-
metadata: {}
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
function validateAgentStep(step, path, context) {
|
|
434
|
-
// AGENT_REF: cross-reference check (not possible in JSON Schema)
|
|
435
|
-
if (typeof step.use === "string" && !Object.prototype.hasOwnProperty.call(context.agents, step.use)) {
|
|
436
|
-
context.diagnostics.error("AGENT_REF", `run: agent step references unknown agent '${step.use}'.`, `${path}.use`);
|
|
437
|
-
}
|
|
438
|
-
if (step.retry !== undefined) {
|
|
439
|
-
validateRetry(step.retry, path, context);
|
|
440
|
-
}
|
|
441
|
-
compileOutputSchema(step, path, context, false);
|
|
442
|
-
}
|
|
443
|
-
function validateProgramStep(step, path, context) {
|
|
444
|
-
const outputSchema = compileOutputSchema(step, path, context, false);
|
|
445
|
-
// Enforce: output schema requires capture.parse: json
|
|
446
|
-
if (outputSchema) {
|
|
447
|
-
const capture = step.capture;
|
|
448
|
-
if (!capture || capture.parse !== "json") {
|
|
449
|
-
context.diagnostics.error("OUTPUT_REQUIRES_JSON", "run: program output schema requires capture.parse: json.", `${path}.output`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
function validateFanout(fanout, path, context) {
|
|
454
|
-
// Reject arrays containing non-primitive values (objects, nested arrays)
|
|
455
|
-
// since JSON.stringify of objects produces invalid CEL syntax.
|
|
456
|
-
// Schema validates that over is string|array; this is a runtime coercion check.
|
|
457
|
-
if (Array.isArray(fanout.over)) {
|
|
458
|
-
for (let i = 0; i < fanout.over.length; i++) {
|
|
459
|
-
const el = fanout.over[i];
|
|
460
|
-
if (el !== null && el !== undefined && typeof el === "object") {
|
|
461
|
-
context.diagnostics.error("FANOUT_OVER_TYPE", `fanout.over array elements must be primitives (string, number, boolean, null), got ${typeof el} at index ${i}.`, `${path}.fanout.over[${i}]`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Coerce array → JSON string so the IR always stores strings
|
|
465
|
-
fanout.over = JSON.stringify(fanout.over);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
function validateSignalStep(step, path, context) {
|
|
469
|
-
// Cross-field: a declared timeout requires an on_timeout policy.
|
|
470
|
-
if (step.timeout !== undefined && step.on_timeout === undefined) {
|
|
471
|
-
context.diagnostics.error("SIGNAL_ON_TIMEOUT", "signal.on_timeout must be fail or default, and is required when timeout is set.", `${path}.on_timeout`);
|
|
472
|
-
}
|
|
473
|
-
// Cross-field: on_timeout: default requires a literal default payload.
|
|
474
|
-
if (step.on_timeout === "default" && !isRecord(step.default)) {
|
|
475
|
-
context.diagnostics.error("SIGNAL_DEFAULT", "signal.default is required (as an object) when on_timeout is default.", `${path}.default`);
|
|
476
|
-
}
|
|
477
|
-
// Duration format validation (not expressible in JSON Schema)
|
|
478
|
-
if (typeof step.timeout === "string") {
|
|
479
|
-
try {
|
|
480
|
-
parseDurationMs(step.timeout, { strict: true });
|
|
481
|
-
}
|
|
482
|
-
catch {
|
|
483
|
-
context.diagnostics.error("SIGNAL_TIMEOUT", "signal.timeout must be a valid duration string (e.g. 5m, 2h, 30s).", `${path}.timeout`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
const outputSchema = compileOutputSchema(step, path, context, true);
|
|
487
|
-
// When `on_timeout: default`, validate the literal `default` payload at compile
|
|
488
|
-
// time so an invalid default is a lint error, not a runtime surprise.
|
|
489
|
-
if (step.on_timeout === "default" && isRecord(step.default) && outputSchema) {
|
|
490
|
-
let validate;
|
|
491
|
-
try {
|
|
492
|
-
validate = ajv.compile(outputSchema);
|
|
493
|
-
}
|
|
494
|
-
catch {
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
if (!validate(step.default)) {
|
|
498
|
-
context.diagnostics.error("SIGNAL_DEFAULT", `signal.default does not match the declared output schema: ${ajv.errorsText(validate.errors)}`, `${path}.default`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
function compileOutputSchema(step, path, context, strictObjectKeys) {
|
|
503
|
-
if (!isRecord(step.output) || Object.keys(step.output).length === 0) {
|
|
504
|
-
delete step.output;
|
|
505
|
-
return undefined;
|
|
506
|
-
}
|
|
507
|
-
if ("schema" in step.output) {
|
|
508
|
-
context.diagnostics.error("OUTPUT_SHAPE", "The 'schema' key in output is no longer supported as a JSON Schema escape hatch. Use the Acpus Schema DSL directly (e.g. output: { field: string }).", `${path}.output.schema`);
|
|
509
|
-
}
|
|
510
|
-
const { schema, errors } = compileSchemaDsl(step.output, { strictObjectKeys });
|
|
511
|
-
for (const err of errors) {
|
|
512
|
-
context.diagnostics.error("OUTPUT_SHAPE", err.message, `${path}.output.${err.field}`);
|
|
513
|
-
}
|
|
514
|
-
if (errors.length > 0) {
|
|
515
|
-
delete step.output;
|
|
516
|
-
return undefined;
|
|
517
|
-
}
|
|
518
|
-
validateJsonSchema(schema, `${path}.output`, context);
|
|
519
|
-
step.output = schema;
|
|
520
|
-
return schema;
|
|
521
|
-
}
|
|
522
|
-
function validateRetry(retry, path, context) {
|
|
523
|
-
// Duration format validation for backoff (not expressible in JSON Schema)
|
|
524
|
-
if (isRecord(retry) && retry.backoff !== undefined) {
|
|
525
|
-
if (typeof retry.backoff === "string") {
|
|
526
|
-
try {
|
|
527
|
-
parseDurationMs(retry.backoff, { strict: true });
|
|
528
|
-
}
|
|
529
|
-
catch {
|
|
530
|
-
context.diagnostics.error("RETRY_SHAPE", "retry.backoff must be a valid duration string.", `${path}.retry.backoff`);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
function validateStepTimeout(step, path, context) {
|
|
536
|
-
if (step.timeout === undefined)
|
|
537
|
-
return;
|
|
538
|
-
// Duration format validation for string timeouts (not expressible in JSON Schema)
|
|
539
|
-
if (typeof step.timeout === "string") {
|
|
540
|
-
try {
|
|
541
|
-
parseDurationMs(step.timeout, { strict: true });
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
context.diagnostics.error("STEP_TIMEOUT", "step.timeout must be a valid duration string or number (ms).", `${path}.timeout`);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
function validateJsonSchema(schema, path, context) {
|
|
549
|
-
if (!isRecord(schema)) {
|
|
550
|
-
context.diagnostics.error("JSON_SCHEMA_SHAPE", "Compiled schema must be a JSON Schema object.", path);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
if (!ajv.validateSchema(schema)) {
|
|
554
|
-
const message = ajv.errorsText(ajv.errors);
|
|
555
|
-
context.diagnostics.error("JSON_SCHEMA_INVALID", `Invalid JSON Schema: ${message}`, path);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
function collectStepIds(steps, diagnostics) {
|
|
559
|
-
const ids = new Set();
|
|
560
|
-
const visit = (items, path) => {
|
|
561
|
-
items.forEach((step, index) => {
|
|
562
|
-
const stepPath = `${path}[${index}]`;
|
|
563
|
-
if (typeof step.id === "string") {
|
|
564
|
-
if (ids.has(step.id)) {
|
|
565
|
-
diagnostics.error("STEP_ID_DUPLICATE", `Duplicate step id '${step.id}'.`, `${stepPath}.id`);
|
|
566
|
-
}
|
|
567
|
-
ids.add(step.id);
|
|
568
|
-
}
|
|
569
|
-
if (Array.isArray(step.pipeline)) {
|
|
570
|
-
visit(asPlainSteps(step.pipeline), `${stepPath}.pipeline`);
|
|
571
|
-
}
|
|
572
|
-
if (Array.isArray(step.parallel)) {
|
|
573
|
-
step.parallel.forEach((branch, branchIndex) => {
|
|
574
|
-
if (isRecord(branch)) {
|
|
575
|
-
if (Array.isArray(branch.do)) {
|
|
576
|
-
visit(asPlainSteps(branch.do), `${stepPath}.parallel[${branchIndex}].do`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
if (isRecord(step.fanout) && Array.isArray(step.fanout.do)) {
|
|
582
|
-
visit(asPlainSteps(step.fanout.do), `${stepPath}.fanout.do`);
|
|
583
|
-
}
|
|
584
|
-
if (isRecord(step.if) && Array.isArray(step.if.then)) {
|
|
585
|
-
visit(asPlainSteps(step.if.then), `${stepPath}.if.then`);
|
|
586
|
-
}
|
|
587
|
-
if (isRecord(step.if) && Array.isArray(step.if.else)) {
|
|
588
|
-
visit(asPlainSteps(step.if.else), `${stepPath}.if.else`);
|
|
589
|
-
}
|
|
590
|
-
if (isRecord(step.switch) && Array.isArray(step.switch.cases)) {
|
|
591
|
-
step.switch.cases.forEach((caseSpec, caseIndex) => {
|
|
592
|
-
if (isRecord(caseSpec) && Array.isArray(caseSpec.do)) {
|
|
593
|
-
visit(asPlainSteps(caseSpec.do), `${stepPath}.switch.cases[${caseIndex}].do`);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
if (isRecord(step.switch) && isRecord(step.switch.default) && Array.isArray(step.switch.default.do)) {
|
|
598
|
-
visit(asPlainSteps(step.switch.default.do), `${stepPath}.switch.default.do`);
|
|
599
|
-
}
|
|
600
|
-
if (isRecord(step.loop) && Array.isArray(step.loop.do)) {
|
|
601
|
-
visit(asPlainSteps(step.loop.do), `${stepPath}.loop.do`);
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
};
|
|
605
|
-
visit(steps, "$.workflow.steps");
|
|
606
|
-
return ids;
|
|
607
|
-
}
|
|
608
|
-
function keyTemplate(nodePath) {
|
|
609
|
-
return {
|
|
610
|
-
astVersion: 1,
|
|
611
|
-
nodePath: nodePath.join("/")
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
function generatedDoId(label) {
|
|
615
|
-
return `${GENERATED_ID_PREFIX}${label}`;
|
|
616
|
-
}
|
|
617
|
-
function generatedPipeline(id, children, nodePath, sourcePath) {
|
|
618
|
-
return {
|
|
619
|
-
id,
|
|
620
|
-
kind: "pipeline",
|
|
621
|
-
nodePath,
|
|
622
|
-
keyTemplate: keyTemplate(nodePath),
|
|
623
|
-
children,
|
|
624
|
-
metadata: {
|
|
625
|
-
generated: true,
|
|
626
|
-
sourcePath
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function compileParallelBranches(value, parentPath, path, context) {
|
|
631
|
-
const branchIds = new Set();
|
|
632
|
-
return value.map((item, index) => {
|
|
633
|
-
let id = `missing_${index}`;
|
|
634
|
-
let doChildren = [];
|
|
635
|
-
if (!isRecord(item)) {
|
|
636
|
-
context.diagnostics.error("STEP_SHAPE", "parallel entries must be branch descriptor objects.", `${path}[${index}]`);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
id = typeof item.id === "string" && item.id.length > 0 ? item.id : id;
|
|
640
|
-
validateAuthorId(id, "Branch", `${path}[${index}].id`, context);
|
|
641
|
-
const pipelineId = generatedDoId(id);
|
|
642
|
-
const pipelinePath = [...parentPath, pipelineId];
|
|
643
|
-
if (Array.isArray(item.do)) {
|
|
644
|
-
doChildren = compileSteps(asSteps(item.do, `${path}[${index}].do`, context), pipelinePath, `${path}[${index}].do`, context);
|
|
645
|
-
}
|
|
646
|
-
if (branchIds.has(id)) {
|
|
647
|
-
context.diagnostics.error("STEP_ID_DUPLICATE", `Duplicate parallel branch id '${id}'.`, `${path}[${index}].id`);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
branchIds.add(id);
|
|
651
|
-
const pipelineId = generatedDoId(id);
|
|
652
|
-
const pipelinePath = [...parentPath, pipelineId];
|
|
653
|
-
return {
|
|
654
|
-
id,
|
|
655
|
-
child: generatedPipeline(pipelineId, doChildren, pipelinePath, `${path}[${index}].do`)
|
|
656
|
-
};
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
function validateAuthorId(id, label, path, context) {
|
|
660
|
-
if (id.startsWith(GENERATED_ID_PREFIX)) {
|
|
661
|
-
context.diagnostics.error("STEP_ID_RESERVED", `${label} id '${id}' must not use reserved internal prefix '${GENERATED_ID_PREFIX}'.`, path);
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
if (!SAFE_AUTHOR_ID.test(id)) {
|
|
665
|
-
context.diagnostics.error("STEP_ID_INVALID", `${label} id '${id}' must match ${SAFE_AUTHOR_ID.source}.`, path);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
function pickMetadata(source, keys) {
|
|
669
|
-
return Object.fromEntries(keys.filter((key) => key in source).map((key) => [key, source[key]]));
|
|
670
|
-
}
|
|
671
|
-
function asSteps(value, path, context) {
|
|
672
|
-
return value.map((item, index) => {
|
|
673
|
-
if (!isRecord(item)) {
|
|
674
|
-
context.diagnostics.error("STEP_SHAPE", "Step entries must be objects.", `${path}[${index}]`);
|
|
675
|
-
return {};
|
|
676
|
-
}
|
|
677
|
-
return item;
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
function asPlainSteps(value) {
|
|
681
|
-
return value.filter(isRecord);
|
|
682
|
-
}
|
|
683
|
-
function isWorkflowSpec(value) {
|
|
684
|
-
return (isRecord(value) &&
|
|
685
|
-
typeof value.version === "number" &&
|
|
686
|
-
typeof value.name === "string" &&
|
|
687
|
-
isRecord(value.workflow) &&
|
|
688
|
-
Array.isArray(value.workflow.steps));
|
|
689
|
-
}
|
|
690
|
-
function digest(source) {
|
|
691
|
-
return `sha256:${createHash("sha256").update(source).digest("hex")}`;
|
|
692
|
-
}
|
|
693
|
-
function errorMessage(error) {
|
|
694
|
-
return error instanceof Error ? error.message : String(error);
|
|
695
|
-
}
|
|
696
|
-
function result(ok, diagnostics) {
|
|
697
|
-
return {
|
|
698
|
-
ok,
|
|
699
|
-
diagnostics: diagnostics.diagnostics
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
//# sourceMappingURL=compiler.js.map
|