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
@@ -14,8 +14,8 @@ import { validateExtraParams } from "../../core/extra-params.js";
14
14
  import { checkJsonSchemaDefinition } from "../../core/json-schema.js";
15
15
  import { parseReference } from "../program/expressions.js";
16
16
  import { PROGRAM_RETRY_REASONS } from "../program/schema.js";
17
- import { jsonBytes, utf8Bytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
18
- import { canonicalTopologicalJobs, compareWorkflowSourceCodePoints } from "./ordering.js";
17
+ import { jsonBytes, utf8Bytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_EXEC_ARG_BYTES, WORKFLOW_MAX_EXEC_ARGV, WORKFLOW_MAX_EXEC_CWD_LENGTH, WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_GATE_LOOPS, WORKFLOW_MAX_INPUTS, WORKFLOW_MAX_MAP_EXPANSION, WORKFLOW_MAX_OUTPUTS, WORKFLOW_MAX_PARAMS, WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_ROUTE_BRANCHES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
18
+ import { compareWorkflowSourceCodePoints } from "./compare.js";
19
19
  import { canonicalizeWorkflowCron, canonicalizeWorkflowRun, canonicalizeWorkflowWorkingDirectory, classifyWorkflowStepUses, rejectNulInArgv, validateWorkflowBuiltinCommand, WorkflowSourceSemanticError, } from "./semantics.js";
20
20
  export const WORKFLOW_SOURCE_IR_VERSION = 1;
21
21
  export const WORKFLOW_SOURCE_IR_MAX_BYTES = 2 * 1024 * 1024;
@@ -35,6 +35,7 @@ export function decodeWorkflowSourceIrV1(input, options = {}) {
35
35
  "description",
36
36
  "tags",
37
37
  "params",
38
+ "outputs",
38
39
  "defaults",
39
40
  "budget",
40
41
  "preamble",
@@ -50,6 +51,7 @@ export function decodeWorkflowSourceIrV1(input, options = {}) {
50
51
  optionalString(root.preamble, "preamble");
51
52
  optionalStringList(root.tags, "tags", 256);
52
53
  validateParams(root.params);
54
+ validateOutputs(root.outputs);
53
55
  validateUnit(root.defaults, "defaults", true);
54
56
  validateBudget(root.budget);
55
57
  span(root.source, "source");
@@ -58,8 +60,8 @@ export function decodeWorkflowSourceIrV1(input, options = {}) {
58
60
  fail("triggers must contain 1 through 64 entries");
59
61
  }
60
62
  validateTriggers(root.triggers);
61
- if (!Array.isArray(root.jobs) || root.jobs.length === 0 || root.jobs.length > 256) {
62
- fail("jobs must contain 1 through 256 entries");
63
+ if (!Array.isArray(root.jobs) || root.jobs.length !== 1) {
64
+ fail("jobs must contain exactly 1 entry");
63
65
  }
64
66
  const jobIds = new Set();
65
67
  for (const [index, job] of root.jobs.entries())
@@ -69,7 +71,6 @@ export function decodeWorkflowSourceIrV1(input, options = {}) {
69
71
  if (!jobIds.has(need))
70
72
  fail(`job ${job.id} needs missing job ${need}`);
71
73
  }
72
- validateTopologicalJobs(root.jobs);
73
74
  return decoded;
74
75
  }
75
76
  function validateTrigger(value, index) {
@@ -177,6 +178,9 @@ function validateStep(value, jobId, index, stepIds, options) {
177
178
  fail(`step ${id} uses must be a non-empty string`);
178
179
  if (step.run !== undefined && !hasRun)
179
180
  fail(`step ${id} run must be a non-empty string`);
181
+ // A-N3: captured at the outer scope so scalarRecord's task-scoped widening
182
+ // (below) can consult it without re-classifying `step.uses`.
183
+ let usesTarget;
180
184
  if (hasUses) {
181
185
  let target;
182
186
  try {
@@ -185,6 +189,7 @@ function validateStep(value, jobId, index, stepIds, options) {
185
189
  catch (cause) {
186
190
  semanticFail(cause, `step ${id} uses`);
187
191
  }
192
+ usesTarget = target;
188
193
  if (target.kind === "builtin-command") {
189
194
  if (step.commandMode !== "literal" &&
190
195
  step.commandMode !== "portable-template" &&
@@ -214,7 +219,13 @@ function validateStep(value, jobId, index, stepIds, options) {
214
219
  }
215
220
  }
216
221
  validateExec(step.exec, `step ${id} exec`, options);
217
- scalarRecord(step.with, `step ${id} with`, true);
222
+ // A-N3, widened in P3a (spec docs/plans/specs/p3a-plan-v5-child-freeze.md
223
+ // §4.2 step 7, row B-10, A-N8): the "must be a scalar" restriction narrows
224
+ // to non-task, non-workflow targets only. A tasks/<ref> or workflows/<ref>
225
+ // step's with: may carry any JSON value the bounded document front end
226
+ // already accepts; freeze (src/workflows/freeze/**) decides what a
227
+ // declared input/param actually accepts.
228
+ scalarRecord(step.with, `step ${id} with`, true, usesTarget?.kind === "task" || usesTarget?.kind === "workflow");
218
229
  environment(step.env, `step ${id} env`);
219
230
  rejectStepWithExpressions(step, id);
220
231
  rejectExpressionsInRecord(step.env, `step ${id} env`);
@@ -258,17 +269,6 @@ function validateStep(value, jobId, index, stepIds, options) {
258
269
  extensions(step.extensions, `step ${id} extensions`);
259
270
  span(step.source, `step ${id} source`);
260
271
  }
261
- function validateTopologicalJobs(jobs) {
262
- const result = canonicalTopologicalJobs(jobs);
263
- if (!result.ok) {
264
- if (result.kind === "missing")
265
- fail(`job ${result.job.id} needs missing job ${result.dependency}`);
266
- fail("jobs contain a dependency cycle");
267
- }
268
- if (result.jobs.some((job, index) => job.id !== jobs[index]?.id)) {
269
- fail("jobs are not in canonical dependency-topological order");
270
- }
271
- }
272
272
  function compareCodePoints(left, right) {
273
273
  return compareWorkflowSourceCodePoints(left, right);
274
274
  }
@@ -441,10 +441,13 @@ function validateRouteTargets(steps, jobId) {
441
441
  }
442
442
  }
443
443
  }
444
- function validateReference(value, location) {
444
+ function validateReference(value, location, expectedKind) {
445
445
  const parsed = parseReference(value);
446
446
  if (!parsed.ok)
447
447
  fail(`${location} is invalid: ${parsed.message}`);
448
+ if (expectedKind && parsed.expr.kind !== expectedKind) {
449
+ fail(`${location} must reference a step output (steps.<id>.output...), not a param`);
450
+ }
448
451
  }
449
452
  function validateParams(value) {
450
453
  if (value === undefined)
@@ -459,6 +462,30 @@ function validateParams(value) {
459
462
  validateSchema(schema, `params.${name}`, false);
460
463
  }
461
464
  }
465
+ /**
466
+ * Structural re-validation of `outputs:` (P3b, spec §4.2) — the parser
467
+ * (`../parser.ts`) already enforces the same rules at authoring time; this is
468
+ * the corruption-gate re-check every source-IR field gets, mirroring
469
+ * `validateParams` immediately above.
470
+ */
471
+ function validateOutputs(value) {
472
+ if (value === undefined)
473
+ return;
474
+ const outputs = record(value, "outputs");
475
+ if (Object.keys(outputs).length === 0 || Object.keys(outputs).length > WORKFLOW_MAX_OUTPUTS) {
476
+ fail(`outputs must contain 1 through ${WORKFLOW_MAX_OUTPUTS} entries`);
477
+ }
478
+ for (const [name, declaration] of Object.entries(outputs)) {
479
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
480
+ fail(`outputs has invalid name ${name}`);
481
+ const decl = record(declaration, `outputs.${name}`);
482
+ keys(decl, ["from", "schema"], `outputs.${name}`);
483
+ nonEmptyString(decl.from, `outputs.${name}.from`);
484
+ validateReference(decl.from, `outputs.${name}.from`, "stepOutput");
485
+ if (decl.schema !== undefined)
486
+ validateSchema(decl.schema, `outputs.${name}.schema`, false);
487
+ }
488
+ }
462
489
  function validateBudget(value) {
463
490
  if (value === undefined)
464
491
  return;
@@ -545,14 +572,25 @@ function rejectExpressionsInRecord(value, location) {
545
572
  fail(`${location}.${key} contains an unsupported expression`);
546
573
  }
547
574
  }
575
+ /** A-N3: recurses into nested values, since a task step's with: may now carry an object/array. */
576
+ function containsUnsupportedExpression(value) {
577
+ if (typeof value === "string")
578
+ return value.includes("${{");
579
+ if (Array.isArray(value))
580
+ return value.some((item) => containsUnsupportedExpression(item));
581
+ if (value !== null && typeof value === "object") {
582
+ return Object.values(value).some((item) => containsUnsupportedExpression(item));
583
+ }
584
+ return false;
585
+ }
548
586
  function rejectStepWithExpressions(step, id) {
549
587
  if (step.with === undefined)
550
588
  return;
551
589
  for (const [key, item] of Object.entries(record(step.with, `step ${id} with`))) {
552
- if (typeof item !== "string" || !item.includes("${{"))
553
- continue;
554
590
  if (step.uses === "akm/command" && step.commandMode === "literal" && key === "content")
555
591
  continue;
592
+ if (!containsUnsupportedExpression(item))
593
+ continue;
556
594
  fail(`step ${id} with.${key} contains an unsupported expression`);
557
595
  }
558
596
  }
@@ -722,13 +760,23 @@ function stringList(value, location, max, allowEmpty) {
722
760
  seen.add(item);
723
761
  }
724
762
  }
725
- function scalarRecord(value, location, allowNull) {
763
+ /**
764
+ * `allowNonScalar` (A-N3, P2b; widened in P3a, A-N8): when true (a
765
+ * `tasks/<ref>` or `workflows/<ref>` step's `with:`), the key-grammar check
766
+ * still runs but the scalar/null value restriction is skipped entirely — a
767
+ * declared object/array-typed input or param, or a `{from: "..."}`
768
+ * reference, may decode. Every other target keeps the byte-identical
769
+ * scalar-or-null restriction.
770
+ */
771
+ function scalarRecord(value, location, allowNull, allowNonScalar = false) {
726
772
  if (value === undefined)
727
773
  return;
728
774
  const map = record(value, location);
729
775
  for (const [key, item] of Object.entries(map)) {
730
776
  if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(key))
731
777
  fail(`${location} has invalid key ${key}`);
778
+ if (allowNonScalar)
779
+ continue;
732
780
  if (item === null && allowNull)
733
781
  continue;
734
782
  if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
@@ -6,7 +6,6 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { parseBuiltinCommandAction } from "../../commands/command/builtin-action.js";
8
8
  import { validatePortableCommandTemplate } from "../../commands/command/portable-template.js";
9
- import { bundleRefToString, parseBundleRef } from "../../core/asset/asset-ref.js";
10
9
  import { parseSchedule } from "../../tasks/schedule.js";
11
10
  import { classifyWorkflowSourceUses } from "./uses.js";
12
11
  const TOKEN_SAFE_RUN = /^[A-Za-z0-9_./:@+=,-]+(?: [A-Za-z0-9_./:@+=,-]+)*$/;
@@ -83,9 +82,6 @@ export function classifyWorkflowStepUses(value, classifier = classifyWorkflowSou
83
82
  if (value.length === 0 || value.trim() !== value || /\s/.test(value)) {
84
83
  throw new WorkflowSourceSemanticError("unsupported-uses-target", "uses must be one exact, non-empty executable ref");
85
84
  }
86
- const task = canonicalTaskTarget(value);
87
- if (task)
88
- return task;
89
85
  let target;
90
86
  try {
91
87
  target = classifier(value);
@@ -93,29 +89,15 @@ export function classifyWorkflowStepUses(value, classifier = classifyWorkflowSou
93
89
  catch (cause) {
94
90
  throw usesFailure(value, cause);
95
91
  }
96
- if (target.kind === "github-action") {
97
- throw new WorkflowSourceSemanticError("remote-action-acquisition-out-of-scope", `Remote action acquisition is out of scope for ${JSON.stringify(value)}.`);
98
- }
99
- if (target.kind === "workflow") {
100
- throw new WorkflowSourceSemanticError("nested-workflow-unsupported", `Nested workflow target ${JSON.stringify(value)} is unsupported in a workflow step.`);
101
- }
92
+ // P3a (docs/plans/specs/p3a-plan-v5-child-freeze.md §1.3(2)/§4, A-N4): a
93
+ // `kind: "workflow"` target used to throw `nested-workflow-unsupported`
94
+ // here. That rejection is REMOVED — classification returns the workflow
95
+ // target like any other target-ref-shaped `uses:`, and freeze decides
96
+ // (`src/workflows/freeze/targets/child-workflow.ts`, the ONE recursive
97
+ // child-workflow resolver both the direct and task-wrapped composition
98
+ // forms route through).
102
99
  return target;
103
100
  }
104
- function canonicalTaskTarget(value) {
105
- try {
106
- const parsed = parseBundleRef(value);
107
- if (parsed.fragment !== undefined || bundleRefToString(parsed) !== value)
108
- return undefined;
109
- const slash = parsed.conceptId.indexOf("/");
110
- if (slash < 0 || parsed.conceptId.slice(0, slash) !== "tasks" || parsed.conceptId.length === slash + 1) {
111
- return undefined;
112
- }
113
- return { kind: "task", ref: value };
114
- }
115
- catch {
116
- return undefined;
117
- }
118
- }
119
101
  /**
120
102
  * Validate AKM's built-in command action at the shared source/decoder boundary.
121
103
  *
@@ -0,0 +1,79 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { asRecord, checkKeys, cloneBoundedJson, noGithubExpression, own, presentJsonValue, sourceError, stringField, TASK_V3_MAX_SCHEDULES, } from "../../tasks/source/bounded-document.js";
5
+ const SOURCE_LABEL = "task v3 source";
6
+ function ctxFrom(options) {
7
+ return {
8
+ filePath: options.filePath,
9
+ sourceLabel: SOURCE_LABEL,
10
+ ...(options.lineAt ? { lineAt: options.lineAt } : {}),
11
+ };
12
+ }
13
+ const ON_KEYS = ["schedule", "workflow_dispatch"];
14
+ function parseOn(value, ctx) {
15
+ const input = asRecord(value, ctx, ["on"]);
16
+ const keys = Object.keys(input);
17
+ if (keys.length === 0)
18
+ sourceError(ctx, ["on"], "must declare schedule and/or workflow_dispatch.");
19
+ const unsupported = keys.find((key) => !ON_KEYS.includes(key));
20
+ if (unsupported)
21
+ sourceError(ctx, ["on", unsupported], "is an unsupported local service event; no scheduler binding was created.");
22
+ const schedules = [];
23
+ if (own(input, "schedule")) {
24
+ if (!Array.isArray(input.schedule) || input.schedule.length === 0) {
25
+ sourceError(ctx, ["on", "schedule"], "must be a non-empty list of {cron: string} records.");
26
+ }
27
+ if (input.schedule.length > TASK_V3_MAX_SCHEDULES) {
28
+ sourceError(ctx, ["on", "schedule"], `accepts at most ${TASK_V3_MAX_SCHEDULES} entries.`);
29
+ }
30
+ for (const [index, raw] of input.schedule.entries()) {
31
+ const entry = asRecord(raw, ctx, ["on", "schedule", index]);
32
+ checkKeys(entry, ["cron"], ctx, ["on", "schedule", index]);
33
+ if (!own(entry, "cron"))
34
+ sourceError(ctx, ["on", "schedule", index, "cron"], "is required.");
35
+ const cron = stringField(entry.cron, ctx, ["on", "schedule", index, "cron"], { nonempty: true });
36
+ noGithubExpression(cron, ctx, ["on", "schedule", index, "cron"]);
37
+ schedules.push(Object.freeze({ cron, source: `on.schedule[${index}].cron`, ordinal: index }));
38
+ }
39
+ }
40
+ let manual = false;
41
+ if (own(input, "workflow_dispatch")) {
42
+ const dispatch = input.workflow_dispatch;
43
+ if (dispatch !== null) {
44
+ const mapping = asRecord(presentJsonValue(dispatch, ctx, ["on", "workflow_dispatch"]), ctx, [
45
+ "on",
46
+ "workflow_dispatch",
47
+ ]);
48
+ if (Object.keys(mapping).length > 0) {
49
+ sourceError(ctx, ["on", "workflow_dispatch"], "must be null or an empty mapping; inputs are unsupported.");
50
+ }
51
+ }
52
+ manual = true;
53
+ }
54
+ return Object.freeze({ manual, schedules: Object.freeze(schedules) });
55
+ }
56
+ /**
57
+ * `on:` is the one canonical scheduling source a workflow YAML trigger
58
+ * fragment has a live caller for (see file header — the task-v3 `akm:`
59
+ * options bag this used to also accept is deleted, along with the
60
+ * "exactly one scheduling source" choice between the two).
61
+ */
62
+ function compileTriggers(input, ctx) {
63
+ if (!own(input, "on"))
64
+ sourceError(ctx, ["on"], "is required.");
65
+ return parseOn(presentJsonValue(input.on, ctx, ["on"]), ctx);
66
+ }
67
+ /**
68
+ * Classify the strict trigger fragment `{on}` into deterministic local
69
+ * scheduler bindings. Full workflow adapters pass only that one field; this
70
+ * rejects `jobs` and every other workflow field (and, since the review fix
71
+ * above, `akm` too) rather than owning the document grammar.
72
+ */
73
+ export function classifyWorkflowYamlTriggers(value, options) {
74
+ const ctx = ctxFrom(options);
75
+ const cloned = cloneBoundedJson(value, ctx, [], { nodes: 0 });
76
+ const input = asRecord(cloned, ctx, []);
77
+ checkKeys(input, ["on"], ctx, []);
78
+ return compileTriggers(input, ctx);
79
+ }
@@ -2,13 +2,39 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * Workflow source adapters deliberately do not own an executable-ref grammar.
6
- * `tasks/...` is the one workflow-only target and is recognized by
7
- * `classifyWorkflowStepUses`; every remaining target delegates here to WP6's
8
- * canonical task-v3 classifier.
5
+ * The workflow `uses:` classification seam (P1a Lane B,
6
+ * docs/plans/specs/p1a-with-rejection-classifier.md §4.2).
7
+ *
8
+ * `classifyWorkflowSourceUses` is the canonical non-task `uses:` classifier
9
+ * used by direct source-IR decoding (and, via `compile.ts`, by the GitHub-YAML
10
+ * entrypoint): it recognizes the `akm/command` builtin special case and
11
+ * otherwise delegates to `classifyTargetRef` (src/execution/target-ref.ts),
12
+ * the canonical classifier for `commands/`, `scripts/`, `tasks/`, and
13
+ * `workflows/` asset refs — including `tasks/...` targets, for which
14
+ * `classifyTargetRef`'s own `tasks/` arm is the one authority (brief §8.1;
15
+ * P4 deleted `classifyWorkflowStepUses`'s `canonicalTaskTarget` pre-check in
16
+ * semantics.ts, which used to match them first).
17
+ *
18
+ * This module imports NOTHING from `src/tasks/source-v3.ts`: workflow `uses:`
19
+ * classification no longer delegates to the task-v3 grammar, and (P4) native
20
+ * target classification recognizes no GitHub Action variant at all —
21
+ * `WorkflowSourceUsesTarget` below has no `github-action` member, typed or
22
+ * otherwise.
23
+ */
24
+ import { classifyTargetRef } from "../../execution/target-ref.js";
25
+ /**
26
+ * Canonical non-task classifier used by direct source-IR decoding (and the
27
+ * GitHub-YAML entrypoint's default, wired via compile.ts). Layers the
28
+ * `akm/command` builtin special case over `classifyTargetRef`, which throws
29
+ * `UsageError` `TARGET_REF_INVALID` for anything else.
9
30
  */
10
- import { classifyTaskV3Uses } from "../../tasks/source-v3.js";
11
- /** Canonical non-task classifier used by direct source-IR decoding. */
12
31
  export function classifyWorkflowSourceUses(value) {
13
- return classifyTaskV3Uses(value);
32
+ if (value === "akm/command") {
33
+ return Object.freeze({ kind: "builtin-command", ref: "akm/command" });
34
+ }
35
+ // classifyTargetRef's return type is structurally identical to this
36
+ // module's `{ kind: "command" | "script" | "task" | "workflow"; ref:
37
+ // string }` union arm, so it is assignable to WorkflowSourceUsesTarget
38
+ // without a cast.
39
+ return classifyTargetRef(value);
14
40
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Upgrade guides and per-release migration notes.
4
4
 
5
- - [v0.9.1 -> v0.9.2 migration guide](v0.9.1-to-v0.9.2.md) -- Task-v2 to task-v3 conversion, the single durable-v4 workflow boundary, and release behavior changes
5
+ - [v0.9.1 -> v0.9.2 migration guide](v0.9.1-to-v0.9.2.md) -- Task-v2/task-v3 to task source v4 conversion, the durable-v4-family workflow boundary at executable `irVersion: 5`, and release behavior changes
6
6
  - [v0.9.2 release note](release-notes/0.9.2.md) -- Self-contained terminal upgrade summary shipped for `akm help migrate 0.9.2`
7
7
  - [v0.8 -> current v0.9 migration guide](v0.8-to-v0.9.md) -- Package upgrade with fresh current config/state and explicit task conversion
8
8
  - [v0.7 -> v0.8 migration guide](v0.7-to-v0.8.md) -- Task schema and 0.8-era changes
@@ -21,19 +21,95 @@ the recovery snapshot and migration 002's rebuild. Snapshot readers and targets
21
21
  are bound to their held inodes, not swappable pathnames. A failed snapshot path
22
22
  is reported and never removed by cleanup code.
23
23
 
24
- Task sources now use Task v3. Normal execution rejects task-v2 files; preview
25
- the repository-wide conversion with `akm migrate apply --dry-run`, review every
26
- `changed`, `skipped`, or `blocked` result, then run `akm migrate apply`. The
27
- migrator validates the replacement before it backs up and replaces a source,
28
- and ambiguous argv arrays remain blocked for manual review.
24
+ Task sources now use task source v4. Normal execution rejects task-v3 and
25
+ task-v2 files with `TASK_SCHEMA_VERSION_UNSUPPORTED`; preview the
26
+ repository-wide conversion with `akm migrate apply --dry-run`, review every
27
+ `changed`, `skipped`, or `blocked` result, then run `akm migrate apply` it
28
+ now runs both the v2→v3 and v3→v4 generations in one pass. The migrator
29
+ validates each replacement before it backs up and replaces a source, and
30
+ ambiguous cases (an argv array with no safe shell equivalent, a `with:`
31
+ block with no v4 equivalent, an ambiguous scheduling source, and similar)
32
+ remain blocked for manual review. Task source v4 adds typed `inputs:` with
33
+ defaults, a single bounded `output:` schema, optional `schedule:`, and
34
+ per-schedule-binding enablement. See
35
+ [Task sources](../v0.9.1-to-v0.9.2.md#task-sources) and
36
+ [The migration procedure](../v0.9.1-to-v0.9.2.md#the-migration-procedure).
37
+
38
+ Task run history's `target.kind` result field also changed vocabulary:
39
+ `"prompt"` is now `"command"` (an agent/LLM dispatch), and the old shared
40
+ `"command"` split into `"shell"` and `"script"`. `akm task history` reads
41
+ both generations of existing rows correctly, forever, keyed on a per-row
42
+ `targetVocab` marker — but a pre-0.9.2 akm's decoder does not recognize
43
+ that marker and **throws** reading a row this release (or later) wrote, so
44
+ upgrade once and don't downgrade below 0.9.2 for a given `state.db`. See
45
+ [Task history result vocabulary](../v0.9.1-to-v0.9.2.md#task-history-result-vocabulary-targetkind).
29
46
 
30
47
  Workflows now accept peer Markdown (`.md`) and GitHub-shaped YAML (`.yml`)
31
- sources through source IR v1. Runs freeze durable plan v4, the only executable
32
- plan format. Pre-v4 stored plans are rejected; start a new run from current
33
- source. A v4 resume does not re-read the authored workflow, configuration, or
34
- asset index. Scheduled fires are new starts, so they read the current source
35
- and create a fresh v4 freeze. V4 also rejects `inherit_env`; use exact named
36
- environment bindings and `pass_env` names instead.
48
+ sources through source IR v1. Runs freeze durable plan `irVersion` 5, the
49
+ only executable plan format. Pre-`irVersion`-5 stored plans cannot resume,
50
+ next, complete, or run `status`, `list`, and `abandon` keep working, and
51
+ `akm workflow abandon <id>` followed by a fresh `akm workflow run <ref>`
52
+ recovers a blocked run; no data is lost. A resume does not re-read the
53
+ authored workflow, configuration, or asset index. Scheduled fires are new
54
+ starts, so they read the current source and create a fresh freeze. `.yml`
55
+ also rejects `inherit_env`; use exact named environment bindings and
56
+ `pass_env` names instead. See
57
+ [Workflow cutover](../v0.9.1-to-v0.9.2.md#workflow-cutover).
58
+
59
+ A workflow step can now compose another workflow as a child — directly
60
+ (`uses: workflows/<ref>`) or through a task whose own target is a workflow
61
+ — bounded by composition depth, cycle detection, and aggregate embedded-plan
62
+ size, all checked at freeze before the parent run is published. Running the
63
+ step drives the child to completion inline, in the parent's own process:
64
+ cancellation propagates because it is the same process, the child is
65
+ independently resumable, and `akm workflow status` on the parent renders a
66
+ `children:` tree. A workflow may also declare `outputs:` — a run-level
67
+ export resolved once from persisted step evidence at completion, which a
68
+ composing parent step promotes as its own output. See
69
+ [Child workflows](../v0.9.1-to-v0.9.2.md#child-workflows) and
70
+ [Workflow outputs](../v0.9.1-to-v0.9.2.md#workflow-outputs).
71
+
72
+ A workflow step's `with:` on a `uses: tasks/<ref>` target now **binds** the
73
+ task's declared `inputs:` — literal values or `{from: "steps.<id>.output…"}`
74
+ references resolved just before dispatch (the reference grammar also
75
+ accepts `{from: "params.<name>"}`, but a composing step's own document can
76
+ never declare `params:`, so that form is not reachable in this release) —
77
+ instead of being silently dropped. A `with:` on a task with no `inputs:`,
78
+ or on a `commands/`/`scripts/` target (never binding surfaces), is now
79
+ rejected at freeze rather than discarded. See
80
+ [`with:` on a task-composed step now binds — or rejects](../v0.9.1-to-v0.9.2.md#with-on-a-task-composed-step-now-binds--or-rejects).
81
+
82
+ Three recognized-but-limited constructs are removed outright in 0.9.2: the
83
+ GitHub Action `uses:` locator (`owner/repo[/path]@ref`) is no longer
84
+ recognized anywhere — it was always rejected before dispatch in every prior
85
+ release, so this deletes the recognition, not a working capability;
86
+ multi-job GitHub-shaped YAML is rejected at the source adapter instead of
87
+ parsing clean and being refused later in two different places — split a
88
+ multi-job document into single-job workflows composed with a child-workflow
89
+ step; and the second task scheduling syntax (`akm.schedule` / a task's
90
+ top-level `on:`) is gone along with task v3, leaving task source v4's
91
+ optional top-level `schedule:` as the one canonical form. See
92
+ [GitHub Action locators are no longer recognized anywhere](../v0.9.1-to-v0.9.2.md#github-action-locators-are-no-longer-recognized-anywhere)
93
+ and
94
+ [Multi-job YAML is rejected at the adapter boundary](../v0.9.1-to-v0.9.2.md#multi-job-yaml-is-rejected-at-the-adapter-boundary).
95
+
96
+ Two new read-only introspection verbs: `akm workflow plan <ref>`
97
+ (secret-free by construction) compiles, resolves, and freezes a workflow
98
+ without publishing a run, printing the canonical step graph, target/child
99
+ expansion, and input bindings; `akm task explain <ref>` prints a task's
100
+ resolved target, declared and supplied `inputs:` (secret-shaped values
101
+ redacted on a best-effort heuristic basis), and schedule bindings — its
102
+ default and `--format json` output are the same raw JSON. See
103
+ [New commands](../v0.9.1-to-v0.9.2.md#new-commands).
104
+
105
+ Task-source, workflow-source, and composition failures now report
106
+ phase-specific `UsageError` codes (`TASK_SOURCE_INVALID`,
107
+ `TASK_SCHEMA_VERSION_UNSUPPORTED`, `TARGET_REF_INVALID`,
108
+ `WORKFLOW_SOURCE_INVALID`, `COMPOSITION_INVALID`, `INPUT_BINDING_INVALID`,
109
+ `TASK_TARGET_UNSUPPORTED`, `WORKFLOW_OUTPUT_INVALID`,
110
+ `WORKFLOW_IR_VERSION_UNSUPPORTED`) instead of the generic
111
+ `INVALID_FLAG_VALUE`; exit codes are unchanged. See
112
+ [Diagnostics](../v0.9.1-to-v0.9.2.md#diagnostics).
37
113
 
38
114
  `akm command run --dry-run` performs authorization and adapter lowering but
39
115
  does not dispatch or materialize credentials. It writes no authored source or
@@ -7,8 +7,9 @@ live one level up in `docs/migration/`.
7
7
 
8
8
  ## Available notes
9
9
 
10
- - [0.9.2](0.9.2.md) — task v3, workflow source IR v1 and durable v4,
11
- command diagnostics, and strategy judgment migration
10
+ - [0.9.2](0.9.2.md) — task source v4 migration, workflow source IR v1 and
11
+ durable-v4-family `irVersion: 5`, command diagnostics, and strategy judgment
12
+ migration
12
13
 
13
14
  ## Adding notes for a new release
14
15
 
@@ -7,8 +7,8 @@ in place is not.
7
7
  ## What the upgrade preserves
8
8
 
9
9
  - authored assets that you copy into a current bundle;
10
- - task-v2 source files that the explicit task migrator can translate without
11
- guessing;
10
+ - task-v2 source files that the explicit task migrator can translate — through
11
+ task-v3 and on to task source v4 — without guessing;
12
12
  - package-manager or standalone-binary updates through `akm upgrade`.
13
13
 
14
14
  ## What it does not preserve
@@ -17,7 +17,7 @@ in place is not.
17
17
  - old `index.db`, `workflow.db`, task-history JSONL, or legacy lock/cache
18
18
  layouts;
19
19
  - old ref grammar or old workflow/task execution paths;
20
- - in-flight pre-v4 workflow plans.
20
+ - in-flight workflow plans older than `irVersion: 5`.
21
21
 
22
22
  Those formats are not compatibility inputs to the current runtime. Keep an
23
23
  archive if you need historical inspection; do not place it in the live 0.9
@@ -70,16 +70,18 @@ for 0.8 databases.
70
70
 
71
71
  ### 4. Convert task-v2 sources explicitly
72
72
 
73
- Normal task execution accepts task v3 only. Preview every translation:
73
+ Normal task execution accepts task source v4 only. Preview every translation:
74
74
 
75
75
  ```sh
76
76
  akm migrate status
77
77
  akm migrate apply --dry-run
78
78
  ```
79
79
 
80
- Review each `changed`, `skipped`, and `blocked` entry. The migrator blocks
81
- ambiguous argv arrays or any conversion whose execution meaning is not
82
- provable. Rewrite blocked files manually as task v3.
80
+ Review each `changed`, `skipped`, and `blocked` entry. The migrator runs both
81
+ generations in one pass task-v2 to task-v3, then task-v3 to task source
82
+ v4 against the resulting files. It blocks ambiguous argv arrays or any
83
+ conversion whose execution meaning is not provable. Rewrite blocked files
84
+ manually as task source v4.
83
85
 
84
86
  Apply only after the preview is correct:
85
87
 
@@ -106,9 +108,9 @@ existing scheduler entry.
106
108
  ## Workflow boundary
107
109
 
108
110
  Current Markdown and GitHub-shaped YAML workflows compile to the same source
109
- IR and freeze durable plan IR v4. Durable v4 is the only executable stored
110
- plan. Do not copy an old workflow database expecting old runs to resume; start
111
- new runs from current authored sources.
111
+ IR and freeze the durable plan v4 family's executable `irVersion: 5` format.
112
+ That is the only executable stored plan. Do not copy an old workflow database
113
+ expecting old runs to resume; start new runs from current authored sources.
112
114
 
113
115
  ## Recovery
114
116
 
@@ -117,6 +119,6 @@ and restore the archived 0.8 installation with its matching 0.8 executable.
117
119
  Do not mix old executable code with current state or current executable code
118
120
  with old state.
119
121
 
120
- For the task-v3 format and the narrower 0.9.1-to-0.9.2 transition, see
122
+ For the task source v4 format and the narrower 0.9.1-to-0.9.2 transition, see
121
123
  [Tasks](../reference/tasks.md) and
122
124
  [Migrating from 0.9.1 to 0.9.2](v0.9.1-to-v0.9.2.md).
@@ -83,32 +83,39 @@ akm migrate status
83
83
  akm migrate apply --dry-run
84
84
  ```
85
85
 
86
- A blocked file is intentionally unchanged. Common causes are argv arrays,
87
- shell-sensitive command forms, invalid YAML, unsupported fields, or a source
88
- that cannot be proven writable. Rewrite that file manually as task v3 and
89
- preview again.
86
+ The migrator runs two generations in one pass task-v2 to task-v3, then
87
+ task-v3 to task source v4 against the resulting files — and either
88
+ generation can block a file. A blocked file is intentionally unchanged.
89
+ Common causes at the v2-to-v3 stage are argv arrays, shell-sensitive command
90
+ forms, invalid YAML, unsupported fields, or a source that cannot be proven
91
+ writable; rewrite that file manually as task v3 and preview again. Common
92
+ causes at the v3-to-v4 stage are a GitHub Action `uses:` locator (no v4
93
+ equivalent) or a `with:` block on a non-command target (v4 wants declared
94
+ `inputs:` instead); rewrite that file manually as task source v4 and preview
95
+ again.
90
96
 
91
97
  The task migrator does not repair config or databases.
92
98
 
93
99
  ## Task migration was interrupted
94
100
 
95
101
  The migrator validates and backs up each changed task immediately before its
96
- atomic replacement. Re-run the preview. Already-current v3 files are skipped;
97
- remaining v2 files are planned again from their current bytes. A changed input
98
- generation fails closed instead of applying a stale plan.
102
+ atomic replacement. Re-run the preview. Already-current task source v4 files
103
+ are skipped; remaining v2 and v3 files are planned again from their current
104
+ bytes. A changed input generation fails closed instead of applying a stale
105
+ plan.
99
106
 
100
107
  Use the per-file backup only to reverse that file deliberately. Do not copy a
101
108
  backup over a file while a task sync or scheduler process is running.
102
109
 
103
110
  ## A workflow will not resume
104
111
 
105
- Only durable plan IR v4 executes. Pre-v4 stored plans are rejected rather than
106
- decoded by a compatibility runtime. Start a new run from the current Markdown
107
- or YAML workflow source.
112
+ Only the durable plan v4 family's `irVersion: 5` executes. Pre-`irVersion`-5
113
+ stored plans are rejected rather than decoded by a compatibility runtime.
114
+ Start a new run from the current Markdown or YAML workflow source.
108
115
 
109
- For a v4 run, a missing or changed authored source is not a resume blocker: the
110
- run uses its frozen plan. A plan-hash or schema failure is durable-state
111
- corruption and must fail closed.
116
+ For an `irVersion: 5` run, a missing or changed authored source is not a resume
117
+ blocker: the run uses its frozen plan. A plan-hash or schema failure is
118
+ durable-state corruption and must fail closed.
112
119
 
113
120
  ## A stale transaction journal is reported
114
121