akm-cli 0.9.0-rc.8 → 0.9.0

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 (450) hide show
  1. package/CHANGELOG.md +1063 -44
  2. package/README.md +51 -25
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +497 -0
  5. package/dist/akm +148 -35
  6. package/dist/{akm-migrate-storage → akm-migrate} +6 -9
  7. package/dist/assets/hints/cli-hints-full.md +223 -95
  8. package/dist/assets/hints/cli-hints-short.md +85 -22
  9. package/dist/assets/improve-strategies/default.json +1 -1
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  11. package/dist/assets/prompts/memory-infer-user.md +2 -3
  12. package/dist/assets/stash-skeleton/README.md +6 -5
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
  25. package/dist/assets/tasks/core/extract.yml +1 -1
  26. package/dist/assets/tasks/core/version-check.yml +1 -1
  27. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  28. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  29. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  30. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  31. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  32. package/dist/assets/templates/html/health.html +1 -3
  33. package/dist/assets/workflows/workflow-template.md +32 -15
  34. package/dist/cli/invocation.js +40 -15
  35. package/dist/cli/parse-args.js +0 -22
  36. package/dist/cli/retired-commands.js +121 -0
  37. package/dist/cli/shared.js +154 -22
  38. package/dist/cli/unknown-flags.js +236 -0
  39. package/dist/cli-node.mjs +2 -1
  40. package/dist/cli.js +696 -258
  41. package/dist/commands/agent/agent-dispatch.js +14 -3
  42. package/dist/commands/agent/contribute-cli.js +73 -88
  43. package/dist/commands/completions.js +79 -22
  44. package/dist/commands/config-cli.js +17 -150
  45. package/dist/commands/env/env-cli.js +59 -143
  46. package/dist/commands/env/env.js +12 -163
  47. package/dist/commands/env/marker-path.js +6 -0
  48. package/dist/commands/env/secret-cli.js +36 -66
  49. package/dist/commands/env/secret.js +24 -57
  50. package/dist/commands/feedback-cli.js +141 -87
  51. package/dist/commands/health/accept-rate.js +58 -0
  52. package/dist/commands/health/advisories.js +3 -4
  53. package/dist/commands/health/checks.js +85 -23
  54. package/dist/commands/health/html-report.js +7 -10
  55. package/dist/commands/health/improve-metrics.js +25 -83
  56. package/dist/commands/health/md-report.js +5 -9
  57. package/dist/commands/health/metrics.js +62 -20
  58. package/dist/commands/health/renderers.js +47 -0
  59. package/dist/commands/health/report-view-model.js +4 -5
  60. package/dist/commands/health/stash-exposure.js +1 -1
  61. package/dist/commands/health/surfaces.js +3 -48
  62. package/dist/commands/health/task-runs.js +3 -67
  63. package/dist/commands/health/types-improve.js +7 -0
  64. package/dist/commands/health.js +99 -28
  65. package/dist/commands/improve/anti-collapse.js +2 -2
  66. package/dist/commands/improve/autonomy-gate.js +68 -0
  67. package/dist/commands/improve/collapse-detector.js +41 -40
  68. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  69. package/dist/commands/improve/consolidate/merge.js +4 -0
  70. package/dist/commands/improve/consolidate.js +140 -1000
  71. package/dist/commands/improve/distill/promote-memory.js +12 -12
  72. package/dist/commands/improve/distill/quality-gate.js +6 -6
  73. package/dist/commands/improve/distill.js +58 -69
  74. package/dist/commands/improve/eligibility.js +105 -57
  75. package/dist/commands/improve/extract-cli.js +14 -133
  76. package/dist/commands/improve/improve-cli.js +98 -114
  77. package/dist/commands/improve/improve-result-file.js +1 -28
  78. package/dist/commands/improve/improve-strategies.js +8 -5
  79. package/dist/commands/improve/improve.js +128 -91
  80. package/dist/commands/improve/loop-stages.js +182 -20
  81. package/dist/commands/improve/memory/derived-ref.js +45 -43
  82. package/dist/commands/improve/memory/memory-belief.js +1 -1
  83. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  84. package/dist/commands/improve/memory/memory-improve.js +6 -5
  85. package/dist/commands/improve/outcome-loop.js +22 -65
  86. package/dist/commands/improve/preparation.js +114 -123
  87. package/dist/commands/improve/proactive-maintenance.js +2 -5
  88. package/dist/commands/improve/reflect.js +56 -160
  89. package/dist/commands/improve/salience.js +11 -122
  90. package/dist/commands/improve/source-identity.js +10 -38
  91. package/dist/commands/lint/base-linter.js +20 -124
  92. package/dist/commands/lint/env-key-rules.js +31 -47
  93. package/dist/commands/lint/index.js +249 -43
  94. package/dist/commands/{events.js → log.js} +33 -38
  95. package/dist/commands/migrate-cli.js +92 -12
  96. package/dist/commands/migration-tool.js +46 -0
  97. package/dist/commands/observability-cli.js +70 -209
  98. package/dist/commands/proposal/drain.js +101 -29
  99. package/dist/commands/proposal/proposal-cli.js +76 -48
  100. package/dist/commands/proposal/proposal.js +54 -18
  101. package/dist/commands/proposal/propose-cli.js +88 -0
  102. package/dist/commands/proposal/propose.js +23 -15
  103. package/dist/commands/proposal/repository.js +701 -278
  104. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  105. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  106. package/dist/commands/proposal/validators/proposals.js +4 -7
  107. package/dist/commands/read/curate.js +34 -53
  108. package/dist/commands/read/knowledge.js +150 -95
  109. package/dist/commands/read/registry-search.js +2 -2
  110. package/dist/commands/read/remember-cli.js +42 -15
  111. package/dist/commands/read/search-cli.js +180 -78
  112. package/dist/commands/read/search.js +58 -43
  113. package/dist/commands/read/show.js +197 -141
  114. package/dist/commands/registry-cli.js +12 -51
  115. package/dist/commands/remember.js +14 -57
  116. package/dist/commands/sources/add-cli.js +100 -31
  117. package/dist/commands/sources/bundle-cli.js +166 -0
  118. package/dist/commands/sources/bundle-config-ops.js +7 -2
  119. package/dist/commands/sources/info.js +18 -5
  120. package/dist/commands/sources/init.js +12 -12
  121. package/dist/commands/sources/installed-stashes.js +382 -98
  122. package/dist/commands/sources/schema-repair.js +3 -2
  123. package/dist/commands/sources/self-update.js +131 -38
  124. package/dist/commands/sources/source-add.js +72 -17
  125. package/dist/commands/sources/source-clone.js +129 -45
  126. package/dist/commands/sources/source-manage.js +43 -23
  127. package/dist/commands/sources/sources-cli.js +57 -208
  128. package/dist/commands/sources/stash-cli.js +46 -53
  129. package/dist/commands/tasks/tasks-cli.js +91 -97
  130. package/dist/commands/tasks/tasks.js +276 -421
  131. package/dist/commands/workflow-cli.js +175 -450
  132. package/dist/core/adapter/adapters/akm-adapter.js +47 -28
  133. package/dist/core/adapter/adapters/akm-lint.js +42 -27
  134. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  135. package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
  136. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  137. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  138. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  139. package/dist/core/adapter/adapters/index.js +6 -6
  140. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  141. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  142. package/dist/core/adapter/adapters/shared.js +3 -19
  143. package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
  144. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  145. package/dist/core/adapter/detect-adapter.js +17 -0
  146. package/dist/core/adapter/recognize-match.js +6 -4
  147. package/dist/core/adapter/validate-context.js +214 -0
  148. package/dist/core/asset/akm-markdown.js +63 -0
  149. package/dist/core/asset/asset-placement.js +20 -6
  150. package/dist/core/asset/asset-ref.js +11 -9
  151. package/dist/core/asset/frontmatter-lint.js +30 -0
  152. package/dist/core/asset/frontmatter.js +37 -9
  153. package/dist/core/asset/markdown.js +40 -51
  154. package/dist/core/asset/resolve-ref.js +89 -18
  155. package/dist/core/asset/stash-meta.js +1 -1
  156. package/dist/core/bundle-id.js +51 -0
  157. package/dist/core/common.js +152 -38
  158. package/dist/core/config/config-io.js +12 -1
  159. package/dist/core/config/config-schema.js +35 -8
  160. package/dist/core/config/config-sources.js +55 -11
  161. package/dist/core/config/config-walker.js +25 -9
  162. package/dist/core/config/config.js +9 -48
  163. package/dist/core/config/experimental.js +21 -0
  164. package/dist/core/config/schema/embedding.js +5 -1
  165. package/dist/core/config/schema/experimental.js +30 -0
  166. package/dist/core/config/schema/improve-processes.js +0 -6
  167. package/dist/core/config/schema/improve.js +21 -3
  168. package/dist/core/config/schema/index-config.js +8 -15
  169. package/dist/core/config/schema/output.js +4 -1
  170. package/dist/core/config/schema/setup.js +9 -18
  171. package/dist/core/config/schema/sources-bundles.js +49 -33
  172. package/dist/core/config/schema/workflow.js +3 -3
  173. package/dist/core/env-secret-ref.js +76 -46
  174. package/dist/core/errors.js +18 -12
  175. package/dist/core/events.js +46 -128
  176. package/dist/core/file-change.js +6 -5
  177. package/dist/core/fs-txn.js +83 -7
  178. package/dist/core/git-message.js +2 -2
  179. package/dist/core/improve-result.js +1 -100
  180. package/dist/core/lesson-lint.js +1 -17
  181. package/dist/core/logs-db.js +2 -1
  182. package/dist/core/migration-operation.js +16 -0
  183. package/dist/core/mutation-target.js +78 -0
  184. package/dist/core/parse.js +4 -1
  185. package/dist/core/paths.js +17 -20
  186. package/dist/core/recognition-util.js +12 -14
  187. package/dist/core/redaction.js +34 -0
  188. package/dist/core/standards/resolve-standards-context.js +2 -14
  189. package/dist/core/standards/resolve-stash-standards.js +2 -2
  190. package/dist/core/standards/resolve-type-conventions.js +2 -2
  191. package/dist/core/state/migrations.js +41 -18
  192. package/dist/core/state-db.js +5 -14
  193. package/dist/core/structured.js +1 -1
  194. package/dist/core/subprocess.js +6 -4
  195. package/dist/core/text-truncation.js +9 -5
  196. package/dist/core/type-presentation.js +3 -3
  197. package/dist/core/warn.js +0 -3
  198. package/dist/core/write-source.js +771 -95
  199. package/dist/indexer/bundle-identity-guard.js +3 -2
  200. package/dist/indexer/db/graph-db.js +0 -24
  201. package/dist/indexer/ensure-index.js +1 -0
  202. package/dist/indexer/graph/graph-boost.js +9 -34
  203. package/dist/indexer/graph/graph-extraction.js +8 -5
  204. package/dist/indexer/index-writer-lock.js +53 -17
  205. package/dist/indexer/index-written-assets.js +16 -22
  206. package/dist/indexer/indexer.js +497 -239
  207. package/dist/indexer/installations.js +14 -96
  208. package/dist/indexer/passes/dir-staleness.js +16 -9
  209. package/dist/indexer/passes/memory-inference.js +11 -9
  210. package/dist/indexer/passes/metadata.js +113 -47
  211. package/dist/indexer/scan/doc-to-entry.js +38 -1
  212. package/dist/indexer/scan/drain-dir.js +13 -23
  213. package/dist/indexer/search/db-search.js +99 -54
  214. package/dist/indexer/search/fts-query.js +47 -24
  215. package/dist/indexer/search/ranking-contributors.js +42 -20
  216. package/dist/indexer/search/ranking.js +18 -99
  217. package/dist/indexer/search/search-fields.js +7 -2
  218. package/dist/indexer/search/search-source.js +82 -93
  219. package/dist/indexer/usage/usage-events.js +0 -89
  220. package/dist/indexer/walk/file-context.js +2 -1
  221. package/dist/indexer/walk/matchers.js +30 -43
  222. package/dist/indexer/walk/path-resolver.js +7 -2
  223. package/dist/indexer/walk/walker.js +38 -12
  224. package/dist/integrations/agent/builders.js +0 -6
  225. package/dist/integrations/agent/config.js +2 -2
  226. package/dist/integrations/agent/detect.js +49 -19
  227. package/dist/integrations/agent/engine-fallback.js +76 -0
  228. package/dist/integrations/agent/profiles.js +14 -0
  229. package/dist/integrations/agent/prompts.js +12 -8
  230. package/dist/integrations/agent/runner-dispatch.js +4 -2
  231. package/dist/integrations/agent/runner.js +0 -1
  232. package/dist/integrations/agent/spawn.js +5 -6
  233. package/dist/integrations/github.js +1 -1
  234. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  235. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  236. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  237. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  238. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  239. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  240. package/dist/integrations/harnesses/index.js +3 -7
  241. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  242. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  243. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  244. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  245. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  246. package/dist/integrations/lockfile.js +101 -6
  247. package/dist/integrations/session-logs/index.js +3 -28
  248. package/dist/llm/client.js +136 -100
  249. package/dist/llm/embedders/remote.js +13 -5
  250. package/dist/llm/feature-gate.js +4 -12
  251. package/dist/llm/graph-extract.js +5 -11
  252. package/dist/llm/memory-infer.js +144 -1
  253. package/dist/llm/metadata-enhance.js +5 -7
  254. package/dist/llm/structured-call.js +1 -1
  255. package/dist/llm/usage-persist.js +26 -5
  256. package/dist/llm/usage-telemetry.js +25 -2
  257. package/dist/output/cli-hints.js +1 -2
  258. package/dist/output/context.js +22 -7
  259. package/dist/output/format-exempt.js +80 -0
  260. package/dist/output/generic-render.js +259 -0
  261. package/dist/output/render-registry.js +57 -0
  262. package/dist/output/renderers.js +14 -36
  263. package/dist/output/shapes/curate.js +10 -1
  264. package/dist/output/shapes/events.js +12 -7
  265. package/dist/output/shapes/helpers.js +56 -83
  266. package/dist/output/shapes/migrate.js +8 -0
  267. package/dist/output/shapes/passthrough.js +7 -41
  268. package/dist/output/shapes/proposal/producer.js +15 -7
  269. package/dist/output/shapes.js +2 -9
  270. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  271. package/dist/output/text/bundle-show.js +7 -0
  272. package/dist/output/text/command-format.js +164 -96
  273. package/dist/output/text/env.js +1 -3
  274. package/dist/output/text/events.js +8 -7
  275. package/dist/output/text/health-format.js +103 -0
  276. package/dist/output/text/health.js +7 -0
  277. package/dist/output/text/helpers.js +10 -8
  278. package/dist/output/text/lint-format.js +43 -0
  279. package/dist/output/text/{save.js → lint.js} +2 -2
  280. package/dist/output/text/migrate.js +88 -0
  281. package/dist/output/text/proposal/producer.js +4 -2
  282. package/dist/output/text/proposal-format.js +44 -72
  283. package/dist/output/text/registry-commands.js +1 -2
  284. package/dist/output/text/show-directives.js +15 -7
  285. package/dist/output/text/status-list.js +32 -0
  286. package/dist/output/text/sync.js +5 -0
  287. package/dist/output/text/workflow-format.js +24 -203
  288. package/dist/output/text/workflow.js +1 -7
  289. package/dist/output/text.js +16 -17
  290. package/dist/registry/factory.js +4 -6
  291. package/dist/registry/origin-resolve.js +16 -27
  292. package/dist/registry/providers/skills-sh.js +3 -3
  293. package/dist/registry/providers/static-index.js +13 -23
  294. package/dist/registry/resolve.js +42 -7
  295. package/dist/registry/semver.js +34 -84
  296. package/dist/runtime.js +2 -23
  297. package/dist/scripts/akm-migrate-node.js +60290 -0
  298. package/dist/scripts/akm-migrate.js +59628 -0
  299. package/dist/setup/detect.js +42 -15
  300. package/dist/setup/registry-stash-loader.js +2 -2
  301. package/dist/setup/setup.js +236 -136
  302. package/dist/setup/steps/connection.js +7 -9
  303. package/dist/setup/steps/platforms.js +9 -9
  304. package/dist/setup/steps/semantic.js +15 -3
  305. package/dist/setup/steps/sources.js +12 -13
  306. package/dist/setup/steps/stashdir.js +2 -3
  307. package/dist/setup/steps/tasks.js +237 -120
  308. package/dist/sources/freshness.js +1 -1
  309. package/dist/sources/provider-factory.js +11 -17
  310. package/dist/sources/providers/filesystem.js +2 -3
  311. package/dist/sources/providers/git-install.js +278 -34
  312. package/dist/sources/providers/git-provider.js +25 -23
  313. package/dist/sources/providers/git-stash.js +395 -106
  314. package/dist/sources/providers/git.js +2 -2
  315. package/dist/sources/providers/npm.js +16 -19
  316. package/dist/sources/providers/provider-utils.js +7 -4
  317. package/dist/sources/providers/sync-from-ref.js +3 -9
  318. package/dist/sources/providers/website.js +6 -1
  319. package/dist/sources/resolve.js +6 -5
  320. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  321. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  322. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  323. package/dist/sources/snapshot-fetchers/github.js +100 -0
  324. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  325. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  326. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  327. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  328. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  329. package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
  330. package/dist/sources/snapshot-fetchers/x.js +910 -0
  331. package/dist/storage/database.js +7 -0
  332. package/dist/storage/engines/sqlite-migrations.js +23 -111
  333. package/dist/storage/managed-db.js +2 -2
  334. package/dist/storage/repositories/canaries-repository.js +1 -1
  335. package/dist/storage/repositories/events-repository.js +27 -11
  336. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  337. package/dist/storage/repositories/index-connection.js +17 -6
  338. package/dist/storage/repositories/index-entries-repository.js +151 -240
  339. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  340. package/dist/storage/repositories/index-fts-repository.js +5 -2
  341. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  342. package/dist/storage/repositories/index-meta-repository.js +2 -3
  343. package/dist/storage/repositories/index-schema.js +10 -25
  344. package/dist/storage/repositories/index-utility-repository.js +15 -28
  345. package/dist/storage/repositories/index-vec-repository.js +6 -1
  346. package/dist/storage/repositories/outcome-repository.js +119 -0
  347. package/dist/storage/repositories/proposals-repository.js +296 -59
  348. package/dist/storage/repositories/registry-cache.js +19 -0
  349. package/dist/storage/repositories/salience-repository.js +172 -0
  350. package/dist/storage/repositories/task-history-repository.js +15 -13
  351. package/dist/storage/repositories/workflow-runs-repository.js +52 -40
  352. package/dist/tasks/backends/cron.js +105 -15
  353. package/dist/tasks/backends/index.js +1 -1
  354. package/dist/tasks/backends/launchd.js +85 -38
  355. package/dist/tasks/backends/schtasks.js +135 -15
  356. package/dist/tasks/embedded.js +56 -40
  357. package/dist/tasks/parser.js +7 -157
  358. package/dist/tasks/resolve-akm-bin.js +137 -59
  359. package/dist/tasks/runner.js +79 -42
  360. package/dist/tasks/scheduler-invocation.js +220 -10
  361. package/dist/tasks/schema.js +24 -1
  362. package/dist/tasks/task-id.js +1 -3
  363. package/dist/tasks/validator.js +20 -6
  364. package/dist/workflows/authoring/authoring.js +94 -143
  365. package/dist/workflows/authoring/scope-key.js +1 -1
  366. package/dist/workflows/exec/frozen-judge.js +28 -2
  367. package/dist/workflows/exec/native-executor.js +77 -57
  368. package/dist/workflows/exec/param-secrets.js +9 -9
  369. package/dist/workflows/exec/run-workflow.js +133 -79
  370. package/dist/workflows/exec/step-work.js +219 -346
  371. package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
  372. package/dist/workflows/ir/compile.js +141 -270
  373. package/dist/workflows/ir/freeze.js +40 -30
  374. package/dist/workflows/ir/params.js +135 -11
  375. package/dist/workflows/ir/plan-hash.js +1 -1
  376. package/dist/workflows/ir/schema.js +25 -26
  377. package/dist/workflows/parser.js +872 -307
  378. package/dist/workflows/program/expressions.js +20 -208
  379. package/dist/workflows/program/schema.js +7 -10
  380. package/dist/workflows/renderer.js +95 -68
  381. package/dist/workflows/resource-limits.js +2 -0
  382. package/dist/workflows/runtime/checkin.js +3 -3
  383. package/dist/workflows/runtime/plan-classifier.js +16 -75
  384. package/dist/workflows/runtime/runs.js +186 -127
  385. package/dist/workflows/runtime/unit-checkin.js +1 -1
  386. package/dist/workflows/runtime/unit-phases.js +2 -2
  387. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  388. package/dist/workflows/schema.js +1 -11
  389. package/dist/workflows/validate-summary.js +30 -36
  390. package/dist/workflows/validator.js +21 -62
  391. package/docs/README.md +68 -0
  392. package/docs/migration/README.md +8 -0
  393. package/docs/migration/release-notes/0.7.0.md +11 -11
  394. package/docs/migration/release-notes/0.9.0.md +208 -27
  395. package/docs/migration/v0.7-to-v0.8.md +46 -47
  396. package/docs/migration/v0.8-to-v0.9.md +564 -208
  397. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  398. package/docs/reference/README.md +12 -0
  399. package/docs/reference/cli.md +2253 -0
  400. package/docs/reference/configuration.md +358 -0
  401. package/docs/reference/data-and-telemetry.md +105 -42
  402. package/docs/reference/workflows.md +647 -0
  403. package/package.json +22 -11
  404. package/schemas/akm-asset-envelope.json +93 -0
  405. package/schemas/akm-config.json +81 -128
  406. package/schemas/akm-workflow.json +74 -73
  407. package/dist/assets/tasks/core/backup.yml +0 -5
  408. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  409. package/dist/cli/config-migrate.js +0 -1806
  410. package/dist/cli/config-validate.js +0 -41
  411. package/dist/commands/backup-cli.js +0 -56
  412. package/dist/commands/bundle/bundle-cli.js +0 -68
  413. package/dist/commands/bundle/bundle.js +0 -219
  414. package/dist/commands/graph/graph-cli.js +0 -124
  415. package/dist/commands/graph/graph.js +0 -489
  416. package/dist/commands/improve/extract-watch.js +0 -140
  417. package/dist/commands/mv-cli.js +0 -1221
  418. package/dist/commands/sources/history.js +0 -201
  419. package/dist/commands/tasks/default-tasks.js +0 -186
  420. package/dist/core/migration-backup.js +0 -1234
  421. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  422. package/dist/llm/memory-infer-impl.js +0 -138
  423. package/dist/migrate/legacy/config-source-migration.js +0 -223
  424. package/dist/migrate/legacy/content-migration.js +0 -305
  425. package/dist/migrate/legacy/legacy-layout.js +0 -779
  426. package/dist/migrate/legacy/legacy-paths.js +0 -25
  427. package/dist/migrate/legacy/legacy-stash-json.js +0 -72
  428. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  429. package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
  430. package/dist/migrate/legacy/three-db-cutover.js +0 -841
  431. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  432. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  433. package/dist/migrate/legacy-ref-grammar.js +0 -214
  434. package/dist/output/shapes/distill.js +0 -14
  435. package/dist/output/shapes/history.js +0 -11
  436. package/dist/output/text/distill.js +0 -6
  437. package/dist/output/text/enable-disable.js +0 -8
  438. package/dist/output/text/history.js +0 -6
  439. package/dist/registry/build-index.js +0 -382
  440. package/dist/schemas/akm-config.json +0 -4704
  441. package/dist/schemas/akm-task.json +0 -87
  442. package/dist/schemas/akm-workflow.json +0 -372
  443. package/dist/scripts/migrate-storage.js +0 -3816
  444. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  445. package/dist/workflows/cli.js +0 -53
  446. package/dist/workflows/exec/brief.js +0 -481
  447. package/dist/workflows/exec/report.js +0 -1460
  448. package/dist/workflows/exec/watch.js +0 -116
  449. package/dist/workflows/program/parser.js +0 -813
  450. package/dist/workflows/program/project.js +0 -104
@@ -3,14 +3,13 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
5
  * Shared step semantics — the ONE implementation of a step's orchestration
6
- * decisions, consumed by BOTH the engine loop (`run-workflow.ts` +
7
- * `native-executor.ts`) and, from R3 on, the harness-neutral driver protocol
8
- * (`workflow brief` / `workflow report`). The cardinal rule of the driver
9
- * protocol (redesign addendum R3) is *no duplicated semantics*: work-list
10
- * computation, prompt assembly, reducer/artifact promotion, output-schema
11
- * validation, artifact-judged gate summaries, gate-feedback recovery, and
12
- * route evaluation live here so an engine-driven run and a brief/report-driven
13
- * run of the same frozen plan produce byte-identical unit graphs.
6
+ * decisions, consumed by the engine loop (`run-workflow.ts` +
7
+ * `native-executor.ts`) on both the fresh-execution and the resume/replay path.
8
+ * The cardinal rule here is *no duplicated semantics*: work-list computation,
9
+ * prompt assembly, reducer/artifact promotion, output-schema validation,
10
+ * artifact-judged gate summaries, gate-feedback recovery, and route evaluation
11
+ * live here so a first run and a resumed run of the same frozen plan produce
12
+ * byte-identical unit graphs.
14
13
  *
15
14
  * ## What is PURE here
16
15
  *
@@ -19,8 +18,9 @@
19
18
  * recovered feedback) — is a pure function: same inputs ⇒ same unit ids, input
20
19
  * hashes, and fully-resolved prompts. It takes NO clock, NO IO, and NO journal
21
20
  * (journal-derived state, i.e. the recovered gate feedback, is passed in). This
22
- * is the load-bearing guarantee that `brief` can predict exactly the units the
23
- * engine would dispatch. So are the reducer/artifact helpers
21
+ * is the load-bearing guarantee that a resumed run recomputes exactly the units
22
+ * the original run dispatched, so journaled rows can be reused instead of
23
+ * re-executed. So are the reducer/artifact helpers
24
24
  * ({@link buildEvidence}, {@link projectStepOutput}, {@link validateStepArtifact},
25
25
  * {@link buildArtifactSummary}), the gate-feedback recovery
26
26
  * ({@link recoverGateFeedback} / {@link activeGateLoop}), and route evaluation
@@ -31,8 +31,8 @@
31
31
  * The gate-evaluation journaling ({@link journalGateEvaluationStart} /
32
32
  * {@link journalGateEvaluationFinish}) writes `workflow_run_units` rows through
33
33
  * the serialized writer queue — an engine-driven judge call is an LLM call and
34
- * is journaled like a unit. It lives here (not in the engine loop) so the
35
- * report path journals gate evaluations through the identical writer.
34
+ * is journaled like a unit. It lives here (not in the engine loop) so every
35
+ * caller journals gate evaluations through the identical writer.
36
36
  *
37
37
  * This module NEVER dispatches a unit and NEVER writes step rows: dispatch is
38
38
  * the executor's job (`native-executor.ts`), advancing the gated spine is the
@@ -44,19 +44,17 @@ import { UsageError } from "../../core/errors.js";
44
44
  import { appendEvent } from "../../core/events.js";
45
45
  import { validateJsonSchemaSubset } from "../../core/json-schema.js";
46
46
  import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
47
- import { canonicalJson as canonicalJsonString, decodeCanonicalPlan } from "../ir/plan-hash.js";
48
- import { parseTemplate, resolveTemplate, resolveWholeValue, } from "../program/expressions.js";
47
+ import { canonicalJson as canonicalJsonString } from "../ir/plan-hash.js";
48
+ import { resolveReferenceString } from "../program/expressions.js";
49
49
  import { WORKFLOW_MAX_MAP_EXPANSION } from "../resource-limits.js";
50
50
  import { requireExecutableWorkflowPlan } from "../runtime/plan-classifier.js";
51
51
  import { completeWorkflowStep } from "../runtime/runs.js";
52
52
  import { GATE_EVALUATION_PHASE } from "../runtime/unit-phases.js";
53
53
  import { enqueueUnitWrite } from "./unit-writer.js";
54
54
  /**
55
- * Default per-unit timeout. Deliberately NOT the 60 s agent default
56
- * (`DEFAULT_AGENT_TIMEOUT_MS`) — workflow units routinely run real coding
57
- * tasks on slow local models; 10 minutes matches the LLM-path default
58
- * (`tryLlmFeature`). A unit's `timeout` declaration overrides this; `none`
59
- * disables.
55
+ * Default per-unit timeout for workflow units. A unit's `timeout` declaration
56
+ * overrides this; `none` disables it. Direct agent dispatch has no timeout by
57
+ * default, while workflow units retain an independent safety ceiling.
60
58
  */
61
59
  export const DEFAULT_UNIT_TIMEOUT_MS = 600_000;
62
60
  /** How much raw unit output is retained in step evidence (full text lives on the unit row). */
@@ -68,14 +66,54 @@ const GATE_ARTIFACT_CLIP = 4_000;
68
66
  * inputs: resolve the fan-out list, derive content-derived unit ids, assemble
69
67
  * each unit's prompt (preamble + interpolated instructions + gate feedback +
70
68
  * schema directive), and hash the resolved input. Same inputs ⇒ byte-identical
71
- * ids/hashes/prompts — the invariant `brief` relies on to predict the engine.
69
+ * ids/hashes/prompts — the invariant resume/replay relies on to recognize the
70
+ * units an earlier run already journaled.
72
71
  *
73
- * Whole-list failures (missing subgraph, template parse error, unresolvable /
74
- * non-array `over`, duplicate fan-out items) return `{ ok: false }`; a per-unit
75
- * expression-resolution failure is carried on that unit's `resolved` field so
76
- * the caller fails just that unit (mirroring the engine's `expression_error`
77
- * outcome), never the whole step.
72
+ * Whole-list failures (missing subgraph, unresolvable / non-array `over`,
73
+ * null or duplicate fan-out items) return `{ ok: false }`. The per-unit
74
+ * `resolved: { ok: false }` branch is STRUCTURALLY UNREACHABLE in the unified
75
+ * format — prose is never scanned for references, and everything that CAN
76
+ * fail (map.over / route.input / inputs:) resolves once per step, failing the
77
+ * whole list above. The branch is retained because every consumer of the work
78
+ * list shares the shape and defensively handles it; if a future unit kind
79
+ * reintroduces per-unit resolution (e.g. an exec/shell unit with real
80
+ * substitution), the failure plumbing is already in place.
78
81
  */
82
+ /**
83
+ * Validate a fan-out item list BEFORE any identity/dispatch work: expansion
84
+ * within the resource limit, no null/undefined items, no canonical duplicates.
85
+ * Returns the failure message, or undefined when the list is dispatchable.
86
+ *
87
+ * Null items: producer garbage — there is nothing to hand the unit as its work
88
+ * item. The pre-unification format rejected them incidentally (substituting
89
+ * `${{ item }}` failed); with items attached as context instead of spliced,
90
+ * nothing later would stop a unit from being dispatched with "Item: null", so
91
+ * the rejection is explicit here. Duplicates: content-derived unit identity
92
+ * makes canonical duplicates collide on id — an authoring error caught
93
+ * deterministically, before dispatch.
94
+ */
95
+ function validateFanOutItems(stepId, items) {
96
+ if (items.length > WORKFLOW_MAX_MAP_EXPANSION) {
97
+ return `Step "${stepId}" fan-out expands to ${items.length} units, exceeding the ${WORKFLOW_MAX_MAP_EXPANSION}-unit resource limit.`;
98
+ }
99
+ const nullIndex = items.findIndex((item) => item === null || item === undefined);
100
+ if (nullIndex !== -1) {
101
+ return (`Step "${stepId}" fan-out list contains a null item (index ${nullIndex}). ` +
102
+ `Every item must be a concrete value — fix the producing step's output.`);
103
+ }
104
+ const firstIndexByCanonical = new Map();
105
+ for (let i = 0; i < items.length; i++) {
106
+ const canonical = canonicalJson(items[i]) ?? "null";
107
+ const firstIndex = firstIndexByCanonical.get(canonical);
108
+ if (firstIndex !== undefined) {
109
+ return (`Step "${stepId}" fan-out list contains duplicate items (indices ${firstIndex} and ${i}: ` +
110
+ `${clip(canonical, 200)}). Content-derived unit identity requires distinct items — ` +
111
+ `deduplicate the list this workflow fans out over.`);
112
+ }
113
+ firstIndexByCanonical.set(canonical, i);
114
+ }
115
+ return undefined;
116
+ }
79
117
  export function computeStepWorkList(plan, input) {
80
118
  const root = plan.root;
81
119
  // Route-only steps (YAML `route:`) carry no execution subgraph.
@@ -88,30 +126,29 @@ export function computeStepWorkList(plan, input) {
88
126
  const template = root.kind === "map" ? root.template : root;
89
127
  const reducer = root.kind === "map" ? root.reducer : "collect";
90
128
  const scope = { params: input.params, stepOutputs: input.stepOutputs };
91
- // Parse the instruction template ONCE (deterministic; resolution is a single
92
- // pass per unit — substituted content is never re-scanned). Only nodes the
93
- // frontend marked `templating: "expressions"` carry the `${{ … }}` grammar;
94
- // classic linear markdown is opaque verbatim text.
95
- let instructionSegments;
96
- if (template.templating === "expressions") {
97
- const parsedInstructions = parseTemplate(template.instructions);
98
- if (!parsedInstructions.ok) {
129
+ // Instructions are ALWAYS the step's body prose, byte-exact — never
130
+ // templated, never scanned for reference syntax (workflow-format-
131
+ // unification, spec §2.3). Only `map.over` / `route.input` / `inputs[]`
132
+ // carry the closed reference grammar.
133
+ // Resolve the step's declared `inputs:` ONCE (shared by every unit in this
134
+ // step — map items differ, declared inputs do not): prior-step artifacts
135
+ // attached to every dispatched unit as structured context.
136
+ const resolvedInputs = [];
137
+ for (const reference of template.inputs ?? []) {
138
+ const resolved = resolveReferenceString(reference, scope);
139
+ if (!resolved.ok) {
99
140
  return {
100
141
  ok: false,
101
- error: `Step "${plan.stepId}" instructions template failed to parse: ` +
102
- parsedInstructions.errors.map((e) => e.message).join(" "),
142
+ error: `Step "${plan.stepId}" declared input "${reference}" failed to resolve: ${resolved.error.message}`,
103
143
  };
104
144
  }
105
- instructionSegments = parsedInstructions.segments;
145
+ resolvedInputs.push({ reference, value: resolved.value });
106
146
  }
107
- else {
108
- instructionSegments = [{ kind: "literal", text: template.instructions }];
109
- }
110
- // Resolve fan-out items: `over` is a single whole-value `${{ … }}` reference
111
- // naming its producer explicitly — no ambient key search.
147
+ // Resolve fan-out items: `over` is a single whole-value reference naming
148
+ // its producer explicitly — no ambient key search.
112
149
  let items;
113
150
  if (root.kind === "map") {
114
- const source = resolveWholeValue(root.over, scope);
151
+ const source = resolveReferenceString(root.over, scope);
115
152
  if (!source.ok) {
116
153
  return {
117
154
  ok: false,
@@ -130,31 +167,12 @@ export function computeStepWorkList(plan, input) {
130
167
  items = [undefined];
131
168
  }
132
169
  const isFanOut = root.kind === "map";
133
- if (isFanOut && items.length > WORKFLOW_MAX_MAP_EXPANSION) {
134
- return {
135
- ok: false,
136
- error: `Step "${plan.stepId}" fan-out expands to ${items.length} units, exceeding the ${WORKFLOW_MAX_MAP_EXPANSION}-unit resource limit.`,
137
- };
138
- }
139
- // Content-derived unit identity: compute every id up front. Duplicate items
140
- // collide on identity — an authoring error caught HERE, deterministically.
170
+ const fanOutProblem = isFanOut ? validateFanOutItems(plan.stepId, items) : undefined;
171
+ if (fanOutProblem)
172
+ return { ok: false, error: fanOutProblem };
173
+ // Content-derived unit identity: compute every id up front (duplicate items
174
+ // were rejected above — identity requires distinct items).
141
175
  const unitIds = items.map((item) => unitIdFor(template.id, item, isFanOut));
142
- if (isFanOut) {
143
- const firstIndexByCanonical = new Map();
144
- for (let i = 0; i < items.length; i++) {
145
- const canonical = canonicalJson(items[i]) ?? "null";
146
- const firstIndex = firstIndexByCanonical.get(canonical);
147
- if (firstIndex !== undefined) {
148
- return {
149
- ok: false,
150
- error: `Step "${plan.stepId}" fan-out list contains duplicate items (indices ${firstIndex} and ${i}: ` +
151
- `${clip(canonical, 200)}). Content-derived unit identity requires distinct items — ` +
152
- `deduplicate the list this workflow fans out over.`,
153
- };
154
- }
155
- firstIndexByCanonical.set(canonical, i);
156
- }
157
- }
158
176
  const gateLoop = input.gateLoop ?? 1;
159
177
  const frozenInvocation = template.invocation;
160
178
  if (!frozenInvocation)
@@ -171,66 +189,80 @@ export function computeStepWorkList(plan, input) {
171
189
  // never clobbered; the content-derived identity (and the prompt's
172
190
  // {{UNIT_ID}}) stays the base id.
173
191
  const journalBaseId = gateLoop > 1 ? `${unitId}~l${gateLoop}` : unitId;
174
- // Single-pass resolution of the pre-parsed template against this unit's
175
- // scope. A resolution failure is deterministic authoring/data breakage.
176
- const unitScope = isFanOut ? { ...scope, item, itemIndex: index } : scope;
177
- const resolvedInstr = resolveTemplate(instructionSegments, unitScope);
178
- let resolved;
179
- if (!resolvedInstr.ok) {
180
- resolved = {
181
- ok: false,
182
- error: `instructions failed to resolve: ${resolvedInstr.errors.map((e) => e.message).join(" ")}`,
183
- };
184
- }
185
- else {
186
- const prompt = buildUnitPrompt({
187
- runId: input.runId,
188
- stepId: plan.stepId,
189
- unitId,
190
- params: input.params,
191
- ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
192
- ...(template.schema ? { schema: template.schema } : {}),
193
- instructions: resolvedInstr.text,
194
- });
195
- // Canonical dispatch-input envelope (reviewer finding #1). Every field
196
- // here is a PLAN-FROZEN input that changes what the backend is actually
197
- // asked to do, so a completed unit is reused ONLY when all of them match;
198
- // a change to any of them re-dispatches. Key order is FIXED — it is the
199
- // hash preimage (JSON.stringify preserves insertion order) — and shared
200
- // by ALL surfaces, since this is the ONE place a unit's inputHash is
201
- // computed (engine, brief, and report all call computeStepWorkList), so
202
- // the byte-identical hash across surfaces is structural, not coincidental.
203
- //
204
- // Included beyond the R4 baseline (prompt/runner/model/schema): resolved
205
- // timeoutMs, the env asset ref NAMES, and isolation — each
206
- // reaches dispatch (native-executor's UnitDispatchRequest) and a changed
207
- // one yields a materially different call. `env` carries NAMES ONLY, never
208
- // resolved values: hashing a resolved secret would leak it into a
209
- // durable hash oracle and would spuriously re-dispatch on every secret
210
- // rotation. `retry`/`onError` are DELIBERATELY excluded — they govern
211
- // failed-unit re-dispatch and step-level failure reduction, not a
212
- // COMPLETED unit's inputs/output, so a completed row stays valid across
213
- // policy changes.
214
- //
215
- // Ambient config is DELIBERATELY excluded — the model-alias table, the
216
- // resolved backend/connection, and the working directory (`ctx.workDir` /
217
- // process.cwd()) are NOT plan-frozen. The frozen plan is the identity
218
- // boundary (redesign addendum determinism bar #2): config drift under an
219
- // in-flight run is out of scope by design.
220
- const dispatch = transitiveDispatchSnapshot(frozenEngine, input.engines ?? {});
221
- const inputHash = createHash("sha256")
222
- .update(canonicalJsonString({
223
- hashVersion: 3,
224
- prompt,
225
- dispatch,
226
- invocation: frozenInvocation,
227
- schema: template.schema ?? null,
228
- env: template.env ?? null,
229
- isolation: template.isolation ?? "none",
230
- }))
231
- .digest("hex");
232
- resolved = { ok: true, prompt, inputHash };
233
- }
192
+ // Context attachment (workflow-format-unification, spec §4): every unit
193
+ // receives the run params (already in the preamble), its item + index if
194
+ // it is a map unit, and the artifacts named by its step's `inputs:`.
195
+ // Instructions reach the unit byte-exact — never interpolated.
196
+ const prompt = buildUnitPrompt({
197
+ runId: input.runId,
198
+ stepId: plan.stepId,
199
+ unitId,
200
+ params: input.params,
201
+ ...(isFanOut ? { item, itemIndex: index } : {}),
202
+ ...(resolvedInputs.length > 0 ? { inputs: resolvedInputs } : {}),
203
+ ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
204
+ ...(template.schema ? { schema: template.schema } : {}),
205
+ instructions: template.instructions,
206
+ });
207
+ // Canonical dispatch-input envelope (reviewer finding #1). Every field
208
+ // here is a PLAN-FROZEN input that changes what the backend is actually
209
+ // asked to do, so a completed unit is reused ONLY when all of them match;
210
+ // a change to any of them re-dispatches. Key order is FIXED — it is the
211
+ // hash preimage (JSON.stringify preserves insertion order) — and this is
212
+ // the ONE place a unit's inputHash is computed (every caller goes through
213
+ // computeStepWorkList), so a hash that is byte-identical across a fresh
214
+ // run and a resume is structural, not coincidental.
215
+ //
216
+ // Unit identity (workflow-format-unification, spec §2.3/§4) hashes the
217
+ // FROZEN TEMPLATE BYTES (`template.instructions`, byte-exact, never an
218
+ // instantiated/interpolated string) + the canonical item JSON + the
219
+ // declared-input artifact hashes + the params snapshot — instead of a
220
+ // resolved/spliced prompt string, since there is no more splicing. The
221
+ // assembled `prompt` above is what the harness SEES; the hash is over the
222
+ // plan-frozen INPUTS that determine it, which is the same replay contract
223
+ // the old resolved-prompt hash gave (same inputs ⇒ same hash) with the
224
+ // interpolation step removed.
225
+ //
226
+ // Included beyond the R4 baseline (template/runner/model/schema): resolved
227
+ // timeoutMs, the env asset ref NAMES, and isolation — each reaches
228
+ // dispatch (native-executor's UnitDispatchRequest) and a changed one
229
+ // yields a materially different call. `env` carries NAMES ONLY, never
230
+ // resolved values: hashing a resolved secret would leak it into a durable
231
+ // hash oracle and would spuriously re-dispatch on every secret rotation.
232
+ // `retry`/`onError` are DELIBERATELY excluded — they govern failed-unit
233
+ // re-dispatch and step-level failure reduction, not a COMPLETED unit's
234
+ // inputs/output, so a completed row stays valid across policy changes.
235
+ //
236
+ // `gateFeedback` IS included (conditionally, so a no-feedback unit's
237
+ // preimage is byte-identical to before): it is appended to the prompt by
238
+ // `buildUnitPrompt`, so a gate loop's retry is materially a different ask
239
+ // than the rejected attempt — omitting it made loop 1 and loop 2 journal
240
+ // identical hashes for different prompts, breaking the "changed inputs ⇒
241
+ // changed hash" audit contract. Replay-safe: feedback is re-derived from
242
+ // the journaled gate decision, so a resumed retry re-hashes identically.
243
+ //
244
+ // Ambient config is DELIBERATELY excluded — the model-alias table, the
245
+ // resolved backend/connection, and the working directory (`ctx.workDir` /
246
+ // process.cwd()) are NOT plan-frozen. The frozen plan is the identity
247
+ // boundary (redesign addendum determinism bar #2): config drift under an
248
+ // in-flight run is out of scope by design.
249
+ const dispatch = transitiveDispatchSnapshot(frozenEngine, input.engines ?? {});
250
+ const inputHash = createHash("sha256")
251
+ .update(canonicalJsonString({
252
+ hashVersion: 4,
253
+ template: template.instructions,
254
+ item: isFanOut ? (item ?? null) : null,
255
+ inputs: resolvedInputs,
256
+ params: input.params,
257
+ dispatch,
258
+ invocation: frozenInvocation,
259
+ schema: template.schema ?? null,
260
+ env: template.env ?? null,
261
+ isolation: template.isolation ?? "none",
262
+ ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
263
+ }))
264
+ .digest("hex");
265
+ const resolved = { ok: true, prompt, inputHash };
234
266
  return {
235
267
  unitId,
236
268
  nodeId: template.id,
@@ -265,13 +297,16 @@ export function computeStepWorkList(plan, input) {
265
297
  };
266
298
  }
267
299
  /**
268
- * Assemble the final prompt: engine preamble + resolved instructions
269
- * (+ gate feedback on loop re-executions, + schema directive). Workflow-
270
- * authored interpolation happened upstream via the expression module; only
271
- * the ENGINE's own preamble placeholders are substituted here.
300
+ * Assemble the final prompt: engine preamble (run params + item/index +
301
+ * declared-input artifacts, all as structured JSON context) + the step's
302
+ * BYTE-EXACT prose instructions (+ gate feedback on loop re-executions, +
303
+ * schema directive). Instructions are NEVER interpolated (workflow-format-
304
+ * unification, spec §2.3) — data reaches the unit as attached context, not
305
+ * string splices; only the ENGINE's own preamble placeholders are substituted
306
+ * here.
272
307
  */
273
308
  export function buildUnitPrompt(input) {
274
- const { runId, stepId, unitId, params, gateFeedback, schema, instructions } = input;
309
+ const { runId, stepId, unitId, params, itemIndex, item, inputs, gateFeedback, schema, instructions } = input;
275
310
  // Function replacements throughout: a string replacement would interpret
276
311
  // GetSubstitution patterns ($&, $$, $', $`) inside VALUES and silently
277
312
  // corrupt the prompt (e.g. a param value containing "$&").
@@ -280,6 +315,15 @@ export function buildUnitPrompt(input) {
280
315
  .replaceAll("{{STEP_ID}}", () => stepId)
281
316
  .replaceAll("{{UNIT_ID}}", () => unitId)
282
317
  .replaceAll("{{PARAMS_JSON}}", () => safeJson(params));
318
+ // Map-unit context: the item this unit was given, plus its index. Attached
319
+ // as structured JSON — the engine never splices it into the instructions.
320
+ const itemBlock = itemIndex !== undefined
321
+ ? `\n\n## Item (index ${itemIndex})\nYou were given this item from the fan-out list:\n${safeJson(item)}`
322
+ : "";
323
+ // Declared `inputs:` context: the prior-step artifacts this step named.
324
+ const inputsBlock = inputs && inputs.length > 0
325
+ ? `\n\n## Declared inputs\n${inputs.map((i) => `### ${i.reference}\n${safeJson(i.value)}`).join("\n\n")}`
326
+ : "";
283
327
  // Gate-loop feedback (R2 max_loops): the judge's rejection is appended so
284
328
  // the re-executed unit can address it — and so the input hash changes,
285
329
  // making the loop's re-dispatch natural instead of a durable-row reuse.
@@ -294,7 +338,7 @@ export function buildUnitPrompt(input) {
294
338
  const schemaDirective = schema
295
339
  ? `\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${safeJson(schema)}`
296
340
  : "";
297
- return `${preamble}\n${instructions}${gateBlock}${schemaDirective}`;
341
+ return `${preamble}\n${instructions}${itemBlock}${inputsBlock}${gateBlock}${schemaDirective}`;
298
342
  }
299
343
  /**
300
344
  * Content-derived unit identity (module doc): `<node_id>:<hash12>` for a
@@ -376,18 +420,17 @@ function unitOutputValue(unit) {
376
420
  return unit.text ?? null;
377
421
  }
378
422
  export function buildEvidence(units, reducer, isFanOut) {
379
- // Per-unit evidence is the DURABLE, surface-independent projection the two
380
- // driver surfaces (engine + brief/report) must agree on byte-for-byte (R4
381
- // conformance, "identical unit graph"). It therefore carries ONLY fields both
382
- // surfaces can reproduce from the journal:
423
+ // Per-unit evidence is the DURABLE projection of the unit graph — a fresh run
424
+ // and a resumed run of the same plan must agree on it byte-for-byte. It
425
+ // therefore carries ONLY fields that can be reproduced from the journal alone:
383
426
  // - a SUCCESS keeps its promoted contribution (structured `result` or clipped
384
- // `text`) — the report path rehydrates exactly these from the unit row;
427
+ // `text`) — the reuse path rehydrates exactly these from the unit row;
385
428
  // - a FAILURE keeps only its `failureReason` (the durable, journaled failure
386
- // vocabulary). The engine's in-memory dispatch diagnostic (`error`) and any
387
- // residual `text` on a failed unit are NOT persisted here: a driver-reported
388
- // failure carries neither, so persisting them on the engine surface alone
389
- // would diverge the durable graph. The full raw text/reason still lives on
390
- // the unit row for engine-side diagnostics; this is the shared graph.
429
+ // vocabulary). The in-memory dispatch diagnostic (`error`) and any residual
430
+ // `text` on a failed unit are NOT persisted here: they do not survive a
431
+ // restart, so persisting them on the live-dispatch path alone would make
432
+ // the durable graph depend on WHEN it was built. The full raw text/reason
433
+ // still lives on the unit row for diagnostics; this is the shared graph.
391
434
  const collected = units.map((u) => u.ok
392
435
  ? {
393
436
  unitId: u.unitId,
@@ -478,13 +521,11 @@ export function reduceStepOutcomes(plan, reducer, isFanOut, onError, units) {
478
521
  * resolution rather than silently reading the envelope). Even the degenerate
479
522
  * artifact must honor the step's declared `outputSchema` before it can complete.
480
523
  *
481
- * Shared by native dispatch (`executeStepPlan`'s `items.length === 0` branch)
482
- * and the R3 driver protocol (`report` auto-completes an empty step the spine
483
- * reaches, since no `report --unit` can ever advance a zero-unit step) so both
484
- * surfaces promote the SAME artifact and apply the SAME schema verdict — the
485
- * anti-drift guarantee. Deliberately does NOT run the reducer/vote-tie logic:
486
- * an empty step has no successful results to count, and a vote-tie "failure"
487
- * would diverge from the engine's long-standing empty-list semantics.
524
+ * Used by native dispatch (`executeStepPlan`'s `items.length === 0` branch): a
525
+ * zero-unit step can never be advanced by a unit completion, so it is promoted
526
+ * here instead. Deliberately does NOT run the reducer/vote-tie logic: an empty
527
+ * step has no successful results to count, and a vote-tie "failure" would
528
+ * diverge from the engine's long-standing empty-list semantics.
488
529
  */
489
530
  export function reduceEmptyStep(plan, reducer) {
490
531
  const evidence = { units: [], itemCount: 0, output: reducer === "collect" ? [] : null };
@@ -498,12 +539,13 @@ export function reduceEmptyStep(plan, reducer) {
498
539
  };
499
540
  }
500
541
  /**
501
- * Rehydrate a journaled unit row into a {@link UnitOutcome}. Shared by the
502
- * executor's durable-row reuse (`native-executor.ts`, completed rows only) and
503
- * the R3 report path (which reduces completed AND failed rows replayed from the
504
- * journal). A completed row's text unit journals its output as a JSON string; a
505
- * schema unit journals the validated structure. A failed row carries its
506
- * `failure_reason`; any journaled text is surfaced too.
542
+ * Rehydrate a journaled unit row into a {@link UnitOutcome}. The executor's
543
+ * durable-row reuse (`native-executor.ts`) calls it for completed rows; the
544
+ * failed-row branch keeps the mapping TOTAL, so any reduction driven off the
545
+ * journal yields the same outcome the live dispatch produced. A completed row's
546
+ * text unit journals its output as a JSON string; a schema unit journals the
547
+ * validated structure. A failed row carries its `failure_reason`; any journaled
548
+ * text is surfaced too.
507
549
  */
508
550
  export function unitOutcomeFromRow(unitId, row, hasSchema) {
509
551
  let parsed;
@@ -536,97 +578,6 @@ export function unitOutcomeFromRow(unitId, row, hasSchema) {
536
578
  ...(row.tokens !== null ? { tokens: row.tokens } : {}),
537
579
  };
538
580
  }
539
- /**
540
- * Select the journaled attempt row that determines a unit's TERMINAL outcome on
541
- * a REPLAY surface — the engine's durable-row reuse AND the harness-neutral
542
- * brief/report driver protocol — given the run's dispatch rows indexed by
543
- * unit_id. This is the ONE place all surfaces resolve "which journaled row IS
544
- * this unit's outcome," so they cannot drift from each other or from the engine.
545
- *
546
- * It mirrors the executor's {@link classifyUnitReuse} attempt scan
547
- * (native-executor.ts): among the base attempt and its `~r<n>` retries — all
548
- * stacked on `journalBaseId`, which already carries the active `~l<loop>` gate
549
- * suffix — the FIRST completed attempt is the effective result. So a unit whose
550
- * base attempt FAILED but whose later retry COMPLETED reduces as COMPLETED,
551
- * exactly like an engine resume reusing the `~r1` row (Codex round-3 finding C);
552
- * reading only the base row would reduce it as failed and diverge the two
553
- * surfaces. With no completed attempt the HIGHEST journaled attempt stands (a
554
- * terminal failure, or a still-running row); no attempt row at all ⇒ `undefined`
555
- * (the unit is still outstanding).
556
- */
557
- export function selectUnitAttemptRow(workUnit, dispatchRows) {
558
- const base = workUnit.journalBaseId;
559
- const maxAttempts = 1 + Math.max(0, workUnit.retry?.max ?? 0);
560
- let fallback;
561
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
562
- const row = dispatchRows.get(attempt === 0 ? base : `${base}~r${attempt}`);
563
- if (!row)
564
- continue;
565
- if (row.status === "completed")
566
- return row;
567
- fallback = row; // remember the highest journaled (non-completed) attempt
568
- }
569
- return fallback;
570
- }
571
- /**
572
- * Is a FAILED unit still RETRY-ELIGIBLE — i.e. NOT terminal, because a driver
573
- * could still re-run it via the `--rerun` form (the engine's automatic
574
- * `<baseId>~r<n>` retry)? A unit whose declared `retry.on` matches the recorded
575
- * failure reason AND whose attempt budget (`1 + retry.max`) is not yet spent can
576
- * still be re-run. No `retry`, an off-list reason, or an exhausted attempt budget
577
- * ⇒ the failure IS terminal. Shared by the report fail-fast decision, the
578
- * `--settle` refusal, and `brief`'s fully-terminal detection so all three agree
579
- * on when a failed unit is genuinely done vs. still re-runnable. The normalized
580
- * failure reason is compared against `retry.on` directly (a canonical taxonomy
581
- * reason is stored verbatim; an `external:*` reason is by construction outside
582
- * the taxonomy `retry.on` lists).
583
- */
584
- export function isRetryEligibleFailure(workUnit, row, failureReason) {
585
- const retry = workUnit.retry;
586
- if (!retry || failureReason === null || !retry.on.includes(failureReason))
587
- return false;
588
- const attempts = row?.attempts ?? 1;
589
- return attempts < 1 + Math.max(0, retry.max);
590
- }
591
- /**
592
- * Does a resolvable unit still need a driver to execute + report it (or re-run
593
- * it)? True for a unit with no terminal row (pending), a still-`running` row (a
594
- * live/stale claim another driver holds), or a FAILED row that is still
595
- * retry-eligible. False for a COMPLETED row, a terminal non-retry-eligible
596
- * FAILURE, or an UNRESOLVABLE unit (the engine's immediate `expression_error` —
597
- * never reportable). The best terminal attempt (base + `~r<n>` retries) is the
598
- * one consulted, the SAME reuse the engine and reducer apply.
599
- */
600
- export function unitStillNeedsReport(workUnit, dispatchRows) {
601
- if (!workUnit.resolved.ok)
602
- return false;
603
- const row = selectUnitAttemptRow(workUnit, dispatchRows);
604
- if (!row)
605
- return true; // no journal row → pending
606
- if (row.status === "running")
607
- return true; // a live/stale claim is still in flight
608
- if (row.status === "failed")
609
- return isRetryEligibleFailure(workUnit, row, row.failure_reason);
610
- return false; // completed (or a non-retry-eligible failure) → terminal
611
- }
612
- /**
613
- * Is the active step's work-list FULLY TERMINAL — every resolvable unit run to a
614
- * terminal (done, or non-retry-eligible failed) state with nothing left to
615
- * execute or per-unit report — yet still needing finalization? This is the
616
- * driver-recovery state after a required-gate block is resumed, or a crash
617
- * between the last unit write and the step's completion (owner manual-validation
618
- * finding 3): the work-list is done but the step never advanced. `brief`
619
- * surfaces it with a single `report --settle` command and `--settle` runs the
620
- * shared completion path for it. A list with ANY outstanding unit (pending,
621
- * in-flight, or retry-eligible failed) is NOT fully terminal — the driver
622
- * `report --unit`s those. A route-only / empty / all-unresolvable list (no
623
- * resolvable units) is a DIFFERENT non-dispatching state, handled separately.
624
- */
625
- export function isWorkListFullyTerminal(workList, dispatchRows) {
626
- if (!workList.units.some((u) => u.resolved.ok))
627
- return false;
628
- return workList.units.every((u) => !unitStillNeedsReport(u, dispatchRows));
629
- }
630
581
  /** Stable stringify (sorted object keys, recursively) so equal values vote together. */
631
582
  export function canonicalJson(value) {
632
583
  return JSON.stringify(sortKeys(value));
@@ -647,9 +598,9 @@ function sortKeys(value) {
647
598
  // `{ complete: false, missing, feedback }` (see journalGateEvaluationFinish).
648
599
  // The feedback stored there is BYTE-IDENTICAL to what the engine threads into
649
600
  // the next loop's prompts — both are the same `rejection.feedback`/`.missing`.
650
- // `brief` recovers it from the journal so its loop-N work-list matches the
651
- // engine's (redesign addendum R3, task item 2). `native-executor.test.ts`
652
- // asserts the round-trip identity.
601
+ // A resume recovers it from the journal so its loop-N work-list (and therefore
602
+ // every unit id and input hash in it) matches the one the original run built.
603
+ // `native-executor.test.ts` asserts the round-trip identity.
653
604
  // GATE_EVALUATION_PHASE moved to ../runtime/unit-phases.ts (leaf) so
654
605
  // unit-checkin can key on it without closing the exec ↔ runtime cycle.
655
606
  /** The unit id of a step's gate-evaluation row for a given 1-based loop. */
@@ -714,9 +665,10 @@ function gateLoopOf(unitId, stepId) {
714
665
  /**
715
666
  * Classify a gate-evaluation row's journaled verdict, failing LOUDLY on a
716
667
  * corrupt one (reviewer #17). A NULL `result_json` is the LEGITIMATE
717
- * errored-judge / in-flight shape (`journalGateEvaluationFinish` writes null for
718
- * an errored judge, and a `running` row has no verdict yet) and classifies as
719
- * `empty`. But a PRESENT `result_json` that does not parse as JSON, or parses to
668
+ * completion-error / in-flight shape (`journalGateEvaluationFinish` writes null
669
+ * if completion itself throws after judge invocation, and a `running` row has no
670
+ * verdict yet) and classifies as `empty`. But a PRESENT `result_json` that does
671
+ * not parse as JSON, or parses to
720
672
  * anything other than an object with a boolean `complete` field, is corruption —
721
673
  * a truncated or hand-edited row — and MUST NOT be silently treated as absent
722
674
  * (which would reset an active step's gate loop to 1 and re-dispatch work whose
@@ -764,7 +716,7 @@ export async function journalGateEvaluationStart(gate) {
764
716
  // Marks the row as a judge call, NOT a dispatch: the budget/lifetime
765
717
  // seed in `driveRun` skips these so resume accounting matches live.
766
718
  phase: GATE_EVALUATION_PHASE,
767
- runner: "llm",
719
+ runner: gate.runner,
768
720
  engine: gate.invocation.engine,
769
721
  model: gate.invocation.model,
770
722
  inputHash: gate.inputHash,
@@ -780,17 +732,14 @@ export async function journalGateEvaluationStart(gate) {
780
732
  * Finish the gate-evaluation unit row with the verdict as observed from the
781
733
  * completion outcome: a rejection journals `{ complete: false, missing,
782
734
  * feedback }`; a pass journals `{ complete: true, missing: [] }`; a judge that
783
- * threw (or, on a required gate, returned an unparseable verdict) journals a
784
- * failed row with a NULL verdict. A NON-required errored gate then fails OPEN
785
- * inside `validateStepSummary`; a REQUIRED errored gate BLOCKS the step
786
- * (`finalizeExecutedStep`, Codex round-3 finding A).
735
+ * threw journals a failed row with the synthesized fail-closed rejection.
787
736
  */
788
737
  export async function journalGateEvaluationFinish(gate, errored, rejection) {
789
738
  const unitId = gateUnitId(gate.stepId, gate.loop);
790
- const verdict = errored
791
- ? null
792
- : rejection
793
- ? { complete: false, missing: rejection.missing, feedback: rejection.feedback }
739
+ const verdict = rejection
740
+ ? { complete: false, missing: rejection.missing, feedback: rejection.feedback }
741
+ : errored
742
+ ? null
794
743
  : { complete: true, missing: [] };
795
744
  const status = errored ? "failed" : "completed";
796
745
  await enqueueUnitWrite(() => withWorkflowRunsRepo((repo) => repo.finishUnit({
@@ -814,7 +763,7 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
814
763
  * comparison is exact string equality against the declared `when:` matches.
815
764
  */
816
765
  export function evaluateRoute(route, scope) {
817
- const resolved = resolveWholeValue(route.input, scope);
766
+ const resolved = resolveReferenceString(route.input, scope);
818
767
  if (!resolved.ok) {
819
768
  return { ok: false, error: `route input ${route.input} failed to resolve: ${resolved.error.message}` };
820
769
  }
@@ -910,26 +859,6 @@ function assertRouteTargetDeclared(route, stepId, selected, runId) {
910
859
  `apply a bogus route decision that would skip the real branch targets. Start a new run.`);
911
860
  }
912
861
  }
913
- /**
914
- * Validate every COMPLETED route step's journaled selection against its declared
915
- * targets (reviewer #7). Read-only: it throws on a PRESENT-but-invalid selection
916
- * and is silent on an absent one, so it never false-positives on a healthy run —
917
- * making it safe to call from the read-only `brief` surface as well as the
918
- * resume/report surfaces that already re-apply the decisions.
919
- */
920
- export function assertJournaledRouteSelectionsValid(plan, state) {
921
- for (const stepPlan of plan.steps) {
922
- if (!stepPlan.route)
923
- continue;
924
- const stepState = state.workflow.steps.find((s) => s.id === stepPlan.stepId);
925
- if (!stepState || stepState.status !== "completed")
926
- continue;
927
- const selected = journaledRouteSelection(stepState.evidence);
928
- if (selected !== undefined) {
929
- assertRouteTargetDeclared(stepPlan.route, stepPlan.stepId, selected, state.run.id);
930
- }
931
- }
932
- }
933
862
  /**
934
863
  * Replay journaled route decisions into the skip bookkeeping (resume path).
935
864
  * For every COMPLETED route step of the frozen plan, in spine order: the
@@ -972,7 +901,7 @@ export function seedJournaledRouteDecisions(plan, state, routeSelected, routeUns
972
901
  if (selected === undefined) {
973
902
  throw new UsageError(`Workflow run ${state.run.id} has a completed route step "${stepPlan.stepId}" with no journaled route ` +
974
903
  `decision, and the decision cannot be re-derived from the journaled evidence. Refusing to guess which ` +
975
- `branch was selected — advance the remaining steps manually with \`akm workflow complete\`.`);
904
+ `branch was selected. The run journal is inconsistent; abandon this run and start a new one.`);
976
905
  }
977
906
  applyRouteDecision(stepPlan.route, stepPlan.stepId, selected, routeSelected, routeUnselected);
978
907
  }
@@ -1020,23 +949,6 @@ export async function finalizeExecutedStep(input) {
1020
949
  // A frozen plan either supplies its judge at the dispatch boundary or has no
1021
950
  // judge. Re-selecting defaults here would let config drift change a run.
1022
951
  const innerJudge = input.summaryJudge ?? null;
1023
- // Reviewer #18: a REQUIRED completion gate must actually be judged. When the
1024
- // gate carries criteria but no judge is available, `validateStepSummary` would
1025
- // fail OPEN and silently pass the gate — exactly the offline/misconfigured
1026
- // bypass a required gate exists to prevent. BLOCK the step instead (a human
1027
- // resolves it via the documented manual path), rather than advance the spine
1028
- // on an unjudged gate. `gate.required` rides the frozen plan (both surfaces);
1029
- // `requireGates` is the engine's run-wide `--require-gates` override. Checked
1030
- // BEFORE route evaluation so a blocked step journals no route decision.
1031
- const gateRequired = stepPlan.gate.required === true || input.requireGates === true;
1032
- if (gateRequired && completionCriteria.length > 0 && innerJudge === null) {
1033
- const notes = `Step "${stepId}" has a REQUIRED completion gate but no summary-validation judge is available ` +
1034
- `(no LLM is configured, or default LLM resolution failed). A required gate must be judged — refusing to fail ` +
1035
- `open and silently pass it. The step is BLOCKED: configure an LLM, then \`akm workflow resume ${runId}\` to ` +
1036
- `re-evaluate the gate, or advance the step manually with \`akm workflow complete\`.`;
1037
- await completeWorkflowStep({ runId, stepId, status: "blocked", notes, evidence: result.evidence, ...lease });
1038
- return { kind: "blocked", summary: notes };
1039
- }
1040
952
  // Route evaluation BEFORE completion: an unroutable value is an
1041
953
  // authoring/config failure that must fail the step deterministically.
1042
954
  let summaryOverride;
@@ -1065,9 +977,9 @@ export async function finalizeExecutedStep(input) {
1065
977
  const summary = stepPlan.root && completionCriteria.length > 0
1066
978
  ? buildArtifactSummary(stepId, result.units, result.evidence)
1067
979
  : (summaryOverride ?? result.summary);
1068
- // Journal engine-driven judge calls as unit rows (they are LLM calls). The
1069
- // wrapper's `invoked` stays false when the gate is fail-open (no criteria / no
1070
- // judge) — nothing is journaled, and human approvals are never cached.
980
+ // Journal engine-driven judge calls as unit rows. With no criteria there is
981
+ // no judge invocation or row; a criteria-bearing plan without a judge is a
982
+ // configuration error rather than a silent bypass.
1071
983
  const frozenGate = innerJudge
1072
984
  ? await withWorkflowRunsRepo((repo) => {
1073
985
  const row = repo.getRunById(runId);
@@ -1091,6 +1003,7 @@ export async function finalizeExecutedStep(input) {
1091
1003
  stepId,
1092
1004
  loop: gateLoop,
1093
1005
  invocation: gateInvocation,
1006
+ runner: frozenGate?.engine?.kind === "agent" ? frozenGate.engine.runnerKind : "llm",
1094
1007
  inputHash: createHash("sha256")
1095
1008
  .update(canonicalJsonString({
1096
1009
  hashVersion: 3,
@@ -1114,8 +1027,7 @@ export async function finalizeExecutedStep(input) {
1114
1027
  // Reviewer #6: once the judge is invoked, its gate row is journaled `running`
1115
1028
  // (journalGateEvaluationStart) and MUST be finished on every exit. The
1116
1029
  // already-fixed window is the judge itself throwing (caught inside
1117
- // validateStepSummary — `judgeState.errored` records it; a non-required gate
1118
- // fails open, a required gate blocks below). The remaining
1030
+ // validateStepSummary — `judgeState.errored` records it). The remaining
1119
1031
  // window is `completeWorkflowStep` throwing AFTER the judge ran — a stolen
1120
1032
  // lease, a concurrent state change, a DB error — which would otherwise skip the
1121
1033
  // finish and strand the gate row in `running`. Finish it as an errored row (the
@@ -1129,10 +1041,7 @@ export async function finalizeExecutedStep(input) {
1129
1041
  summary,
1130
1042
  evidence: result.evidence,
1131
1043
  summaryJudge,
1132
- // Codex round-3 finding A: mark this completion's gate REQUIRED so
1133
- // `validateStepSummary` does NOT fail open when the judge throws / is
1134
- // unreachable / returns garbage — it flags `errored` and we block below.
1135
- ...(gateRequired ? { requireGate: true } : {}),
1044
+ ...(input.signal ? { signal: input.signal } : {}),
1136
1045
  ...lease,
1137
1046
  });
1138
1047
  }
@@ -1142,25 +1051,8 @@ export async function finalizeExecutedStep(input) {
1142
1051
  throw err;
1143
1052
  }
1144
1053
  const rejection = "ok" in completion && completion.ok === false ? completion : undefined;
1145
- // A required gate whose judge could not be evaluated is an errored gate, not a
1146
- // real rejection: journal the gate row as errored (verdict null) so the
1147
- // observed outcome is honest, driven by EITHER the wrapper catching a throw OR
1148
- // validateStepSummary flagging an unparseable verdict.
1149
- const gateErrored = judgeState.errored || rejection?.errored === true;
1150
1054
  if (gateUnit) {
1151
- await journalGateEvaluationFinish(gateUnit, gateErrored, rejection);
1152
- }
1153
- // Codex round-3 finding A: a REQUIRED gate that could not be judged (the judge
1154
- // threw, was unreachable, or returned an unparseable verdict) must NOT fail
1155
- // open and advance. The gate row is journaled errored above; BLOCK the step (a
1156
- // human resolves it) instead of silently passing an unjudged required gate.
1157
- if (rejection?.errored) {
1158
- const notes = `Step "${stepId}" has a REQUIRED completion gate but its summary-validation judge failed to return a verdict ` +
1159
- `(the LLM threw, was unreachable, or returned an unparseable response). A required gate must be judged — refusing ` +
1160
- `to fail open and silently pass it. The step is BLOCKED: fix the LLM/connection, then \`akm workflow resume ${runId}\` ` +
1161
- `to re-evaluate the gate, or advance the step manually with \`akm workflow complete\`.`;
1162
- await completeWorkflowStep({ runId, stepId, status: "blocked", notes, evidence: result.evidence, ...lease });
1163
- return { kind: "blocked", summary: notes };
1055
+ await journalGateEvaluationFinish(gateUnit, judgeState.errored, rejection);
1164
1056
  }
1165
1057
  if (!rejection) {
1166
1058
  return { kind: "advanced", ...(summaryOverride !== undefined ? { summaryOverride } : {}) };
@@ -1173,25 +1065,6 @@ export async function finalizeExecutedStep(input) {
1173
1065
  gateRejection: { stepId, missing: rejection.missing, feedback: rejection.feedback },
1174
1066
  };
1175
1067
  }
1176
- // ── Frozen plan parse + integrity check (shared) ─────────────────────────────
1177
- /**
1178
- * Parse and integrity-check a run's frozen plan JSON (migration 006). Shared by
1179
- * the engine loop's plan loader (`run-workflow.ts`) and the R3 brief/report
1180
- * surfaces so all three apply the SAME corruption + hash checks — the frozen
1181
- * plan the engine executes is the exact plan brief describes and report
1182
- * validates against. A NULL `plan_json` is the CALLER's decision (the engine
1183
- * warns and compiles from the asset; brief/report error), so this helper only
1184
- * handles a PRESENT plan string.
1185
- */
1186
- export function parseFrozenPlan(runId, planJson, planHash) {
1187
- try {
1188
- return decodeCanonicalPlan(runId, planJson, planHash);
1189
- }
1190
- catch (cause) {
1191
- throw new UsageError(`Workflow run ${runId} has a corrupt frozen plan: ${cause instanceof Error ? cause.message : String(cause)}. ` +
1192
- `The journaled plan cannot be executed — abandon it and start a new run.`);
1193
- }
1194
- }
1195
1068
  // ── Small helpers ────────────────────────────────────────────────────────────
1196
1069
  function safeJson(value) {
1197
1070
  try {