akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -4,7 +4,7 @@
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { parseBundleRef } from "../../core/asset/asset-ref.js";
6
6
  import { loadConfig } from "../../core/config/config.js";
7
- import { ConfigError, NotFoundError, TransientError, UsageError } from "../../core/errors.js";
7
+ import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
8
8
  import { appendEvent } from "../../core/events.js";
9
9
  import { warn } from "../../core/warn.js";
10
10
  import { insertEventOnce } from "../../storage/repositories/events-repository.js";
@@ -12,28 +12,25 @@ import { withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-
12
12
  import { getCurrentWorkflowScopeKey } from "../authoring/scope-key.js";
13
13
  import { frozenSummaryJudge } from "../exec/frozen-judge.js";
14
14
  import { detectSecretShapedParams } from "../exec/param-secrets.js";
15
- import { collectWorkflowWarnings } from "../ir/compile.js";
16
- import { compileResolveFreezeWorkflowV4 } from "../ir/freeze-v4.js";
15
+ import { freezeWorkflow } from "../freeze/freeze.js";
17
16
  import { materializeWorkflowParameterFlags, validateWorkflowParams } from "../ir/params.js";
18
17
  import { canonicalPlanJson, computePlanHash } from "../ir/plan-hash.js";
19
18
  import { clip, WORKFLOW_UNIT_DIAGNOSTIC_CLIP } from "../resource-limits.js";
20
19
  import { validateStepSummary } from "../validate-summary.js";
21
20
  import { resolveAgentIdentity } from "./agent-identity.js";
22
- import { evaluateCheckin } from "./checkin.js";
23
- import { assertRunStatusMatchesSpine, classifyWorkflowRunPlan, frozenStepRows, reconcileWorkflowSpineWithPlan, requireExecutableWorkflowPlan, } from "./plan-classifier.js";
24
21
  import { resolveWorkflowRunOutputs } from "./run-outputs.js";
25
- import { evaluateStaleUnits } from "./unit-checkin.js";
22
+ import { frozenStepRows, readRunPlan } from "./run-plan.js";
26
23
  import { canonicalizeWorkflowRefInput, loadWorkflowAsset, resolveWorkflowEntryId } from "./workflow-asset-loader.js";
27
24
  /**
28
25
  * Membership test for the journaled `runner` column, which is an untyped string.
29
- * The `Record<IrRuntimeKind, …>` is exhaustiveness-checked, so a new runtime
26
+ * The `Record<WorkflowRuntimeKind, …>` is exhaustiveness-checked, so a new runtime
30
27
  * kind cannot be added to the union without being accepted here too.
31
28
  */
32
29
  const IR_RUNTIME_KINDS = { llm: true, agent: true, sdk: true, exec: true };
33
30
  function runtimeKindOf(runner) {
34
31
  return runner !== null && Object.hasOwn(IR_RUNTIME_KINDS, runner) ? runner : null;
35
32
  }
36
- function toUnitDiagnostic(row, stale) {
33
+ function toUnitDiagnostic(row) {
37
34
  let diagnostic = null;
38
35
  if (row.result_json !== null) {
39
36
  // `result_json` is a JSON-encoded value: a bare JSON string for a free-text
@@ -63,10 +60,6 @@ function toUnitDiagnostic(row, stale) {
63
60
  diagnostic,
64
61
  startedAt: row.started_at,
65
62
  finishedAt: row.finished_at,
66
- stale: stale !== undefined,
67
- staleIdleMs: stale ? (Number.isFinite(stale.idleMs) ? stale.idleMs : null) : null,
68
- claimHolder: row.claim_holder,
69
- claimExpiresAt: row.claim_expires_at,
70
63
  engine: row.engine ?? null,
71
64
  runtimeKind: runtimeKindOf(row.runner),
72
65
  platform: null,
@@ -74,11 +67,11 @@ function toUnitDiagnostic(row, stale) {
74
67
  }
75
68
  export async function startWorkflowRun(ref, params = {}, options) {
76
69
  const asset = await loadWorkflowAsset(ref);
77
- // Frozen plan (redesign addendum, R1): compile the plan ONCE at start and
70
+ // Frozen plan: compile the plan ONCE at start and
78
71
  // persist it on the run row in the same transaction as the insert. Every
79
72
  // later invocation executes this snapshot — the asset file is never re-read
80
73
  // for an in-flight run; re-planning is an explicit new run.
81
- const frozen = await compileResolveFreezeWorkflowV4(asset, loadConfig());
74
+ const frozen = await freezeWorkflow(asset, loadConfig());
82
75
  const plan = frozen.plan;
83
76
  if (options?.parameterFlags?.length && Object.keys(params).length > 0) {
84
77
  throw new UsageError("Workflow parameters must use either an object or per-parameter flags, not both.");
@@ -89,10 +82,10 @@ export async function startWorkflowRun(ref, params = {}, options) {
89
82
  // Non-fatal WARNINGS: untyped-step and undeclared-param advisories surface
90
83
  // as `warn()` lines at start (stderr, consistent with the repo's other
91
84
  // author-facing warnings) without blocking the run.
92
- for (const w of collectWorkflowWarnings(asset.sourceIr)) {
85
+ for (const w of frozen.warnings) {
93
86
  warn(`workflow run: ${asset.path}:${w.line} — ${w.message}`);
94
87
  }
95
- // Reviewer #12: validate supplied parameters against the frozen param
88
+ // validate supplied parameters against the frozen param
96
89
  // schemas BEFORE creating the run, so a type-mismatched param (e.g. a string
97
90
  // for a `{ type: array }` param) is rejected with actionable errors instead
98
91
  // of flowing silently into a unit prompt. Programs without declared param
@@ -121,24 +114,10 @@ export async function startWorkflowRun(ref, params = {}, options) {
121
114
  // (workflow_ref, scope_key) pair, refuse to create a parallel run unless
122
115
  // `force: true` is set. Previously every call inserted unconditionally,
123
116
  // so two terminals starting the same workflow could leave two runs racing.
124
- // The
125
- // active-alias query and all inserts now share this immediate transaction.
126
- // #506: arm a file-signal check-in (a timestamp, NOT a background thread —
127
- // per the workflow-agent check-in ADR) so a stalled run can be
128
- // re-targeted with a `continue` directive. The agent harness + session id
129
- // are already resolved above (agentHarness/agentSessionId, from #501).
130
- // #942: an active run of this ref may already exist in a DIFFERENT
131
- // scope — the incident this issue reports (a scheduled task's cwd and a
132
- // human's shell hash to different scope keys, so each believed it held
133
- // no active run and each started one). The scope-local uniqueness guard
134
- // stays scope-local (a documented, deliberate per-project partition —
135
- // see storage-locations.md); this only warns, once, so the operator can
136
- // resume or abandon the other run instead of silently accumulating a
137
- // second one. `findActiveRunOutsideScope` excludes the caller's own
138
- // scope IN SQL (never merely post-filtered) so the caller's own active
139
- // run can never sort first under `LIMIT 1` and mask a genuinely different
140
- // scope's run — the failure mode a same-scope-inclusive query plus a
141
- // post-filter has with `--new`/`--force`.
117
+ // The active-alias query and all inserts share one immediate transaction.
118
+ // #942: an active run of this ref in a different scope (e.g. a scheduled
119
+ // task's cwd vs. a shell) is warned about once; the uniqueness guard stays
120
+ // scope-local. The query excludes the caller's own scope in SQL.
142
121
  const crossScopeActive = repo.findActiveRunOutsideScope(workflowRefs, scopeKey);
143
122
  const crossScopeWarning = crossScopeActive
144
123
  ? `Workflow ${asset.ref} already has an active run in another scope ` +
@@ -161,7 +140,6 @@ export async function startWorkflowRun(ref, params = {}, options) {
161
140
  updatedAt: now,
162
141
  agentHarness,
163
142
  agentSessionId,
164
- checkinArmedAt: now,
165
143
  },
166
144
  steps: frozenStepRows(plan).map((step) => ({
167
145
  runId,
@@ -173,16 +151,14 @@ export async function startWorkflowRun(ref, params = {}, options) {
173
151
  })),
174
152
  planJson,
175
153
  planHash,
176
- revalidateSources: () => frozen.sourceCollector.revalidate(),
177
154
  });
178
155
  const result = await getWorkflowStatus(runId);
179
156
  if (crossScopeWarning)
180
157
  result.warnings = [...(result.warnings ?? []), crossScopeWarning];
181
- // #13: params are declared non-secret (they are copied verbatim into every
182
- // unit prompt and hashed into the unit identity, so they cannot be redacted
183
- // without breaking replay determinism). Surface a loud, best-effort warning
184
- // when a param LOOKS like a credential so the author moves it to an env
185
- // binding. Advisory only — never blocks the start.
158
+ // Params reach every unit prompt in clear (only the journal scrubs them).
159
+ // Surface a loud, best-effort warning when a param LOOKS like a credential
160
+ // so the author moves it to an env binding. Advisory only — never blocks
161
+ // the start.
186
162
  const secretWarnings = detectSecretShapedParams(effectiveParams);
187
163
  if (secretWarnings.length > 0)
188
164
  result.warnings = [...(result.warnings ?? []), ...secretWarnings];
@@ -203,12 +179,7 @@ export async function getWorkflowStatus(runId, opts) {
203
179
  // The honest diagnostic surface (#22): read the unit journal straight and
204
180
  // project each row, INCLUDING failures whose diagnostic text the
205
181
  // deterministic evidence graph drops. Read-only; never mutates the run.
206
- const rows = repo.getUnitsForRun(run.id);
207
- // Codex round-3 finding B: run the pure stale-claim evaluator (`now`
208
- // injected for deterministic tests) so a unit left `running` by a process
209
- // that died surfaces as stale here, not just as raw `running`.
210
- const staleById = new Map(evaluateStaleUnits(rows, opts.now ?? Date.now()).map((u) => [u.unitId, u]));
211
- detail.units = rows.map((row) => toUnitDiagnostic(row, staleById.get(row.unit_id)));
182
+ detail.units = repo.getUnitsForRun(run.id).map(toUnitDiagnostic);
212
183
  }
213
184
  return detail;
214
185
  });
@@ -271,9 +242,6 @@ export async function getNextWorkflowStep(specifier, params, options) {
271
242
  return withWorkflowRunsRepo(async (repo) => {
272
243
  const { run, autoStarted, resumed, startWarnings } = await resolveRunSpecifier(repo, specifier, params, options?.parameterFlags, options?.newRun);
273
244
  const steps = readWorkflowRunSteps(repo, run.id);
274
- const plan = requireExecutableWorkflowPlan(run);
275
- reconcileWorkflowSpineWithPlan(plan, run, steps);
276
- assertRunStatusMatchesSpine(run, steps);
277
245
  return {
278
246
  ...projectNextResult(run, steps),
279
247
  ...(autoStarted ? { autoStarted: true } : {}),
@@ -291,15 +259,6 @@ export async function getNextWorkflowStep(specifier, params, options) {
291
259
  function projectNextResult(run, steps) {
292
260
  const currentStep = resolveCurrentStep(run, steps);
293
261
  const done = run.status === "completed" ? true : undefined;
294
- // #506: surface a check-in directive through the normal command output when
295
- // the run looks stalled. Pure timestamp evaluation — no background thread.
296
- const checkin = evaluateCheckin({
297
- status: run.status,
298
- updatedAt: run.updated_at,
299
- checkinArmedAt: run.checkin_armed_at,
300
- agentHarness: run.agent_harness,
301
- agentSessionId: run.agent_session_id,
302
- }) ?? undefined;
303
262
  return {
304
263
  run: toWorkflowRunSummary(run),
305
264
  workflow: {
@@ -309,20 +268,16 @@ function projectNextResult(run, steps) {
309
268
  },
310
269
  step: currentStep ? toWorkflowRunStepState(currentStep) : null,
311
270
  ...(done ? { done } : {}),
312
- ...(checkin ? { checkin } : {}),
313
271
  };
314
272
  }
315
273
  export async function resumeWorkflowRun(runId) {
316
274
  return withWorkflowRunsRepo((repo) => {
317
275
  const run = readWorkflowRunOrPrefix(repo, runId);
318
- const storedPlan = requireExecutableWorkflowPlan(run);
319
276
  const steps = readWorkflowRunSteps(repo, run.id);
320
- reconcileWorkflowSpineWithPlan(storedPlan, run, steps);
321
277
  if (run.status === "completed") {
322
278
  throw new UsageError(`Workflow run ${run.id} is already completed and cannot be resumed.`);
323
279
  }
324
280
  if (run.status === "active") {
325
- assertRunStatusMatchesSpine(run, steps);
326
281
  return buildWorkflowRunDetail(repo, run, steps);
327
282
  }
328
283
  // blocked or failed → flip back to active and re-open the current step so
@@ -335,9 +290,7 @@ export async function resumeWorkflowRun(runId) {
335
290
  repo.markRunActive(run.id, now);
336
291
  });
337
292
  const updated = { ...run, status: "active", updated_at: now };
338
- const refreshedSteps = readWorkflowRunSteps(repo, run.id);
339
- assertRunStatusMatchesSpine(updated, refreshedSteps);
340
- return buildWorkflowRunDetail(repo, updated, refreshedSteps);
293
+ return buildWorkflowRunDetail(repo, updated, readWorkflowRunSteps(repo, run.id));
341
294
  });
342
295
  }
343
296
  /**
@@ -372,11 +325,8 @@ export async function abandonWorkflowRun(runId) {
372
325
  status: "failed",
373
326
  updated_at: now,
374
327
  completed_at: now,
375
- checkin_armed_at: now,
376
328
  };
377
- const steps = readWorkflowRunSteps(repo, run.id);
378
- const detail = buildWorkflowRunDetail(repo, updated, steps);
379
- return detail;
329
+ return buildWorkflowRunDetail(repo, updated, readWorkflowRunSteps(repo, run.id));
380
330
  });
381
331
  }
382
332
  export async function completeWorkflowStep(input) {
@@ -384,14 +334,10 @@ export async function completeWorkflowStep(input) {
384
334
  // the write transaction — a slow/hung LLM must never hold a db write lock.
385
335
  const preflight = await withWorkflowRunsRepo((repo) => {
386
336
  const run = readWorkflowRun(repo, input.runId);
387
- const storedPlan = requireExecutableWorkflowPlan(run);
388
- const steps = readWorkflowRunSteps(repo, run.id);
389
- reconcileWorkflowSpineWithPlan(storedPlan, run, steps);
390
- assertRunStatusMatchesSpine(run, steps);
337
+ const storedPlan = requireRunPlan(run);
391
338
  if (run.status !== "active") {
392
339
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
393
340
  }
394
- assertLeaseAllowsSpineAdvance(run, input.leaseHolder);
395
341
  const existing = repo.getStep(run.id, input.stepId);
396
342
  if (!existing) {
397
343
  throw new NotFoundError(`Step "${input.stepId}" was not found in workflow run ${run.id}.`);
@@ -433,11 +379,7 @@ export async function completeWorkflowStep(input) {
433
379
  }
434
380
  const verdict = await validateStepSummary({ stepTitle: preflight.stepPlan.title, completionCriteria: criteria, summary }, judge ?? undefined, input.signal);
435
381
  if (!verdict.complete) {
436
- // Re-arm the check-in so a subsequent stall is still nudged, but leave the
437
- // step pending and return corrective feedback instead of completing.
438
- await withWorkflowRunsRepo((repo) => {
439
- repo.rearmCheckin(input.runId, new Date().toISOString());
440
- });
382
+ // Leave the step pending and return corrective feedback instead of completing.
441
383
  return {
442
384
  ok: false,
443
385
  runId: input.runId,
@@ -455,17 +397,10 @@ export async function completeWorkflowStep(input) {
455
397
  let outputWarnings;
456
398
  repo.transaction(() => {
457
399
  const run = readWorkflowRun(repo, input.runId);
458
- const plan = requireExecutableWorkflowPlan(run);
459
- const spine = readWorkflowRunSteps(repo, run.id);
460
- reconcileWorkflowSpineWithPlan(plan, run, spine);
461
- assertRunStatusMatchesSpine(run, spine);
400
+ const plan = requireRunPlan(run);
462
401
  if (run.status !== "active") {
463
402
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
464
403
  }
465
- // Re-checked inside the write transaction (like every other preflight
466
- // condition): an engine may have claimed the run while the summary gate
467
- // above was awaiting its LLM judge.
468
- assertLeaseAllowsSpineAdvance(run, input.leaseHolder);
469
404
  const existing = repo.getStep(run.id, input.stepId);
470
405
  if (!existing) {
471
406
  throw new NotFoundError(`Step "${input.stepId}" was not found in workflow run ${run.id}.`);
@@ -479,13 +414,8 @@ export async function completeWorkflowStep(input) {
479
414
  if (input.signal?.aborted)
480
415
  throw interruptionReason(input.signal);
481
416
  const completedAt = new Date().toISOString();
482
- // The promoted artifact is persisted WHOLE, unclipped (issue C): a
483
- // step artifact that does not fit some cap used to be replaced by a
484
- // truncation marker at this exact write, and the run looked fine right
485
- // up until a LATER invocation (a resume, or any downstream step
486
- // referencing it) found the marker instead of the value and failed
487
- // permanently, with every prior paid step now unrecoverable. Persisting
488
- // the real value here is what makes it readable again on resume.
417
+ // The promoted artifact is persisted whole, so a resume and every later
418
+ // reference read the real value.
489
419
  const evidenceJson = input.evidence ? JSON.stringify(input.evidence) : null;
490
420
  repo.updateStepCompletion({
491
421
  status: input.status,
@@ -498,13 +428,8 @@ export async function completeWorkflowStep(input) {
498
428
  });
499
429
  refreshedSteps = readWorkflowRunSteps(repo, run.id);
500
430
  const state = deriveRunState(refreshedSteps);
501
- // P3b (spec §4.3, B-N13): resolve + persist declared outputs INSIDE
502
- // this same transaction, immediately after the run is known to have
503
- // COMPLETED. A resolution failure throws here, and the transaction
504
- // rolls back whole — the step completion included — so the observable
505
- // outcome is fail-before-mutation: the step stays pending, the run
506
- // stays active, and (since appendEvent runs outside this transaction)
507
- // no event is appended.
431
+ // Declared outputs resolve inside this transaction once the run
432
+ // completes; a failure rolls the step completion back whole.
508
433
  let outputsJson; // undefined = untouched, keep the row's existing value
509
434
  if (state.status === "completed" && plan.outputs) {
510
435
  const resolved = resolveWorkflowRunOutputs(plan, refreshedSteps);
@@ -512,14 +437,11 @@ export async function completeWorkflowStep(input) {
512
437
  repo.setRunOutputs(run.id, outputsJson);
513
438
  outputWarnings = resolved.errors;
514
439
  }
515
- // Re-arm the check-in on every state change: a healthy, progressing run
516
- // keeps pushing the stall window forward so the directive never fires.
517
440
  repo.updateRunState({
518
441
  status: state.status,
519
442
  currentStepId: state.currentStepId,
520
443
  updatedAt: completedAt,
521
444
  completedAt: state.completedAt,
522
- checkinArmedAt: completedAt,
523
445
  runId: run.id,
524
446
  });
525
447
  updatedRun = {
@@ -528,7 +450,6 @@ export async function completeWorkflowStep(input) {
528
450
  current_step_id: state.currentStepId,
529
451
  updated_at: completedAt,
530
452
  completed_at: state.completedAt,
531
- checkin_armed_at: completedAt,
532
453
  ...(outputsJson !== undefined ? { outputs_json: outputsJson } : {}),
533
454
  };
534
455
  });
@@ -663,48 +584,33 @@ function readWorkflowRunOrPrefix(repo, specifier) {
663
584
  const run = findRunByIdOrPrefix(repo, specifier);
664
585
  if (!run)
665
586
  throw new NotFoundError(`Workflow run "${specifier}" not found.`, "WORKFLOW_NOT_FOUND");
666
- return reclaimOrphanedEngineLease(repo, run);
587
+ return run;
667
588
  }
668
589
  function readWorkflowRun(repo, runId) {
669
590
  const run = repo.getRunById(runId);
670
591
  if (!run) {
671
592
  throw new NotFoundError(`Workflow run "${runId}" not found.`, "WORKFLOW_NOT_FOUND");
672
593
  }
673
- return reclaimOrphanedEngineLease(repo, run);
594
+ return run;
674
595
  }
675
596
  /**
676
- * Self-heal a run's engine lease once it has expired — the orphaned-lease
677
- * case where an engine crashed without releasing it — mirroring the
678
- * maintenance barrier's self-reclaim of a wedged sentinel, applied at the
679
- * points a caller actually asks "what is this run's state". Never touches a
680
- * live lease: {@link WorkflowRunsRepository.reclaimExpiredEngineLease} is a
681
- * compare-and-swap on the exact (holder, until) this call observed, so a
682
- * lease renewed or re-acquired between the read and this write is left alone.
597
+ * The frozen plan a step completion resolves its gate/outputs against. The
598
+ * engine has already read (or abandoned) the plan before it gets here; this
599
+ * throw only covers a direct caller completing a step of a run whose plan is
600
+ * unreadable.
683
601
  */
684
- function reclaimOrphanedEngineLease(repo, run) {
685
- const { engine_lease_holder: holder, engine_lease_until: until } = run;
686
- if (!holder || !until)
687
- return run;
688
- const now = new Date().toISOString();
689
- if (until >= now)
690
- return run;
691
- repo.reclaimExpiredEngineLease(run.id, holder, until, now);
692
- return { ...run, engine_lease_holder: null, engine_lease_until: null };
602
+ function requireRunPlan(run) {
603
+ const read = readRunPlan(run);
604
+ if (read.ok)
605
+ return read.plan;
606
+ throw new UsageError(read.newer
607
+ ? read.problem
608
+ : `${read.problem} Run 'akm workflow abandon ${run.id}' and start a new run with 'akm workflow run ${run.workflow_ref}'.`);
693
609
  }
694
610
  function readWorkflowRunSteps(repo, runId) {
695
611
  return repo.getStepsForRun(runId);
696
612
  }
697
613
  function buildWorkflowRunDetail(repo, run, steps) {
698
- // Review M1: `workflow status` (and every other detail-shaped response) now
699
- // evaluates the check-in, not just `workflow run`. Pure timestamp check —
700
- // no background thread (see checkin.ts).
701
- const checkin = evaluateCheckin({
702
- status: run.status,
703
- updatedAt: run.updated_at,
704
- checkinArmedAt: run.checkin_armed_at,
705
- agentHarness: run.agent_harness,
706
- agentSessionId: run.agent_session_id,
707
- });
708
614
  const children = childRunTree(repo, run.id, run.id);
709
615
  return {
710
616
  run: toWorkflowRunSummary(run),
@@ -713,39 +619,14 @@ function buildWorkflowRunDetail(repo, run, steps) {
713
619
  title: run.workflow_title,
714
620
  steps: steps.map(toWorkflowRunStepState),
715
621
  },
716
- ...(checkin ? { checkin } : {}),
717
622
  ...(children ? { children } : {}),
718
623
  };
719
624
  }
720
625
  /**
721
- * Build the parent-child status tree rooted at `rootRunId`, recursively, for
722
- * whatever run's children are being listed (`forRunId`) — P3b, spec §4.5.
723
- * `rootRunId` is threaded unchanged through the recursion, so every blocked
724
- * node's `resume.then` names the SAME top-of-query run regardless of nesting
725
- * depth: `akm workflow resume <rootRunId> && akm workflow run <rootRunId>` —
726
- * never each node's own immediate parent, which the tree's caller has no
727
- * command for.
728
- *
729
- * That command is sufficient to clear a block exactly ONE level deep (the
730
- * root's own composing step blocked directly on this node) but NOT deeper
731
- * (code-review round 4, finding 6 / Review log R6 — corrects a false claim
732
- * this comment used to make here). Re-driving the root does **not** cascade
733
- * back down through every intermediate composing step: `driveChildWorkflowUnit`
734
- * (child-workflow.ts) never re-drives a child whose OWN status is `blocked`
735
- * (row A-22) — no lease is even taken — so a re-drive just RE-OBSERVES the
736
- * still-blocked status and re-propagates the block upward (an intermediate
737
- * run is always blocked when a descendant is, row A-21, applied
738
- * recursively), never reaching the deepest blocked node. Clearing a
739
- * depth-2-or-deeper block requires resuming EVERY blocked run in the chain,
740
- * deepest first, then re-running only the root — see "Recovering a blocked
741
- * child" in docs/guides/run-workflows.md and "Blocked-child recovery" in
742
- * docs/reference/workflow-schema.md for the worked multi-level sequence.
743
- * `resume.then` is deliberately not widened to enumerate that chain (no
744
- * envelope change, no new field) — the docs carry the multi-level sequence
745
- * instead.
746
- *
747
- * Absent, never `[]`, when `forRunId` has no children (P3a's `childRunsOf`
748
- * order: `created_at, id`).
626
+ * The parent-child status tree for `forRunId`'s children, recursively. Every
627
+ * blocked node's `resume.then` names the top-of-query run (`rootRunId`); a
628
+ * block deeper than one level needs every blocked run resumed, deepest first
629
+ * (docs/guides/run-workflows.md). Absent, never `[]`, when there are no children.
749
630
  */
750
631
  function childRunTree(repo, rootRunId, forRunId) {
751
632
  const rows = repo.childRunsOf(forRunId);
@@ -755,7 +636,7 @@ function childRunTree(repo, rootRunId, forRunId) {
755
636
  }
756
637
  function toChildRunNode(repo, rootRunId, row) {
757
638
  const spawnedByUnitId = row.parent_unit_id ?? "";
758
- // B-36: the parent STEP that spawned it, resolved via the real journaled
639
+ // the parent STEP that spawned it, resolved via the real journaled
759
640
  // unit row — null when that unit row is gone.
760
641
  const stepId = row.parent_run_id && row.parent_unit_id
761
642
  ? (repo.getUnit(row.parent_run_id, row.parent_unit_id)?.step_id ?? null)
@@ -784,7 +665,6 @@ function toChildRunNode(repo, rootRunId, row) {
784
665
  };
785
666
  }
786
667
  function toWorkflowRunSummary(run) {
787
- const plan = classifyWorkflowRunPlan(run);
788
668
  return {
789
669
  id: run.id,
790
670
  workflowRef: run.workflow_ref,
@@ -799,47 +679,15 @@ function toWorkflowRunSummary(run) {
799
679
  params: parseJsonObject(run.params_json),
800
680
  agentHarness: run.agent_harness ?? null,
801
681
  agentSessionId: run.agent_session_id ?? null,
802
- planIrVersion: plan.irVersion,
803
- executionSupport: plan.support,
804
- // Surface the engine lease (holder id + expiry — never workflow-authored
805
- // content) so `workflow run`/`status` show which native execution
806
- // invocation currently holds the run lease. Gated on `until` still being
807
- // in the future: a crashed engine's lease self-expires, and a run
808
- // whose holder is provably gone must stop reading as engine-driven the
809
- // instant that happens, not just once something next attempts to acquire
810
- // it (`readWorkflowRun`/`readWorkflowRunOrPrefix` also reclaim the DB
811
- // columns outright on the same condition).
812
- ...(run.engine_lease_holder && run.engine_lease_until && run.engine_lease_until >= new Date().toISOString()
813
- ? { engineLease: { holder: run.engine_lease_holder, until: run.engine_lease_until } }
814
- : {}),
815
- // P3b (spec §4.5): all three optional and conditionally spread, so every
682
+ planIrVersion: run.plan_ir_version ?? null,
683
+ // P3b: all three optional and conditionally spread, so every
816
684
  // pre-existing (non-child, no-outputs-declared) run's envelope is
817
- // byte-identical (Stable tier, rows B-27, B-45).
685
+ // byte-identical.
818
686
  ...(run.outputs_json ? { outputs: parseJsonObject(run.outputs_json) ?? {} } : {}),
819
687
  ...(run.parent_run_id ? { parentRunId: run.parent_run_id } : {}),
820
688
  ...(run.parent_unit_id ? { spawnedByUnitId: run.parent_unit_id } : {}),
821
689
  };
822
690
  }
823
- /**
824
- * Single-driver enforcement (R2 run lease): while a LIVE (unexpired) engine
825
- * lease is held, only the holding engine may advance the gate spine. Manual
826
- * A call with no `leaseHolder` — or a stale engine invocation
827
- * whose lease was claimed by another — is refused with the holder + expiry.
828
- * An EXPIRED lease never blocks: the engine that held it is presumed dead.
829
- */
830
- function assertLeaseAllowsSpineAdvance(run, leaseHolder) {
831
- if (!run.engine_lease_holder || !run.engine_lease_until)
832
- return;
833
- if (leaseHolder === run.engine_lease_holder)
834
- return;
835
- if (run.engine_lease_until < new Date().toISOString())
836
- return; // expired ⇒ claimable, not live
837
- // #948 addendum: moved off UsageError (exit 75, not exit 2) — a held lease
838
- // is ordinary contention, not a bad command line.
839
- throw new TransientError(`Workflow run ${run.id} is being driven by engine ${run.engine_lease_holder} ` +
840
- `(run lease expires ${run.engine_lease_until}). The engine owns the step spine while it runs — ` +
841
- `wait for it to finish or for the lease to expire before advancing steps manually.`, "RUN_LEASE_HELD");
842
- }
843
691
  function toWorkflowRunStepState(step) {
844
692
  return {
845
693
  id: step.step_id,
@@ -1,6 +1,7 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
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
+ import { createHash } from "node:crypto";
4
5
  import fs from "node:fs";
5
6
  import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
6
7
  import { makeBundleRef, parseBundleRef } from "../../core/asset/asset-ref.js";
@@ -12,8 +13,7 @@ import { deriveInstallations } from "../../indexer/installations.js";
12
13
  import { resolveAdapterConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js";
13
14
  import { resolveSourceEntries } from "../../indexer/search/search-source.js";
14
15
  import { withIndexDb } from "../../storage/repositories/index-db.js";
15
- import { compileWorkflowSource } from "../source-ir/compile.js";
16
- import { sourceStepInstructions } from "../source-ir/program.js";
16
+ import { compileWorkflowSource } from "../compile.js";
17
17
  /**
18
18
  * Build the canonical `workflow_runs.workflow_ref` for an AKM workflow asset.
19
19
  */
@@ -36,7 +36,7 @@ export async function canonicalizeWorkflowRefInput(ref) {
36
36
  return (await loadWorkflowAsset(ref)).ref;
37
37
  }
38
38
  /**
39
- * Resolve a workflow ref and compile its authored bytes into source IR.
39
+ * Resolve a workflow ref and compile its authored bytes into a plan.
40
40
  */
41
41
  export async function loadWorkflowAsset(ref) {
42
42
  const bundleRef = parseWorkflowRefInput(ref);
@@ -91,8 +91,27 @@ export async function loadWorkflowAsset(ref) {
91
91
  const fullRef = workflowAdapterId === "akm-workflow"
92
92
  ? makeBundleRef(sourceBundleId, canonicalName)
93
93
  : canonicalWorkflowRunRef(sourceBundleId, canonicalName);
94
- const sourceIr = compileWorkflowSourceFromDisk(assetPath, resolvedSourcePath);
95
- return projectAsset(sourceIr, fullRef, assetPath, resolvedSourcePath, workflowAdapterId, canonicalName);
94
+ const title = canonicalName.split("/").pop() || canonicalName;
95
+ const bytes = fs.readFileSync(assetPath);
96
+ const result = compileWorkflowSource(bytes.toString("utf8"), {
97
+ path: assetPath,
98
+ workspaceRoot: resolvedSourcePath,
99
+ title,
100
+ });
101
+ if (!result.ok) {
102
+ const details = result.errors.map((error) => ` ${error.path}:${error.line} — ${error.message}`).join("\n");
103
+ const isMultiJob = result.errors.length === 1 && result.errors[0]?.code === "multi-job-unsupported";
104
+ throw new UsageError(`Workflow source has ${result.errors.length} error(s):\n${details}`, isMultiJob ? "COMPOSITION_INVALID" : "WORKFLOW_SOURCE_INVALID", isMultiJob ? COMPOSITION_INVALID_MULTI_JOB_HINT : undefined);
105
+ }
106
+ return {
107
+ ref: fullRef,
108
+ path: assetPath,
109
+ sourcePath: resolvedSourcePath,
110
+ adapterId: workflowAdapterId,
111
+ title,
112
+ plan: result.plan,
113
+ sourceHash: createHash("sha256").update(bytes).digest("hex"),
114
+ };
96
115
  }
97
116
  function ownsNativeWorkflowRuntime(source) {
98
117
  return source.adapterId === "akm" || source.adapterId === "akm-workflow";
@@ -120,55 +139,3 @@ export function resolveWorkflowEntryId(_sourcePath, ref, adapterId) {
120
139
  return row?.id ?? null;
121
140
  });
122
141
  }
123
- function compileWorkflowSourceFromDisk(assetPath, workspaceRoot) {
124
- const content = fs.readFileSync(assetPath, "utf8");
125
- const result = compileWorkflowSource(content, { path: assetPath, workspaceRoot });
126
- if (!result.ok) {
127
- // P4-N2's mapping (docs/plans/specs/p4-deletions-closeout.md §3.3.4): this
128
- // is the FIRST compile of a workflow's own source that a ref resolves
129
- // through (loadWorkflowAsset runs before compileResolveFreezeWorkflowV4),
130
- // so it must apply the same code split as the freeze wrapper — otherwise
131
- // row B-44's COMPOSITION_INVALID promise never reaches `startWorkflowRun`
132
- // callers, which never get past this point on a multi-job source.
133
- const details = result.errors.map((error) => ` ${error.path}:${error.line} — ${error.message}`).join("\n");
134
- const isMultiJob = result.errors.length === 1 && result.errors[0]?.code === "multi-job-unsupported";
135
- const code = isMultiJob ? "COMPOSITION_INVALID" : "WORKFLOW_SOURCE_INVALID";
136
- throw new UsageError(`Workflow source has ${result.errors.length} error(s):\n${details}`, code, isMultiJob ? COMPOSITION_INVALID_MULTI_JOB_HINT : undefined);
137
- }
138
- return result.ir;
139
- }
140
- function projectAsset(sourceIr, ref, assetPath, sourcePath, adapterId, canonicalName) {
141
- const title = canonicalName.split("/").pop() || canonicalName;
142
- return {
143
- ref,
144
- path: assetPath,
145
- sourcePath,
146
- adapterId,
147
- title,
148
- ...(sourceIr.params
149
- ? {
150
- parameters: Object.entries(sourceIr.params).map(([name, schema]) => {
151
- const description = schema.description;
152
- return { name, ...(typeof description === "string" && description ? { description } : {}) };
153
- }),
154
- }
155
- : {}),
156
- steps: sourceIr.jobs.flatMap((job) => job.steps.map((step, sequenceIndex) => ({
157
- id: step.id,
158
- title: step.id,
159
- instructions: step.route ? stepFallbackInstructions(step) : sourceStepInstructions(step),
160
- ...(step.gate?.rubric?.trim() ? { completionCriteria: [step.gate.rubric] } : {}),
161
- sequenceIndex,
162
- }))),
163
- sourceIr,
164
- };
165
- }
166
- /** A route-only step with no body section still needs a non-empty spine instructions string. */
167
- function stepFallbackInstructions(step) {
168
- if (!step.route)
169
- return "";
170
- const branches = step.route.branches.map((b) => `"${b.match}" -> ${b.stepId}`);
171
- if (step.route.defaultStepId !== undefined)
172
- branches.push(`default -> ${step.route.defaultStepId}`);
173
- return `Route on ${step.route.input}: ${branches.join(", ")}.`;
174
- }