akm-cli 0.9.2-alpha.4 → 0.9.2
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 +493 -0
- package/STABILITY.md +23 -5
- package/dist/assets/hints/cli-hints-full.md +12 -7
- package/dist/assets/tasks/core/extract.yml +3 -5
- package/dist/assets/tasks/core/improve.yml +3 -5
- package/dist/assets/tasks/core/index-refresh.yml +3 -5
- package/dist/assets/tasks/core/sync.yml +3 -5
- package/dist/assets/tasks/core/version-check.yml +3 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -6
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +3 -5
- package/dist/cli/unknown-flags.js +12 -1
- package/dist/cli.js +8 -1
- package/dist/commands/command/command-execution.js +23 -2
- package/dist/commands/health/improve-metrics.js +38 -0
- package/dist/commands/health/windows.js +8 -4
- package/dist/commands/health.js +8 -4
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/migrate-cli.js +130 -24
- package/dist/commands/proposal/validators/proposal-validators.js +7 -2
- package/dist/commands/tasks/explain.js +304 -0
- package/dist/commands/tasks/tasks-cli.js +185 -3
- package/dist/commands/tasks/tasks.js +265 -52
- package/dist/commands/workflow/plan.js +159 -0
- package/dist/commands/workflow-cli.js +94 -2
- package/dist/core/activation-policy.js +2 -12
- package/dist/core/adapter/adapters/akm-lint.js +7 -4
- package/dist/core/adapter/adapters/akm-metadata.js +26 -14
- package/dist/core/adapter/adapters/akm-task-adapter.js +13 -10
- package/dist/core/errors.js +45 -0
- package/dist/core/json-schema.js +15 -5
- package/dist/core/state/migrations.js +57 -0
- package/dist/core/state-db.js +16 -14
- package/dist/core/subprocess.js +47 -13
- package/dist/execution/guarded-source.js +44 -0
- package/dist/execution/input-contract.js +250 -0
- package/dist/execution/target-ref.js +63 -0
- package/dist/indexer/usage/usage-events.js +14 -3
- package/dist/integrations/agent/execution-lowering.js +12 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +12 -3
- package/dist/output/text/workflow-format.js +192 -10
- package/dist/output/text/workflow.js +2 -1
- package/dist/runtime.js +1 -0
- package/dist/scripts/akm-migrate-node.js +11838 -10118
- package/dist/scripts/akm-migrate.js +11828 -10117
- package/dist/setup/steps/tasks.js +34 -17
- package/dist/storage/repositories/task-history-repository.js +5 -1
- package/dist/storage/repositories/workflow-runs-repository.js +144 -6
- package/dist/tasks/backends/launchd.js +31 -84
- package/dist/tasks/embedded.js +13 -7
- package/dist/tasks/model/invocation.js +4 -0
- package/dist/tasks/prepare/prepare-script-target.js +9 -0
- package/dist/tasks/prepare/prepare-support.js +154 -0
- package/dist/tasks/prepare/prepare.js +117 -0
- package/dist/tasks/prepare/prepared-execution.js +4 -0
- package/dist/tasks/prepare/script-capture.js +80 -0
- package/dist/tasks/run/attempt-lifecycle.js +165 -0
- package/dist/tasks/run/load-task.js +117 -0
- package/dist/tasks/run/provenance.js +20 -0
- package/dist/tasks/run/run-command-task.js +92 -0
- package/dist/tasks/run/run-native-task.js +222 -0
- package/dist/tasks/run/run-task.js +99 -0
- package/dist/tasks/run/run-workflow-task.js +222 -0
- package/dist/tasks/run/task-history.js +134 -0
- package/dist/tasks/run/task-log.js +179 -0
- package/dist/tasks/run/task-result.js +19 -0
- package/dist/tasks/scheduler-binding.js +66 -2
- package/dist/tasks/scheduler-invocation.js +63 -3
- package/dist/tasks/scheduler-sync.js +77 -14
- package/dist/tasks/source/bounded-document.js +455 -0
- package/dist/tasks/source/parse-task-source.js +59 -0
- package/dist/tasks/source/project-v4.js +62 -0
- package/dist/tasks/source/task-input-diagnostics.js +36 -0
- package/dist/tasks/source/task-source-v4.js +626 -0
- package/dist/tasks/source-v3.js +10 -733
- package/dist/tasks/task-run-reserved-flags.js +79 -0
- package/dist/workflows/authoring/authoring.js +17 -8
- package/dist/workflows/exec/child-invocation.js +34 -0
- package/dist/workflows/exec/child-workflow.js +370 -0
- package/dist/workflows/exec/exec-unit.js +50 -170
- package/dist/workflows/exec/frozen-judge.js +19 -2
- package/dist/workflows/exec/native-executor.js +49 -27
- package/dist/workflows/exec/param-secrets.js +12 -0
- package/dist/workflows/exec/run-workflow.js +48 -59
- package/dist/workflows/exec/step-work.js +222 -80
- package/dist/workflows/exec/unit-dispatch.js +72 -0
- package/dist/workflows/freeze/child-output-references.js +94 -0
- package/dist/workflows/freeze/environment.js +174 -0
- package/dist/workflows/freeze/identity.js +22 -0
- package/dist/workflows/freeze/resolve-steps.js +78 -0
- package/dist/workflows/freeze/source-freeze.js +57 -0
- package/dist/workflows/freeze/step-values.js +68 -0
- package/dist/workflows/freeze/targets/child-workflow.js +206 -0
- package/dist/workflows/freeze/targets/command.js +81 -0
- package/dist/workflows/freeze/targets/script.js +57 -0
- package/dist/workflows/freeze/targets/shell.js +31 -0
- package/dist/workflows/freeze/targets/task.js +179 -0
- package/dist/workflows/freeze/task-bindings.js +180 -0
- package/dist/workflows/ir/compile.js +59 -11
- package/dist/workflows/ir/environment-v4.js +3 -3
- package/dist/workflows/ir/freeze-v4.js +41 -7
- package/dist/workflows/ir/params.js +58 -131
- package/dist/workflows/ir/plan-hash.js +3 -3
- package/dist/workflows/ir/schema-v4.js +246 -17
- package/dist/workflows/parser.js +74 -2
- package/dist/workflows/program/schema.js +5 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +24 -7
- package/dist/workflows/runtime/run-outputs.js +103 -0
- package/dist/workflows/runtime/runs.js +114 -9
- package/dist/workflows/runtime/workflow-asset-loader.js +14 -6
- package/dist/workflows/source-files.js +5 -5
- package/dist/workflows/source-ir/compare.js +17 -0
- package/dist/workflows/source-ir/compile.js +7 -3
- package/dist/workflows/source-ir/github-yaml.js +64 -17
- package/dist/workflows/source-ir/schema.js +69 -21
- package/dist/workflows/source-ir/semantics.js +7 -25
- package/dist/workflows/source-ir/triggers.js +79 -0
- package/dist/workflows/source-ir/uses.js +33 -7
- package/docs/migration/README.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +87 -11
- package/docs/migration/release-notes/README.md +3 -2
- package/docs/migration/v0.8-to-v0.9.md +13 -11
- package/docs/migration/v0.9.0-troubleshooting.md +20 -13
- package/docs/migration/v0.9.1-to-v0.9.2.md +598 -49
- package/docs/reference/README.md +1 -1
- package/docs/reference/cli.md +140 -46
- package/docs/reference/configuration.md +6 -5
- package/docs/reference/supported-formats.md +9 -5
- package/docs/reference/tasks.md +338 -75
- package/docs/reference/workflow-schema.md +290 -16
- package/docs/reference/workflows.md +57 -7
- package/package.json +1 -1
- package/schemas/akm-task.json +173 -118
- package/schemas/akm-workflow.json +28 -0
- package/dist/tasks/runner.js +0 -941
- package/dist/tasks/runtime-v3.js +0 -281
- package/dist/workflows/ir/source-freeze-v4.js +0 -506
- package/dist/workflows/source-ir/ordering.js +0 -38
|
@@ -4,21 +4,51 @@
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
7
|
-
import { UsageError } from "../../core/errors.js";
|
|
7
|
+
import { COMPOSITION_INVALID_MULTI_JOB_HINT, UsageError } from "../../core/errors.js";
|
|
8
8
|
import { GuardedExecutionSourceCollector } from "../../execution/guarded-source.js";
|
|
9
9
|
import { defaultMapConcurrency, workflowMaxConcurrency } from "../concurrency-policy.js";
|
|
10
|
+
import { assertChildOutputReferences } from "../freeze/child-output-references.js";
|
|
11
|
+
import { resolveWorkflowSourceV4 } from "../freeze/source-freeze.js";
|
|
10
12
|
import { compileWorkflowPlan } from "./compile.js";
|
|
11
13
|
import { canonicalJson } from "./plan-hash.js";
|
|
12
|
-
import { decodeWorkflowPlanV4,
|
|
13
|
-
import { resolveWorkflowSourceV4 } from "./source-freeze-v4.js";
|
|
14
|
+
import { decodeWorkflowPlanV4, WORKFLOW_IR_V5_VERSION, } from "./schema-v4.js";
|
|
14
15
|
/** Compile, resolve, and freeze one executable plan without publishing any state. */
|
|
15
16
|
export async function compileResolveFreezeWorkflowV4(asset, config, options = {}) {
|
|
16
17
|
const sourceCollector = options.sourceCollector ?? new GuardedExecutionSourceCollector();
|
|
18
|
+
const composition = options.composition ?? {
|
|
19
|
+
depth: 0,
|
|
20
|
+
refPath: [asset.ref],
|
|
21
|
+
budget: { embeddedBytes: 0 },
|
|
22
|
+
};
|
|
23
|
+
// Injected rather than imported (A-N7/step-values.ts's `ChildCompositionContext`
|
|
24
|
+
// doc): `targets/child-workflow.ts` is downstream of this module (via
|
|
25
|
+
// `resolve-steps.ts` <- `source-freeze.ts`, imported directly above — the
|
|
26
|
+
// `source-freeze-v4.ts` shim P4 deleted used to sit on this edge), so it
|
|
27
|
+
// cannot import `compileResolveFreezeWorkflowV4` directly without closing
|
|
28
|
+
// a static import cycle
|
|
29
|
+
// (tests/architecture/import-cycle-ratchet.test.ts, shrink-only, empty
|
|
30
|
+
// baseline). This closure is this function's own recursive call, passed
|
|
31
|
+
// down as a plain value through `ResolutionContext.freezeChild`.
|
|
32
|
+
const freezeChild = async (request) => {
|
|
33
|
+
const child = await compileResolveFreezeWorkflowV4(request.asset, config, {
|
|
34
|
+
sourceCollector: request.sourceCollector,
|
|
35
|
+
composition: request.composition,
|
|
36
|
+
});
|
|
37
|
+
return { plan: child.plan, sourceCollector: child.sourceCollector };
|
|
38
|
+
};
|
|
17
39
|
const workflowSource = captureWorkflowSource(asset, sourceCollector);
|
|
18
|
-
const resolved = await resolveWorkflowSourceV4(asset, workflowSource, config, sourceCollector);
|
|
40
|
+
const resolved = await resolveWorkflowSourceV4(asset, workflowSource, config, sourceCollector, composition, freezeChild);
|
|
19
41
|
const compiled = compileWorkflowPlan(resolved.sourceIr, asset.title, resolved.units);
|
|
20
42
|
if (!compiled.ok) {
|
|
21
|
-
|
|
43
|
+
// P4-N2's mapping (docs/plans/specs/p4-deletions-closeout.md §3.3.4)
|
|
44
|
+
// applies here too, though `resolved.sourceIr` is already guaranteed
|
|
45
|
+
// exactly one job by this point (source-freeze.ts's own compile already
|
|
46
|
+
// succeeded), so this compiler's own errors never carry the
|
|
47
|
+
// multi-job-unsupported code — the arm always resolves to
|
|
48
|
+
// WORKFLOW_SOURCE_INVALID.
|
|
49
|
+
const isMultiJob = compiled.errors.length === 1 && compiled.errors[0]?.code === "multi-job-unsupported";
|
|
50
|
+
const code = isMultiJob ? "COMPOSITION_INVALID" : "WORKFLOW_SOURCE_INVALID";
|
|
51
|
+
throw new UsageError(compiled.errors.map((error) => `${asset.path}:${error.line}: ${error.message}`).join("\n"), code, isMultiJob ? COMPOSITION_INVALID_MULTI_JOB_HINT : undefined);
|
|
22
52
|
}
|
|
23
53
|
const steps = compiled.plan.steps.map((step) => {
|
|
24
54
|
const frozenJudge = step.gate.criteria.length === 0 ? null : resolved.judges.get(step.stepId);
|
|
@@ -42,12 +72,15 @@ export async function compileResolveFreezeWorkflowV4(asset, config, options = {}
|
|
|
42
72
|
: freezeResolvedUnit(step.root, frozen);
|
|
43
73
|
return Object.freeze({ ...step, root, gate });
|
|
44
74
|
});
|
|
75
|
+
// P3b, spec §4.4: after every step's target is resolved (so every embedded
|
|
76
|
+
// child plan is available) and before the plan object is assembled.
|
|
77
|
+
assertChildOutputReferences(steps);
|
|
45
78
|
const sourceReadSet = sourceCollector
|
|
46
79
|
.snapshot()
|
|
47
80
|
.sources.filter((source) => Boolean(source.identity))
|
|
48
81
|
.map((source) => sourceSnapshot(source));
|
|
49
82
|
const plan = decodeWorkflowPlanV4({
|
|
50
|
-
irVersion:
|
|
83
|
+
irVersion: WORKFLOW_IR_V5_VERSION,
|
|
51
84
|
title: compiled.plan.title,
|
|
52
85
|
...(compiled.plan.params ? { params: compiled.plan.params } : {}),
|
|
53
86
|
...(compiled.plan.paramSchemas ? { paramSchemas: compiled.plan.paramSchemas } : {}),
|
|
@@ -55,6 +88,7 @@ export async function compileResolveFreezeWorkflowV4(asset, config, options = {}
|
|
|
55
88
|
execution: { maxConcurrency: workflowMaxConcurrency(config.workflow?.maxConcurrency) },
|
|
56
89
|
sourceReadSet,
|
|
57
90
|
steps,
|
|
91
|
+
...(compiled.plan.outputs ? { outputs: compiled.plan.outputs } : {}),
|
|
58
92
|
});
|
|
59
93
|
return Object.freeze({
|
|
60
94
|
plan,
|
|
@@ -102,7 +136,7 @@ function captureWorkflowSource(asset, collector) {
|
|
|
102
136
|
const captured = collector.capture(file, root, { authored: true });
|
|
103
137
|
const parsed = parseBundleRef(asset.ref);
|
|
104
138
|
if (!parsed.bundle || !asset.adapterId) {
|
|
105
|
-
throw new UsageError(`Workflow ${asset.ref} has no fully-qualified bundle/adapter owner for durable publication.`, "
|
|
139
|
+
throw new UsageError(`Workflow ${asset.ref} has no fully-qualified bundle/adapter owner for durable publication.`, "WORKFLOW_SOURCE_INVALID");
|
|
106
140
|
}
|
|
107
141
|
return collector.bindIdentity(file, root, {
|
|
108
142
|
ref: asset.ref,
|
|
@@ -8,15 +8,62 @@
|
|
|
8
8
|
* flowing into a unit prompt. The schemas are frozen into the plan, so
|
|
9
9
|
* validation is a pure function of the frozen plan and supplied params.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* P2a (docs/plans/specs/p2a-task-source-v4.md §4.3, D3): this module is now a
|
|
12
|
+
* THIN CONSUMER of the shared input contract, `src/execution/input-contract.ts`
|
|
13
|
+
* (§4, D3, D3-N1/D3-N2/D3-N3). Every export below keeps its existing name,
|
|
14
|
+
* signature, message, code, and hint byte-identically —
|
|
15
|
+
* `tests/workflows/workflow-param-flags.test.ts` and
|
|
16
|
+
* `tests/integration/workflows/params-validation.test.ts` pin that with zero
|
|
17
|
+
* diff. `contractFromPlan` adapts a `WorkflowParameterPlan` into an
|
|
18
|
+
* `InputContract` (every declared param, `required: false` — workflow params
|
|
19
|
+
* declare nothing required). `WORKFLOW_PARAMETER_DIAGNOSTICS` reproduces
|
|
20
|
+
* today's five workflow-parameter messages/codes for the shared
|
|
21
|
+
* `materializeInputFlags` (D3-N3); its `contractViolation` formatter
|
|
22
|
+
* re-roots each `"$"`-prefixed error from the shared module's internal check
|
|
23
|
+
* to `"params."`, matching what `validateWorkflowParams` (called directly,
|
|
24
|
+
* already `"params."`-rooted) has always produced. No coercion or validation
|
|
25
|
+
* logic lives in this file — it lives once, in the shared module.
|
|
14
26
|
*
|
|
15
27
|
* Pure module: no IO, no engine imports.
|
|
16
28
|
*/
|
|
17
29
|
import { UsageError } from "../../core/errors.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
30
|
+
import { materializeInputFlags, validateInputs, } from "../../execution/input-contract.js";
|
|
31
|
+
/** Every declared workflow param as an `InputDeclaration`, `required: false` — workflow params declare nothing required (§4.3). */
|
|
32
|
+
function contractFromPlan(plan) {
|
|
33
|
+
const names = plan.params ?? Object.keys(plan.paramSchemas ?? {});
|
|
34
|
+
const contract = {};
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
contract[name] = { schema: plan.paramSchemas?.[name] ?? {}, required: false };
|
|
37
|
+
}
|
|
38
|
+
return contract;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `contractFromPlan`, exported under its P3a-facing name (spec
|
|
42
|
+
* docs/plans/specs/p3a-plan-v5-child-freeze.md A-N8): a child workflow has no
|
|
43
|
+
* `inputs:` contract of its own (that is task source v4's vocabulary) — it
|
|
44
|
+
* declares `params:`, and this is the SAME adapter this file already uses
|
|
45
|
+
* for run-parameter validation, reused so `freezeTaskInputBindings`
|
|
46
|
+
* (`src/workflows/freeze/task-bindings.ts`, generic over `InputContract`) has
|
|
47
|
+
* exactly one normalizer for every binding surface. Additive; no behavior
|
|
48
|
+
* change to this file's own exports.
|
|
49
|
+
*/
|
|
50
|
+
export { contractFromPlan as workflowParamContract };
|
|
51
|
+
function invalidParameter(name, message) {
|
|
52
|
+
return new UsageError(`Workflow parameter "--${name}" ${message}.`, "INVALID_FLAG_VALUE");
|
|
53
|
+
}
|
|
54
|
+
/** The workflow-parameter message/code vocabulary for `materializeInputFlags` (D3-N3) — today's five strings, unchanged. */
|
|
55
|
+
const WORKFLOW_PARAMETER_DIAGNOSTICS = {
|
|
56
|
+
unknownFlag: (name, declared) => {
|
|
57
|
+
const available = declared.map((n) => `--${n}`).join(", ");
|
|
58
|
+
return new UsageError(`Unknown workflow parameter "--${name}". Parameter flags must exactly match a declared workflow parameter.`, "UNKNOWN_FLAG", available ? `Declared parameters: ${available}.` : "This workflow declares no parameters.");
|
|
59
|
+
},
|
|
60
|
+
invalidValue: (name, detail) => invalidParameter(name, detail),
|
|
61
|
+
contractViolation: (errors) => new UsageError(`Workflow parameter flags do not satisfy the workflow's declared schemas:\n${errors
|
|
62
|
+
.map((error) => ` - ${error.replace(/^\$/, "params")}`)
|
|
63
|
+
.join("\n")}`, "INVALID_FLAG_VALUE"),
|
|
64
|
+
duplicateNonArray: (name) => invalidParameter(name, "was provided more than once but is not declared as an array"),
|
|
65
|
+
malformedJson: (name) => invalidParameter(name, "must contain valid JSON"),
|
|
66
|
+
};
|
|
20
67
|
/**
|
|
21
68
|
* Materialize exact-name CLI parameter flags against the plan being frozen for
|
|
22
69
|
* the run. The CLI deliberately carries raw values to this boundary so type
|
|
@@ -25,123 +72,7 @@ import { PROGRAM_PARAM_NAME_PATTERN } from "../program/schema.js";
|
|
|
25
72
|
export function materializeWorkflowParameterFlags(plan, flags) {
|
|
26
73
|
if (flags.length === 0)
|
|
27
74
|
return {};
|
|
28
|
-
|
|
29
|
-
const grouped = new Map();
|
|
30
|
-
for (const flag of flags) {
|
|
31
|
-
if (!PROGRAM_PARAM_NAME_PATTERN.test(flag.name) || !declared.has(flag.name)) {
|
|
32
|
-
const available = [...declared]
|
|
33
|
-
.sort()
|
|
34
|
-
.map((name) => `--${name}`)
|
|
35
|
-
.join(", ");
|
|
36
|
-
throw new UsageError(`Unknown workflow parameter "--${flag.name}". Parameter flags must exactly match a declared workflow parameter.`, "UNKNOWN_FLAG", available ? `Declared parameters: ${available}.` : "This workflow declares no parameters.");
|
|
37
|
-
}
|
|
38
|
-
const values = grouped.get(flag.name) ?? [];
|
|
39
|
-
values.push(flag.value);
|
|
40
|
-
grouped.set(flag.name, values);
|
|
41
|
-
}
|
|
42
|
-
const entries = [];
|
|
43
|
-
for (const [name, values] of grouped) {
|
|
44
|
-
const schema = plan.paramSchemas?.[name];
|
|
45
|
-
entries.push([name, materializeFlagValues(name, values, schema)]);
|
|
46
|
-
}
|
|
47
|
-
const params = Object.fromEntries(entries);
|
|
48
|
-
const errors = validateWorkflowParams(plan, params);
|
|
49
|
-
if (errors.length > 0) {
|
|
50
|
-
throw new UsageError(`Workflow parameter flags do not satisfy the workflow's declared schemas:\n${errors.map((error) => ` - ${error}`).join("\n")}`, "INVALID_FLAG_VALUE");
|
|
51
|
-
}
|
|
52
|
-
return params;
|
|
53
|
-
}
|
|
54
|
-
function materializeFlagValues(name, values, schema) {
|
|
55
|
-
const types = schemaTypes(schema);
|
|
56
|
-
if (types.includes("array")) {
|
|
57
|
-
if (values.length === 1 && typeof values[0] === "string" && values[0].trim().startsWith("[")) {
|
|
58
|
-
const parsed = parseJsonFlag(name, values[0]);
|
|
59
|
-
if (!Array.isArray(parsed))
|
|
60
|
-
throw invalidParameter(name, "must be a JSON array");
|
|
61
|
-
return parsed;
|
|
62
|
-
}
|
|
63
|
-
const itemSchema = isRecord(schema?.items) ? schema.items : undefined;
|
|
64
|
-
return values.map((value) => coerceFlagValue(name, value, itemSchema));
|
|
65
|
-
}
|
|
66
|
-
if (values.length > 1) {
|
|
67
|
-
throw invalidParameter(name, "was provided more than once but is not declared as an array");
|
|
68
|
-
}
|
|
69
|
-
return coerceFlagValue(name, values[0], schema);
|
|
70
|
-
}
|
|
71
|
-
function coerceFlagValue(name, raw, schema) {
|
|
72
|
-
const types = schemaTypes(schema);
|
|
73
|
-
if (types.length === 0)
|
|
74
|
-
return raw;
|
|
75
|
-
if (typeof raw === "boolean") {
|
|
76
|
-
if (types.includes("boolean"))
|
|
77
|
-
return raw;
|
|
78
|
-
if (types.includes("string"))
|
|
79
|
-
return String(raw);
|
|
80
|
-
throw invalidParameter(name, `requires a value of type ${types.join(" | ")}`);
|
|
81
|
-
}
|
|
82
|
-
// A union that permits strings keeps the user's exact text. This prevents a
|
|
83
|
-
// value such as "001" from being silently converted to a number.
|
|
84
|
-
if (types.includes("string"))
|
|
85
|
-
return raw;
|
|
86
|
-
for (const type of types) {
|
|
87
|
-
switch (type) {
|
|
88
|
-
case "boolean":
|
|
89
|
-
if (raw === "true")
|
|
90
|
-
return true;
|
|
91
|
-
if (raw === "false")
|
|
92
|
-
return false;
|
|
93
|
-
break;
|
|
94
|
-
case "number": {
|
|
95
|
-
const value = Number(raw);
|
|
96
|
-
if (raw.trim() !== "" && Number.isFinite(value))
|
|
97
|
-
return value;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
case "integer": {
|
|
101
|
-
const value = Number(raw);
|
|
102
|
-
if (raw.trim() !== "" && Number.isSafeInteger(value))
|
|
103
|
-
return value;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
case "null":
|
|
107
|
-
if (raw === "null")
|
|
108
|
-
return null;
|
|
109
|
-
break;
|
|
110
|
-
case "object": {
|
|
111
|
-
const parsed = parseJsonFlag(name, raw);
|
|
112
|
-
if (isRecord(parsed))
|
|
113
|
-
return parsed;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case "array": {
|
|
117
|
-
const parsed = parseJsonFlag(name, raw);
|
|
118
|
-
if (Array.isArray(parsed))
|
|
119
|
-
return parsed;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
throw invalidParameter(name, `must be ${types.join(" | ")}; received ${JSON.stringify(raw)}`);
|
|
125
|
-
}
|
|
126
|
-
function schemaTypes(schema) {
|
|
127
|
-
const declared = schema?.type;
|
|
128
|
-
if (typeof declared === "string")
|
|
129
|
-
return [declared];
|
|
130
|
-
return Array.isArray(declared) ? declared.filter((value) => typeof value === "string") : [];
|
|
131
|
-
}
|
|
132
|
-
function parseJsonFlag(name, raw) {
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(raw);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
throw invalidParameter(name, "must contain valid JSON");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function invalidParameter(name, message) {
|
|
141
|
-
return new UsageError(`Workflow parameter "--${name}" ${message}.`, "INVALID_FLAG_VALUE");
|
|
142
|
-
}
|
|
143
|
-
function isRecord(value) {
|
|
144
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
75
|
+
return materializeInputFlags(contractFromPlan(plan), flags, WORKFLOW_PARAMETER_DIAGNOSTICS);
|
|
145
76
|
}
|
|
146
77
|
/**
|
|
147
78
|
* Validate a run's supplied params against the plan's frozen param schemas.
|
|
@@ -149,16 +80,12 @@ function isRecord(value) {
|
|
|
149
80
|
* valid). Params the plan does not declare a schema for are not constrained.
|
|
150
81
|
*/
|
|
151
82
|
export function validateWorkflowParams(plan, params) {
|
|
152
|
-
|
|
153
|
-
|
|
83
|
+
// A plan with no schemas must not start emitting `properties: {}` noise —
|
|
84
|
+
// preserved as an explicit early return (§4.3 binding constraint), even
|
|
85
|
+
// though an empty contract would validate to `[]` regardless.
|
|
86
|
+
if (!plan.paramSchemas || Object.keys(plan.paramSchemas).length === 0)
|
|
154
87
|
return [];
|
|
155
|
-
|
|
156
|
-
// whose `properties` are the declared param schemas. Missing declared params
|
|
157
|
-
// are NOT required (params may be optional / defaulted downstream); only a
|
|
158
|
-
// PRESENT param that violates its declared schema is an error.
|
|
159
|
-
// Re-root the validator's `$` JSON-pointer prefix to `params` for messages
|
|
160
|
-
// that read naturally in a start/CLI error (e.g. `params.files: expected …`).
|
|
161
|
-
return validateJsonSchemaSubset(params, { type: "object", properties: schemas }).map((e) => e.replace(/^\$/, "params"));
|
|
88
|
+
return validateInputs(contractFromPlan(plan), params, { pathRoot: "params" });
|
|
162
89
|
}
|
|
163
90
|
/**
|
|
164
91
|
* Run-integrity assert (reviewer #12): the journaled `params_json`
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { utf8Bytes, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_PLAN_BYTES } from "../resource-limits.js";
|
|
17
|
-
import { decodeWorkflowPlanV4,
|
|
17
|
+
import { decodeWorkflowPlanV4, WORKFLOW_IR_V5_VERSION } from "./schema-v4.js";
|
|
18
18
|
/** sha256 hex of the canonical (recursively sorted-keys) JSON of the plan. */
|
|
19
19
|
export function computePlanHash(plan) {
|
|
20
20
|
return createHash("sha256").update(canonicalPlanJson(plan)).digest("hex");
|
|
@@ -44,8 +44,8 @@ export function decodeCanonicalPlan(runId, planJson, planHash, expectedVersion)
|
|
|
44
44
|
const actual = createHash("sha256").update(planJson).digest("hex");
|
|
45
45
|
if (!planHash || !/^[0-9a-f]{64}$/.test(planHash) || actual !== planHash)
|
|
46
46
|
throw new Error(`Workflow run ${runId} frozen plan integrity check failed.`);
|
|
47
|
-
if (expectedVersion !== undefined && expectedVersion !== null && expectedVersion !==
|
|
48
|
-
throw new Error(`Workflow run ${runId} uses unsupported workflow IR version ${expectedVersion}; this runtime supports only workflow IR version
|
|
47
|
+
if (expectedVersion !== undefined && expectedVersion !== null && expectedVersion !== WORKFLOW_IR_V5_VERSION) {
|
|
48
|
+
throw new Error(`Workflow run ${runId} uses unsupported workflow IR version ${expectedVersion}; this runtime supports only workflow IR version 5.`);
|
|
49
49
|
}
|
|
50
50
|
const plan = decodeWorkflowPlanV4(parsed);
|
|
51
51
|
const canonical = canonicalPlanJson(plan);
|