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.
Files changed (143) hide show
  1. package/CHANGELOG.md +493 -0
  2. package/STABILITY.md +23 -5
  3. package/dist/assets/hints/cli-hints-full.md +12 -7
  4. package/dist/assets/tasks/core/extract.yml +3 -5
  5. package/dist/assets/tasks/core/improve.yml +3 -5
  6. package/dist/assets/tasks/core/index-refresh.yml +3 -5
  7. package/dist/assets/tasks/core/sync.yml +3 -5
  8. package/dist/assets/tasks/core/version-check.yml +3 -5
  9. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +3 -5
  10. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -6
  11. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +3 -5
  12. package/dist/assets/tasks/improve/akm-improve-frequent.yml +3 -5
  13. package/dist/assets/tasks/improve/akm-improve-nightly.yml +3 -5
  14. package/dist/cli/unknown-flags.js +12 -1
  15. package/dist/cli.js +8 -1
  16. package/dist/commands/command/command-execution.js +23 -2
  17. package/dist/commands/health/improve-metrics.js +38 -0
  18. package/dist/commands/health/windows.js +8 -4
  19. package/dist/commands/health.js +8 -4
  20. package/dist/commands/lint/index.js +1 -1
  21. package/dist/commands/migrate-cli.js +130 -24
  22. package/dist/commands/proposal/validators/proposal-validators.js +7 -2
  23. package/dist/commands/tasks/explain.js +304 -0
  24. package/dist/commands/tasks/tasks-cli.js +185 -3
  25. package/dist/commands/tasks/tasks.js +265 -52
  26. package/dist/commands/workflow/plan.js +159 -0
  27. package/dist/commands/workflow-cli.js +94 -2
  28. package/dist/core/activation-policy.js +2 -12
  29. package/dist/core/adapter/adapters/akm-lint.js +7 -4
  30. package/dist/core/adapter/adapters/akm-metadata.js +26 -14
  31. package/dist/core/adapter/adapters/akm-task-adapter.js +13 -10
  32. package/dist/core/errors.js +45 -0
  33. package/dist/core/json-schema.js +15 -5
  34. package/dist/core/state/migrations.js +57 -0
  35. package/dist/core/state-db.js +16 -14
  36. package/dist/core/subprocess.js +47 -13
  37. package/dist/execution/guarded-source.js +44 -0
  38. package/dist/execution/input-contract.js +250 -0
  39. package/dist/execution/target-ref.js +63 -0
  40. package/dist/indexer/usage/usage-events.js +14 -3
  41. package/dist/integrations/agent/execution-lowering.js +12 -1
  42. package/dist/output/shapes/passthrough.js +2 -0
  43. package/dist/output/text/helpers.js +1 -1
  44. package/dist/output/text/migrate.js +12 -3
  45. package/dist/output/text/workflow-format.js +192 -10
  46. package/dist/output/text/workflow.js +2 -1
  47. package/dist/runtime.js +1 -0
  48. package/dist/scripts/akm-migrate-node.js +11838 -10118
  49. package/dist/scripts/akm-migrate.js +11828 -10117
  50. package/dist/setup/steps/tasks.js +34 -17
  51. package/dist/storage/repositories/task-history-repository.js +5 -1
  52. package/dist/storage/repositories/workflow-runs-repository.js +144 -6
  53. package/dist/tasks/backends/launchd.js +31 -84
  54. package/dist/tasks/embedded.js +13 -7
  55. package/dist/tasks/model/invocation.js +4 -0
  56. package/dist/tasks/prepare/prepare-script-target.js +9 -0
  57. package/dist/tasks/prepare/prepare-support.js +154 -0
  58. package/dist/tasks/prepare/prepare.js +117 -0
  59. package/dist/tasks/prepare/prepared-execution.js +4 -0
  60. package/dist/tasks/prepare/script-capture.js +80 -0
  61. package/dist/tasks/run/attempt-lifecycle.js +165 -0
  62. package/dist/tasks/run/load-task.js +117 -0
  63. package/dist/tasks/run/provenance.js +20 -0
  64. package/dist/tasks/run/run-command-task.js +92 -0
  65. package/dist/tasks/run/run-native-task.js +222 -0
  66. package/dist/tasks/run/run-task.js +99 -0
  67. package/dist/tasks/run/run-workflow-task.js +222 -0
  68. package/dist/tasks/run/task-history.js +134 -0
  69. package/dist/tasks/run/task-log.js +179 -0
  70. package/dist/tasks/run/task-result.js +19 -0
  71. package/dist/tasks/scheduler-binding.js +66 -2
  72. package/dist/tasks/scheduler-invocation.js +63 -3
  73. package/dist/tasks/scheduler-sync.js +77 -14
  74. package/dist/tasks/source/bounded-document.js +455 -0
  75. package/dist/tasks/source/parse-task-source.js +59 -0
  76. package/dist/tasks/source/project-v4.js +62 -0
  77. package/dist/tasks/source/task-input-diagnostics.js +36 -0
  78. package/dist/tasks/source/task-source-v4.js +626 -0
  79. package/dist/tasks/source-v3.js +10 -733
  80. package/dist/tasks/task-run-reserved-flags.js +79 -0
  81. package/dist/workflows/authoring/authoring.js +17 -8
  82. package/dist/workflows/exec/child-invocation.js +34 -0
  83. package/dist/workflows/exec/child-workflow.js +370 -0
  84. package/dist/workflows/exec/exec-unit.js +50 -170
  85. package/dist/workflows/exec/frozen-judge.js +19 -2
  86. package/dist/workflows/exec/native-executor.js +49 -27
  87. package/dist/workflows/exec/param-secrets.js +12 -0
  88. package/dist/workflows/exec/run-workflow.js +48 -59
  89. package/dist/workflows/exec/step-work.js +222 -80
  90. package/dist/workflows/exec/unit-dispatch.js +72 -0
  91. package/dist/workflows/freeze/child-output-references.js +94 -0
  92. package/dist/workflows/freeze/environment.js +174 -0
  93. package/dist/workflows/freeze/identity.js +22 -0
  94. package/dist/workflows/freeze/resolve-steps.js +78 -0
  95. package/dist/workflows/freeze/source-freeze.js +57 -0
  96. package/dist/workflows/freeze/step-values.js +68 -0
  97. package/dist/workflows/freeze/targets/child-workflow.js +206 -0
  98. package/dist/workflows/freeze/targets/command.js +81 -0
  99. package/dist/workflows/freeze/targets/script.js +57 -0
  100. package/dist/workflows/freeze/targets/shell.js +31 -0
  101. package/dist/workflows/freeze/targets/task.js +179 -0
  102. package/dist/workflows/freeze/task-bindings.js +180 -0
  103. package/dist/workflows/ir/compile.js +59 -11
  104. package/dist/workflows/ir/environment-v4.js +3 -3
  105. package/dist/workflows/ir/freeze-v4.js +41 -7
  106. package/dist/workflows/ir/params.js +58 -131
  107. package/dist/workflows/ir/plan-hash.js +3 -3
  108. package/dist/workflows/ir/schema-v4.js +246 -17
  109. package/dist/workflows/parser.js +74 -2
  110. package/dist/workflows/program/schema.js +5 -2
  111. package/dist/workflows/resource-limits.js +20 -0
  112. package/dist/workflows/runtime/plan-classifier.js +24 -7
  113. package/dist/workflows/runtime/run-outputs.js +103 -0
  114. package/dist/workflows/runtime/runs.js +114 -9
  115. package/dist/workflows/runtime/workflow-asset-loader.js +14 -6
  116. package/dist/workflows/source-files.js +5 -5
  117. package/dist/workflows/source-ir/compare.js +17 -0
  118. package/dist/workflows/source-ir/compile.js +7 -3
  119. package/dist/workflows/source-ir/github-yaml.js +64 -17
  120. package/dist/workflows/source-ir/schema.js +69 -21
  121. package/dist/workflows/source-ir/semantics.js +7 -25
  122. package/dist/workflows/source-ir/triggers.js +79 -0
  123. package/dist/workflows/source-ir/uses.js +33 -7
  124. package/docs/migration/README.md +1 -1
  125. package/docs/migration/release-notes/0.9.2.md +87 -11
  126. package/docs/migration/release-notes/README.md +3 -2
  127. package/docs/migration/v0.8-to-v0.9.md +13 -11
  128. package/docs/migration/v0.9.0-troubleshooting.md +20 -13
  129. package/docs/migration/v0.9.1-to-v0.9.2.md +598 -49
  130. package/docs/reference/README.md +1 -1
  131. package/docs/reference/cli.md +140 -46
  132. package/docs/reference/configuration.md +6 -5
  133. package/docs/reference/supported-formats.md +9 -5
  134. package/docs/reference/tasks.md +338 -75
  135. package/docs/reference/workflow-schema.md +290 -16
  136. package/docs/reference/workflows.md +57 -7
  137. package/package.json +1 -1
  138. package/schemas/akm-task.json +173 -118
  139. package/schemas/akm-workflow.json +28 -0
  140. package/dist/tasks/runner.js +0 -941
  141. package/dist/tasks/runtime-v3.js +0 -281
  142. package/dist/workflows/ir/source-freeze-v4.js +0 -506
  143. 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, WORKFLOW_IR_V4_VERSION, } from "./schema-v4.js";
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
- throw new UsageError(compiled.errors.map((error) => `${asset.path}:${error.line}: ${error.message}`).join("\n"), "INVALID_FLAG_VALUE");
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: WORKFLOW_IR_V4_VERSION,
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.`, "INVALID_FLAG_VALUE");
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
- * Uses the same bounded {@link validateJsonSchemaSubset} the engine applies to
12
- * unit output. Internal callers may validate partial parameter objects; the CLI
13
- * separately rejects flags that do not exactly name declared parameters.
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 { validateJsonSchemaSubset } from "../../core/json-schema.js";
19
- import { PROGRAM_PARAM_NAME_PATTERN } from "../program/schema.js";
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
- const declared = new Set(plan.params ?? Object.keys(plan.paramSchemas ?? {}));
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
- const schemas = plan.paramSchemas;
153
- if (!schemas || Object.keys(schemas).length === 0)
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
- // Validate the params object as a whole against a synthetic object schema
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, WORKFLOW_IR_V4_VERSION } from "./schema-v4.js";
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 !== WORKFLOW_IR_V4_VERSION) {
48
- throw new Error(`Workflow run ${runId} uses unsupported workflow IR version ${expectedVersion}; this runtime supports only workflow IR version 4.`);
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);