akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -2,125 +2,37 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * Native executor — executes one frozen step subgraph (`IrStepPlan.root`) on
6
- * the local machine: fan-out through the scheduler, schema-validated
7
- * structured output through `runStructured` (core/structured.ts), per-unit
8
- * persistence through the serialized writer queue, and `workflow_unit_*`
9
- * events for observability.
5
+ * Native executor — executes one frozen step subgraph (`WorkflowPlanStep.root`)
6
+ * locally: fan-out through the scheduler, structured output through
7
+ * `runStructured`, per-unit journaling through the serialized writer queue,
8
+ * and `workflow_unit_*` events. It never writes step rows; advancing the spine
9
+ * is `run-workflow.ts`'s job.
10
10
  *
11
- * Data flow: there is no interpolation language — a unit's instructions are
12
- * the step's body prose byte-exact, and data reaches it as attached
13
- * structured context instead. References resolve once per step, only in the
14
- * closed frontmatter positions, against the promoted step-output artifact.
15
- * See docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md
16
- * for the full design history (peer review R1, the P1 injection class it closes).
17
- *
18
- * Empty free-text outputs (peer review): a SUCCESSFUL schemaless unit that
19
- * returns the empty string is normalized to "no output" — {@link dispatchUnit}
20
- * drops the falsy `text`, `finishUnitAttempt` journals `result_json = NULL`, and
21
- * durable-row reuse rehydrates the same absence (`unitOutcomeFromRow`). This is
22
- * the ONLY empty-output resolution: `''` never survives into the journal, so the
23
- * live artifact cannot diverge from the artifact a resume rebuilds from the same
24
- * rows (the byte-identical-graph cardinal rule). Consequences that follow from
25
- * "empty == absent", not special-cased anywhere:
26
- * - a SOLO empty step promotes `output = null` (the unit's absent text ??
27
- * null); a `collect` fan-out promotes `null` in that item's slot.
28
- * - A downstream `steps.x.output` reference to an empty solo step therefore
29
- * resolves against `null` and fails LOUDLY at reference resolution
30
- * (`… resolved to null`) — a deterministic whole-step failure, never a
31
- * silent empty string.
32
- * - A SCHEMA unit is unaffected by this normalization: an empty response is
33
- * not parseable JSON, so `runStructured` fails it (`parse_error`) — an
34
- * empty output can never satisfy a declared schema as a silent `null`.
35
- *
36
- * Typed artifacts (addendum, R2): when the step declares an `output` schema
37
- * (`IrStepPlan.outputSchema`), the promoted artifact is validated with the
38
- * JSON-schema-subset validator BEFORE the step can complete. A mismatch fails
39
- * the step (fail-fast) with the validation errors in the summary — a
40
- * downstream consumer must never receive an artifact the author's contract
41
- * says cannot exist. The failure is flagged (`artifactSchemaFailure` on the
42
- * result) so the engine's bounded gate loop can re-run the step with the
43
- * validation errors as feedback ("gate loops can re-run it") — a step with
44
- * loop budget left regenerates instead of killing the run.
45
- *
46
- * Unit identity (addendum, R2): CONTENT-DERIVED, never positional. A fan-out
47
- * unit's id is `<node_id>:<sha256(canonicalJson(item))[:12]>`; a solo unit's
48
- * is `<node_id>:solo`. Identity therefore survives item-list regeneration and
49
- * reordering — resuming a run whose producer re-emitted the same items in a
50
- * different order reuses every journaled result. Consequences:
51
- * - DUPLICATE items in one fan-out list collide on identity. That is an
52
- * authoring error (the same work dispatched twice under one id): the step
53
- * fails deterministically after resolving the item list, naming the
54
- * duplicate, before anything dispatches.
55
- * - REPLAY DIVERGENCE: a journaled COMPLETED row whose unit_id matches but
56
- * whose `input_hash` differs is a hard step failure ("replay divergence"),
57
- * never a silent re-dispatch — under a frozen plan the same identity must
58
- * reproduce the same inputs, so a mismatch means the journal (or params
59
- * row) was tampered with. Failed/running/missing rows dispatch live.
60
- * - Rows with unrelated ids never match a content-derived id and are ignored.
61
- *
62
- * Gate loops (addendum, R2 `gate.max_loops`): when the engine re-executes a
63
- * step subgraph after a gate rejection, it threads the judge's feedback in as
64
- * `ctx.gateFeedback` (appended to every unit prompt — the input hash changes,
65
- * so re-dispatch is natural) and marks the attempt with `ctx.gateLoop` (>= 2).
66
- * Loop attempts journal under `<unitId>~l<loop>` — like `~r<n>` retries, pure
67
- * journal bookkeeping on top of the content-derived identity, so loop 1's
68
- * rows are never clobbered. Because gate feedback is JUDGE-authored (a fresh
69
- * LLM output per invocation, not a pure function of the frozen plan), a
70
- * journaled loop row whose hash no longer matches re-dispatches live instead
71
- * of raising replay divergence — the divergence guarantee applies to loop-1
72
- * rows, whose inputs ARE pure functions of (plan, params, journaled results).
73
- *
74
- * Failure policy (addendum, "explicit surface, fail-fast default"):
75
- * - `onError: "fail"` (default) fails the step on any unit failure;
76
- * `"continue"` records failures in the evidence and lets the gate decide.
77
- * - `retry: { max, on }` re-dispatches a failed unit up to `max` extra
78
- * times when its `failureReason` is in `on`. Every retry journals its OWN
79
- * row under `<unitId>~r<attempt>` so no attempt's record is clobbered.
80
- *
81
- * Worktree isolation (addendum, R2 `isolation: worktree`): each journaled
82
- * attempt of an isolated agent/sdk unit runs in a FRESH detached git worktree
83
- * of the engine's working directory (`ctx.workDir`, default `process.cwd()`),
84
- * minted under a run-scoped tmp dir (`worktree.ts`) and passed to dispatch as
85
- * the child's cwd. The path is journaled on the unit row (`worktree_path`);
86
- * after the unit finishes, a clean worktree is removed and a dirty one is
87
- * retained + logged (uncollected work is never destroyed). "Clean" is
88
- * `git status --porcelain` WITHOUT `--ignored`, so a worktree whose only
89
- * residue is `.gitignore`-matched files (build outputs, `node_modules`) counts
90
- * as clean and IS removed — those files are disposable by the repo's own
91
- * declaration, and retaining a worktree per build would blow up disk
92
- * (`worktree.ts` contract). A non-git base directory fails the step cleanly
93
- * before any dispatch, and llm units reject isolation loudly — there is no
94
- * child process to isolate.
95
- *
96
- * Budget ceilings (addendum, R2): a frozen plan's `budget` block
97
- * (`max_units` / `max_tokens`) is enforced per RUN. The engine seeds
98
- * `ctx.unitsDispatched` (journal row count) and `ctx.tokensUsed` (journaled
99
- * token sum) and threads the running totals across steps; this executor
100
- * consumes both per ACTUAL dispatch. Hitting a ceiling aborts pending and
101
- * in-flight dispatches through an AbortController chained onto `ctx.signal`
102
- * and fails the step with a "budget exceeded (<which> ceiling)" summary —
103
- * hard, regardless of `on_error`, exactly like the lifetime cap.
104
- *
105
- * Layering (see the plan's *Reconciliation* section):
106
- * - Dispatch goes through ONE injected {@link UnitDispatcher} seam. The
107
- * default dispatcher adapts the frozen snapshot into the common resolved
108
- * request, lowers it through the registered harness/direct-LLM adapter,
109
- * and reaches transport only through the central lowered-dispatch seam.
110
- * - This module NEVER writes step rows: advancing the gated spine is the
111
- * engine loop's job (`run-workflow.ts`) via `completeWorkflowStep`.
11
+ * - Data flow: instructions are the step's prose byte-exact; data reaches a
12
+ * unit as attached context (docs/architecture/decisions/0001-…).
13
+ * - An empty successful free-text output is journaled as absent, so a live
14
+ * run and a resume promote the same artifact.
15
+ * - A declared step `output` schema is validated before the step completes;
16
+ * a mismatch fails the step with `artifactSchemaFailure`, which the gate
17
+ * loop may retry.
18
+ * - Unit ids are content-derived (`<node>:<sha(item)>` / `<node>:solo`), so a
19
+ * resume reuses every completed row whatever the item order; retries and
20
+ * gate loops journal under `~r<n>` / `~l<n>`.
21
+ * - `onError: "fail"` fails on any unit failure, `"continue"` lets the gate
22
+ * decide; `retry: { max, on }` re-dispatches listed failure reasons.
23
+ * - `isolation: worktree` runs each attempt of an agent/sdk unit in a fresh
24
+ * detached worktree (removed when clean, kept when dirty); llm units reject it.
25
+ * - Run `budget` ceilings are consumed per actual dispatch and abort the step
26
+ * when crossed, regardless of `on_error`.
112
27
  */
113
- import { randomUUID } from "node:crypto";
114
28
  import { appendEvent } from "../../core/events.js";
115
29
  import { validateJsonSchemaSubset } from "../../core/json-schema.js";
116
30
  import { runStructured } from "../../core/structured.js";
117
31
  import { warn } from "../../core/warn.js";
118
32
  import { assertFrozenDirectoryContained } from "../../execution/directory-identity.js";
119
- import { assertFrozenExecutableIdentity } from "../../execution/executable-identity.js";
120
33
  import { withWorkflowRunsConnection, withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
121
- import { materializeFrozenWorkflowEnvironment } from "../ir/environment-v4.js";
122
34
  import { WORKFLOW_UNIT_DIAGNOSTIC_CLIP } from "../resource-limits.js";
123
- // The ONE child-workflow drive (P3b §3.2) — publishes and drives a
35
+ // The ONE child-workflow drive — publishes and drives a
124
36
  // `child-workflow`-targeted unit; this module's dispatch seam is its only
125
37
  // production caller.
126
38
  import { driveChildWorkflowUnit } from "./child-workflow.js";
@@ -128,10 +40,12 @@ import { driveChildWorkflowUnit } from "./child-workflow.js";
128
40
  // (exec/frozen-judge.ts). Consumers import the leaf directly — this module is
129
41
  // not a second front door onto the seam.
130
42
  import { collectWorkflowDispatchSensitiveValues, redactUnitOutcome } from "./dispatch-redaction.js";
43
+ import { materializeFrozenWorkflowEnvironment } from "./environment.js";
131
44
  // The exec (shell) unit runner — a leaf that owns argv spawning, containment,
132
45
  // and the process-outcome → failure-reason mapping.
133
46
  import { runExecUnit } from "./exec-unit.js";
134
47
  import { mergeLoweringNotices } from "./lowering-notices.js";
48
+ import { secretShapedParamValues } from "./param-secrets.js";
135
49
  import { scheduleUnits } from "./scheduler.js";
136
50
  // Shared step semantics — the ONE implementation consumed by the engine
137
51
  // (this module + run-workflow.ts) on both the fresh-execution and the resume
@@ -142,16 +56,9 @@ import { cleanupFrozenScript, frozenScriptCommand, materializeFrozenScript } fro
142
56
  import { enqueueUnitWrite } from "./unit-writer.js";
143
57
  import { assertGitWorkTree, cleanupUnitWorktree, createUnitWorktree } from "./worktree.js";
144
58
  /**
145
- * Mutable per-step dispatch budget: the declared run-level budget ceilings
146
- * (`budget.max_units` / `budget.max_tokens`, addendum R2). Consumed once per
147
- * journaled dispatch attempt (including retries); durable-row reuses never
148
- * touch it — the peer-review fix that keeps large partially-completed
149
- * fan-outs resumable instead of tripping a pre-batch check on
150
- * `journaled + items`. Token usage accumulates per actual dispatch on top of
151
- * the journal-seeded run total (reused rows' tokens are already in the
152
- * seed). Check-and-increment is synchronous, so concurrent units cannot race
153
- * it; crossing a declared ceiling fires `onExceeded` ONCE (the executor's
154
- * chained AbortController), aborting pending and in-flight dispatches.
59
+ * Per-step dispatch budget against the run's `budget` ceilings, seeded from
60
+ * the journal and consumed per actual dispatch (never by a reused row).
61
+ * Crossing a ceiling fires `onExceeded` once, aborting pending dispatches.
155
62
  */
156
63
  class DispatchBudget {
157
64
  used;
@@ -199,30 +106,14 @@ class DispatchBudget {
199
106
  this.onExceeded?.();
200
107
  }
201
108
  }
202
- function classifyUnitReuse(workUnit, completedRows, gateLoop) {
203
- const inputHash = workUnit.inputHash;
109
+ function classifyUnitReuse(workUnit, completedRows) {
204
110
  // Scan EVERY journaled attempt row of this unit (`<base>` / `<base>~r<N>`
205
- // for ANY N), not just the attempts the CURRENT retry policy allows:
206
- // retry/onError are deliberately excluded from the input hash (step-work.ts)
207
- // precisely so completed rows stay valid across policy changes — a run
111
+ // for ANY N), not just the attempts the CURRENT retry policy allows: a run
208
112
  // re-invoked with a lowered retry.max must still find the `~rN` row a prior
209
113
  // invocation completed beyond the new max, never re-dispatch finished work.
210
- // A completed hash-matching row anywhere wins (reuse); a completed loop-1
211
- // row with a DIFFERENT hash — and no matching sibling — is replay divergence.
212
- let divergedAttemptId;
213
- for (const prior of completedRows?.get(workUnit.journalBaseId) ?? []) {
214
- if (prior.input_hash === inputHash)
215
- return { kind: "reuse", row: prior };
216
- // Gate-loop rows are NOT replay-deterministic (the prompt embeds a fresh
217
- // judge output): a stale loop-N row with a different hash re-dispatches
218
- // live. Divergence only guards loop-1 rows, whose inputs ARE a pure
219
- // function of (frozen plan, params, journaled results).
220
- if (gateLoop <= 1)
221
- divergedAttemptId ??= prior.unit_id;
222
- }
223
- if (divergedAttemptId !== undefined)
224
- return { kind: "diverge", attemptId: divergedAttemptId };
225
- return { kind: "dispatch" };
114
+ // The row's `input_hash` is informational — resume skips completed units.
115
+ const prior = completedRows?.get(workUnit.journalBaseId)?.[0];
116
+ return prior ? { kind: "reuse", row: prior } : { kind: "dispatch" };
226
117
  }
227
118
  function indexCompletedRows(rows) {
228
119
  const index = new Map();
@@ -239,37 +130,16 @@ function indexCompletedRows(rows) {
239
130
  return index;
240
131
  }
241
132
  /**
242
- * Execute one step plan natively. Never throws for unit-level failures.
243
- *
244
- * The whole step runs inside ONE state.db connection scope
245
- * ({@link withWorkflowRunsConnection}): the journal read, every unit's
246
- * insert/finish transaction, and every `workflow_unit_*` event share a single
247
- * handle for the step's lifetime instead of opening and closing state.db twice
248
- * per unit plus twice per unit's events. The scope closes the handle when the
249
- * step settles (success, failure, or throw), so there is no handle to leak and
250
- * no lifetime that outlives the step. Everything inside keeps its existing
251
- * transaction boundaries — see `core/state-db-scope.ts` for why sharing a
252
- * handle across concurrently-scheduled units is safe here.
133
+ * Execute one step plan natively. Never throws for unit-level failures. The
134
+ * whole step shares one state.db connection scope ({@link withWorkflowRunsConnection}).
253
135
  */
254
136
  export function executeStepPlan(plan, ctx) {
255
137
  return withWorkflowRunsConnection(() => executeStepPlanInConnection(plan, ctx));
256
138
  }
257
139
  /**
258
- * Open the step's dispatch budget and the abort signal it trips.
259
- *
260
- * Budget ceilings (addendum R2): when the frozen plan declares a budget,
261
- * dispatch runs under an AbortController CHAINED onto `ctx.signal` — hitting a
262
- * ceiling aborts pending and in-flight dispatches, and the step fails hard.
263
- * Without a budget the context signal passes through untouched (the no-budget
264
- * path is byte-identical to pre-R2 behavior).
265
- *
266
- * The returned {@link DispatchBudget} is seeded with the run's journaled
267
- * dispatch count and token total and consumed per ACTUAL dispatch inside
268
- * `runUnit` — never for durable-row reuses, so resuming a large
269
- * partially-completed fan-out works.
270
- *
271
- * `unchainSignal` MUST be called when dispatch finishes (the caller's
272
- * `finally`) so the upstream abort listener is removed.
140
+ * Open the step's dispatch budget and the abort signal it trips: with a
141
+ * declared budget, dispatch runs under an AbortController chained onto
142
+ * `ctx.signal`. The caller must call `unchainSignal` when dispatch finishes.
273
143
  */
274
144
  function openDispatchBudget(ctx, dispatched) {
275
145
  const declaredBudget = ctx.budget && (ctx.budget.maxUnits !== undefined || ctx.budget.maxTokens !== undefined) ? ctx.budget : undefined;
@@ -358,13 +228,8 @@ async function prepareStepDispatchPrerequisites(input) {
358
228
  }
359
229
  async function executeStepPlanInConnection(plan, ctx) {
360
230
  const dispatched = ctx.unitsDispatched ?? 0;
361
- // Work-list computation is the SHARED, PURE decision (step-work.ts): resolve
362
- // the fan-out list, derive content-derived unit ids, assemble each unit's
363
- // prompt, and hash its resolved input. A resume recomputes the identical list
364
- // from the same frozen plan — that shared pure implementation is what lets
365
- // journaled rows be matched instead of re-executed. This module owns only the
366
- // impure remainder: env/worktree preflight, durable-row reuse, dispatch,
367
- // journaling, budget.
231
+ // The work list is the shared pure decision (step-work.ts), so a resume
232
+ // recomputes the identical list and matches journaled rows.
368
233
  const workList = computeStepWorkList(plan, {
369
234
  runId: ctx.runId,
370
235
  params: ctx.params,
@@ -384,26 +249,12 @@ async function executeStepPlanInConnection(plan, ctx) {
384
249
  return { ...reduceEmptyStep(plan, reducer), unitsDispatched: dispatched };
385
250
  }
386
251
  const dispatcher = ctx.dispatcher ?? defaultUnitDispatcher;
387
- // Durable-row resume: load the step's journaled unit rows FIRST — before
388
- // resolving env or preflighting worktrees. A unit whose previous attempt
389
- // completed with the SAME input hash (the canonical envelope in step-work.ts)
390
- // is reused, not re-dispatched — a crash-resume must never double-issue
391
- // side-effecting work. Loading the rows up front is what lets us skip the
392
- // dispatch prerequisites below when nothing will actually dispatch.
252
+ // Load journaled rows first: a completed unit is reused, never re-dispatched.
393
253
  const completedRows = indexCompletedRows(await withWorkflowRunsRepo((repo) => repo.getUnitsForStep(ctx.runId, plan.stepId)));
394
- // Reviewer finding #2: env resolution and worktree preflight are DISPATCH
395
- // prerequisites, so they must run only when a unit will actually dispatch. A
396
- // fully-journaled step whose units all reuse completed rows must resume to
397
- // completion even if an env asset was deleted, a secret is unavailable, the
398
- // cwd is no longer a git worktree, or git is missing — none of that is needed
399
- // to hand back a cached result. The predicate mirrors runUnit's reuse
400
- // decision exactly (shared classifyUnitReuse).
401
- const gateLoop = ctx.gateLoop ?? 1;
402
- // Classify every unit ONCE. The gate below and each unit's own dispatch then
403
- // read the SAME decision rather than recomputing it from inputs that must be
404
- // identical — the agreement the gate depends on is structural, not a property
405
- // two call sites have to keep re-establishing.
406
- const reuseDecisions = workUnits.map((unit) => classifyUnitReuse(unit, completedRows, gateLoop));
254
+ // Env resolution and worktree preflight run only when some unit will
255
+ // dispatch, so a fully-journaled step resumes even if an env asset or git is
256
+ // gone. Every unit is classified once; the preflight and dispatch share it.
257
+ const reuseDecisions = workUnits.map((unit) => classifyUnitReuse(unit, completedRows));
407
258
  const willDispatch = reuseDecisions.some((decision) => decision.kind === "dispatch");
408
259
  const prerequisites = await prepareStepDispatchPrerequisites({
409
260
  plan,
@@ -454,9 +305,9 @@ async function executeStepPlanInConnection(plan, ctx) {
454
305
  await Promise.allSettled(pendingWorktreeCleanups);
455
306
  }
456
307
  // Capture live-only diagnostics BEFORE any hard reduction replaces the unit
457
- // list with a failed-step envelope. Budget/cap, replay divergence, and
458
- // journal-write failures must not erase notices already observed from real
459
- // dispatches; durable row reuses naturally contribute none.
308
+ // list with a failed-step envelope. Budget/cap and journal-write failures
309
+ // must not erase notices already observed from real dispatches; durable row
310
+ // reuses naturally contribute none.
460
311
  const notices = mergeLoweringNotices(...outcomes.map((outcome) => outcome?.notices));
461
312
  // A declared budget ceiling is a hard backstop: a step that hit one FAILS
462
313
  // regardless of on_error policy (a capped run must never quietly pass its
@@ -470,21 +321,8 @@ async function executeStepPlanInConnection(plan, ctx) {
470
321
  failureReason: "aborted",
471
322
  error: "unit was not dispatched (aborted or scheduler failure)",
472
323
  });
473
- // Replay divergence is a HARD failure regardless of on_error: a journal
474
- // whose completed row disagrees with the frozen plan's inputs must stop the
475
- // run loudly (module doc), never be tolerated as "just a failed unit".
476
- const diverged = units.filter((u) => u.failureReason === "replay_divergence");
477
- if (diverged.length > 0) {
478
- return failedStep(budget.used, diverged
479
- .map((u) => u.error ?? `replay divergence: unit "${u.unitId}" was journaled with different inputs`)
480
- .join(" "), notices);
481
- }
482
- // A journal-write failure is likewise HARD regardless of on_error: the
483
- // unit dispatched (spent tokens, ran side effects) but its result could not
484
- // be persisted, so completing the step would promote an artifact the
485
- // journal cannot rebuild on resume — and the stuck-`running` row would
486
- // wedge or double-dispatch a later invocation. The summary carries the
487
- // per-unit cause verbatim.
324
+ // A journal-write failure fails the step regardless of on_error: the unit ran
325
+ // but its result could not be persisted, so no artifact can be promoted.
488
326
  const unjournaled = units.filter((u) => u.failureReason === "journal_write_failed");
489
327
  if (unjournaled.length > 0) {
490
328
  return {
@@ -492,15 +330,7 @@ async function executeStepPlanInConnection(plan, ctx) {
492
330
  tokensUsed: budget.tokens,
493
331
  };
494
332
  }
495
- // Failure policy + reducer + typed-artifact validation are the SHARED
496
- // post-dispatch decision (`reduceStepOutcomes`): `onError: "fail"` (default)
497
- // fails the step on any unit failure, `"continue"` records failures and lets
498
- // the gate decide, a vote reducer with no majority fails under either policy,
499
- // and the promoted artifact is validated against the step's declared output
500
- // schema (fail-fast; the `artifactSchemaFailure` marker lets the bounded gate
501
- // loop retry that ONE failure class with the errors as feedback). The report
502
- // path (R3) reduces journal-replayed outcomes through the same function, so a
503
- // step promotes the SAME artifact whichever surface drove it.
333
+ // Failure policy, reducer, and artifact schema check: the shared `reduceStepOutcomes`.
504
334
  const reduced = reduceStepOutcomes(plan, reducer, isFanOut, template.onError, units);
505
335
  return {
506
336
  ...reduced,
@@ -539,9 +369,9 @@ async function runUnit(input) {
539
369
  ...(env ? { env } : {}),
540
370
  ...(sensitiveValues ? { sensitiveValues } : {}),
541
371
  ...(input.signal ? { signal: input.signal } : {}),
542
- // F-1 (spec §5.2 point 2): forwarded to exec-unit.ts's childEnv for a
372
+ // F-1: forwarded to exec-unit.ts's childEnv for a
543
373
  // "script"/"shell" unit, and to dispatchWorkflowExecution's
544
- // dispatchLoweredExecutionRequest eventSource option (unit-dispatch.ts)
374
+ // runExecution eventSource option (unit-dispatch.ts)
545
375
  // for a "command" unit — both arms observe it.
546
376
  ...(ctx.eventSource !== undefined ? { eventSource: ctx.eventSource } : {}),
547
377
  };
@@ -553,14 +383,9 @@ async function runUnit(input) {
553
383
  const attemptIdFor = (_attempt) => journalBaseId;
554
384
  // Durable-row reuse — literally the decision executeStepPlan's preflight gate
555
385
  // counted, handed down rather than recomputed, so the gate cannot disagree
556
- // with what happens here. A completed row with the matching input hash IS the
557
- // result: return it without touching rows, dispatching, or re-emitting events
558
- // (a crash-resume must never double-issue work). A completed loop-1 row with
559
- // a DIFFERENT hash is replay divergence (under a frozen plan the same
560
- // content-derived identity must reproduce the same inputs — the journal was
561
- // tampered with; executeStepPlan promotes this to a hard step failure
562
- // regardless of on_error). Stale gate-loop rows, failed/running/missing rows,
563
- // and pre-release R1 positional ids all fall through and dispatch live.
386
+ // with what happens here. A completed row IS the result: return it without
387
+ // touching rows, dispatching, or re-emitting events (a crash-resume must
388
+ // never double-issue work). Failed/running/missing rows dispatch live.
564
389
  const reuse = input.reuse;
565
390
  if (reuse.kind === "reuse") {
566
391
  // Identity in the durable step evidence is the CONTENT-derived base id, not
@@ -568,15 +393,6 @@ async function runUnit(input) {
568
393
  // base ids, so evidence.units[].unitId stays stable across retries+resumes.
569
394
  return reuseCompletedUnit(unitId, reuse.row, workUnit.schema !== undefined);
570
395
  }
571
- if (reuse.kind === "diverge") {
572
- return {
573
- unitId,
574
- ok: false,
575
- failureReason: "replay_divergence",
576
- error: `replay divergence: unit "${reuse.attemptId}" was journaled with different inputs ` +
577
- `(journaled input_hash does not match this invocation's) — refusing to re-dispatch.`,
578
- };
579
- }
580
396
  let outcome;
581
397
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
582
398
  if (input.signal?.aborted) {
@@ -614,7 +430,7 @@ async function runUnit(input) {
614
430
  // Attempts use `~r<n>` journal suffixes while durable step evidence remains
615
431
  // attached to the content-derived base identity.
616
432
  outcome.unitId = unitId;
617
- // Budget token accounting (addendum R2): every actual dispatch's reported
433
+ // Budget token accounting: every actual dispatch's reported
618
434
  // usage counts against the run's max_tokens ceiling; crossing it aborts
619
435
  // pending dispatches via the chained controller. Reuses never reach here
620
436
  // (their tokens are already in the journal-seeded total).
@@ -632,47 +448,17 @@ async function runUnit(input) {
632
448
  return outcome;
633
449
  }
634
450
  /**
635
- * What a finished attempt writes to `workflow_run_steps`' unit row
636
- * `result_json` — the ONE durable, human-facing surface for a dispatch outcome
637
- * (`akm workflow status --units` reads exactly this, and the step summary is
638
- * built from the same text).
639
- *
640
- * A SUCCESS journals its promoted value, unchanged.
641
- *
642
- * A FAILURE journals its DIAGNOSTIC. Before this, only `result`/`text` were
643
- * written: `outcome.error` — the one field that says WHY — reached nothing
644
- * durable, because `buildEvidence` deliberately drops it from the deterministic
645
- * evidence graph and nothing else persisted it. For an engine unit that mostly
646
- * cost detail; for an `exec` unit it lost the diagnostic entirely, since a
647
- * command that fails and explains itself on stderr with empty stdout left
648
- * `status --units` showing a bare `non_zero_exit`.
649
- *
650
- * Three constraints hold:
651
- *
652
- * - REDACTION — the caller journals only `redactUnitOutcome(...)` output, so
653
- * `error` has already been through the shared dispatch redaction contract
654
- * (`exec/dispatch-redaction.ts`) with this dispatch's resolved `env:`
655
- * values. It is scrubbed by construction, exactly like `text`.
656
- * - BOUNDS — clipped to {@link WORKFLOW_UNIT_DIAGNOSTIC_CLIP}, the same bound
657
- * `status --units` renders with, so a runaway command cannot use the journal
658
- * as its log file.
659
- * - HASHES — `result_json` is an OUTPUT. The unit input hash
660
- * (`computeUnitInputHash`) is computed from plan-frozen INPUTS only
661
- * (template bytes, item, declared inputs, params, dispatch/invocation/exec
662
- * snapshots, env ref names, isolation), and reuse compares the stored
663
- * `input_hash` against that. Nothing here is a hash preimage input, so no
664
- * completed unit re-dispatches because of it.
665
- *
666
- * Partial output on a failed unit is kept ALONGSIDE the diagnostic rather than
667
- * replacing it: a tool that fails after printing its real complaint on stdout
668
- * is common, and the reason lives on whichever stream that tool chose.
451
+ * What an attempt journals as the unit row's `result_json` (what `status
452
+ * --units` shows): a success's promoted value, or a failure's diagnostic plus
453
+ * any partial output. The caller passes an already-redacted outcome; clipped
454
+ * to {@link WORKFLOW_UNIT_DIAGNOSTIC_CLIP}.
669
455
  */
670
456
  function journaledUnitResultJson(outcome) {
671
457
  if (outcome.result !== undefined)
672
458
  return JSON.stringify(outcome.result);
673
459
  if (outcome.ok)
674
460
  return outcome.text ? JSON.stringify(outcome.text) : null;
675
- const parts = [outcome.error, outcome.text].filter((part) => Boolean(part && part.trim()));
461
+ const parts = [outcome.error, outcome.text].filter((part) => Boolean(part?.trim()));
676
462
  if (parts.length === 0)
677
463
  return null;
678
464
  return JSON.stringify(clip(parts.join("\n--- unit output ---\n"), WORKFLOW_UNIT_DIAGNOSTIC_CLIP));
@@ -681,15 +467,7 @@ async function prepareAttemptWorktree(input) {
681
467
  if (input.worktreeBase === undefined)
682
468
  return { ok: true, request: input.request };
683
469
  const created = await createUnitWorktree(input.worktreeBase, input.ctx.runId, input.attemptId,
684
- // A child-workflow target (P3a, schema-v4.ts) carries no gitCommitOid of
685
- // its own — it is a composition target, never a worktree-isolated exec
686
- // one. This arm IS reachable — a step that composes a child workflow and
687
- // also declares `isolation: worktree` gets a worktree prepared here
688
- // (worktree prep runs ahead of dispatch), but the child executor
689
- // (child-workflow.ts, P3b §3.2) never dispatches through it: driving a
690
- // child publishes and drives a RUN, not a command/exec unit, so the
691
- // prepared worktree is simply unused by the drive. This ternary keeps the
692
- // field access total over the frozen-target union either way.
470
+ // A child-workflow target has no commit of its own (its prepared worktree goes unused).
693
471
  input.workUnit.frozenTarget.kind === "child-workflow" ? undefined : input.workUnit.frozenTarget.gitCommitOid);
694
472
  if (created.preservedLeftover !== undefined) {
695
473
  warn(`Workflow unit ${input.attemptId}: a previous attempt left uncollected work in its isolation worktree; ` +
@@ -714,8 +492,7 @@ async function reserveJournaledDispatch(input, worktreePath, startedAt) {
714
492
  await enqueueUnitWrite(async () => {
715
493
  await withWorkflowRunsRepo((repo) => {
716
494
  const target = workUnit.frozenTarget;
717
- const holder = ctx.leaseHolder ?? `direct:${randomUUID()}`;
718
- const reserved = repo.reserveUnitAttempt({
495
+ durableAttempt = repo.reserveUnitAttempt({
719
496
  runId: ctx.runId,
720
497
  unitId: attemptId,
721
498
  stepId: plan.stepId,
@@ -727,15 +504,8 @@ async function reserveJournaledDispatch(input, worktreePath, startedAt) {
727
504
  model: target.kind === "command" ? (target.request.model?.resolved ?? null) : null,
728
505
  inputHash,
729
506
  worktreePath: worktreePath ?? null,
730
- claimHolder: holder,
731
- claimExpiresAt: new Date(Date.parse(startedAt) + 90_000).toISOString(),
732
507
  now: startedAt,
733
- leaseMode: ctx.leaseHolder === undefined ? "direct" : "engine",
734
- });
735
- if (reserved.kind === "busy") {
736
- throw new Error(`unit "${attemptId}" already has a live durable attempt held by ${reserved.attempt.claim_holder}`);
737
- }
738
- durableAttempt = reserved.attempt;
508
+ }).attempt;
739
509
  });
740
510
  });
741
511
  if (!durableAttempt)
@@ -751,7 +521,6 @@ async function finishJournaledDispatch(input) {
751
521
  unitId: attemptId,
752
522
  attempt: durableAttempt.attempt,
753
523
  dispatchId: durableAttempt.dispatch_id,
754
- claimHolder: durableAttempt.claim_holder,
755
524
  status: outcome.ok ? "completed" : "failed",
756
525
  resultJson: journaledUnitResultJson(outcome),
757
526
  tokens: outcome.tokens ?? null,
@@ -764,8 +533,8 @@ async function finishJournaledDispatch(input) {
764
533
  throw new Error(`finishUnitAttempt updated no row: no durable attempt "${attemptId}" exists for run "${ctx.runId}".`);
765
534
  }
766
535
  warn(`Workflow unit ${attemptId} (run ${ctx.runId}) ${outcome.ok ? "completed" : `failed (${outcome.failureReason ?? "error"})`}, ` +
767
- `but its durable attempt was reclaimed or finished by another engine invocation — refusing to overwrite ` +
768
- `the CAS winner. This dispatch's result is not journaled.`);
536
+ "but its durable attempt was already finished — refusing a duplicate terminal write. " +
537
+ "This dispatch's result is not journaled.");
769
538
  }
770
539
  }));
771
540
  }
@@ -821,17 +590,8 @@ async function dispatchJournaledAttempt(input) {
821
590
  attempt: durableAttempt.attempt,
822
591
  dispatchId: durableAttempt.dispatch_id,
823
592
  };
824
- // P3b §3.2: the ONE dispatch-seam branch. A `child-workflow`-targeted unit
825
- // never reaches `UnitDispatcher` — it is routed to the child executor
826
- // instead (src/workflows/exec/child-workflow.ts), which publishes the
827
- // child idempotently and drives it with the SAME engine
828
- // (`runWorkflowSteps`) the top-level path uses. Placed HERE — after
829
- // `reserveJournaledDispatch` claims this attempt row, before
830
- // `finishJournaledDispatch`/the worktree epilogue below — so a
831
- // child-workflow unit is journaled exactly like any other unit, and a
832
- // crash between reservation and child publication leaves a `running`
833
- // parent row with no child, recovered by resume (which re-dispatches the
834
- // parent unit and republishes the child idempotently).
593
+ // A child-workflow unit goes to the child executor instead of the
594
+ // dispatcher, after its attempt row is reserved, so it journals like any unit.
835
595
  const dispatched = request.frozenTarget.kind === "child-workflow"
836
596
  ? await driveChildWorkflowUnit({
837
597
  request,
@@ -845,23 +605,21 @@ async function dispatchJournaledAttempt(input) {
845
605
  // Credential and passthrough values are intentionally sampled only AFTER
846
606
  // the default dispatcher has authorized/lowered the frozen request and
847
607
  // materialized credentials at its terminal dispatch boundary. Custom test
848
- // dispatchers receive the same post-dispatch journal scrub.
608
+ // dispatchers receive the same post-dispatch journal scrub. Secret-shaped
609
+ // run params join the set: they must reach the prompt in clear (the unit
610
+ // needs them), but nothing about them needs to reach the journal.
849
611
  const sensitiveValues = collectWorkflowDispatchSensitiveValues({
850
612
  ...(request.frozenTarget.kind === "command" ? { runner: request.frozenTarget.runner } : {}),
851
- ...(request.sensitiveValues ? { sensitiveValues: request.sensitiveValues } : {}),
613
+ sensitiveValues: [...(request.sensitiveValues ?? []), ...secretShapedParamValues(ctx.params)],
852
614
  }, request.env);
853
615
  const outcome = redactUnitOutcome(dispatched, sensitiveValues);
854
616
  const finishedAt = new Date().toISOString();
855
- // A dispatched unit's outcome is NEVER silently discarded. The single-driver
856
- // guard lives on the append-only attempt row: attempt number, dispatch id,
857
- // claim holder, and running status must all match. A stale driver's finish
858
- // therefore cannot clobber a reclaimed or retried dispatch. An attempt that
859
- // IS still ours is finished with the real
860
- // result even when the run went non-active or the lease moved mid-flight —
861
- // dropping it would leave the row `running` and make a later resume
862
- // re-dispatch side-effecting work that already ran and already spent tokens.
863
- // Persisting a unit result never advances the run; spine advancement stays
864
- // lease-guarded in completeWorkflowStep.
617
+ // A dispatched unit's outcome is NEVER silently discarded: the attempt is
618
+ // finished with the real result even when the run went non-active
619
+ // mid-flight — dropping it would leave the row `running` and make a later
620
+ // resume re-dispatch side-effecting work that already ran and already spent
621
+ // tokens. Persisting a unit result never advances the run; that is
622
+ // completeWorkflowStep's job.
865
623
  let journalError;
866
624
  try {
867
625
  await finishJournaledDispatch({
@@ -874,14 +632,8 @@ async function dispatchJournaledAttempt(input) {
874
632
  catch (err) {
875
633
  journalError = err;
876
634
  }
877
- // Worktree lifecycle epilogue: a CLEAN worktree is removed; a DIRTY one is
878
- // retained and logged — the unit left uncollected work, and its journaled
879
- // worktree_path says where. Cleanup is best-effort observability, never a
880
- // unit failure, so it is STARTED here and awaited at the step barrier: the
881
- // removal serializes on the same per-repo chain as every sibling's
882
- // `git worktree add`, and awaiting it in this unit's scheduler slot made a
883
- // finished unit wait out other units' full checkouts before its worker could
884
- // claim the next item.
635
+ // A clean worktree is removed, a dirty one kept and logged. Cleanup starts
636
+ // here and is awaited at the step barrier, never holding this scheduler slot.
885
637
  queueAttemptWorktreeCleanup(input, worktreePath);
886
638
  // A journal-write failure AFTER a successful dispatch is its own loud
887
639
  // failure class: the unit's work ran (and may have succeeded), but its
@@ -938,7 +690,7 @@ async function dispatchUnit(request, dispatcher) {
938
690
  let tokens = 0;
939
691
  let sawUsage = false;
940
692
  let loweringNotices;
941
- // Harness-native session id revealed by dispatch (P2). Captured across
693
+ // Harness-native session id revealed by dispatch. Captured across
942
694
  // structured-output retries (last one wins) so it survives into the
943
695
  // UnitOutcome and gets journaled by finishUnitAttempt — the seam's
944
696
  // contract ("stored opportunistically on the unit row for resume").
@@ -1046,9 +798,6 @@ export const defaultUnitDispatcher = async (request, feedback) => {
1046
798
  const frozenTarget = request.frozenTarget;
1047
799
  if (frozenTarget.kind === "script") {
1048
800
  assertFrozenDirectoryContained(frozenTarget.cwdIdentity);
1049
- if (frozenTarget.executable) {
1050
- assertFrozenExecutableIdentity(frozenTarget.executable, `unit ${request.unitId} executable`);
1051
- }
1052
801
  const materialized = materializeFrozenScript({
1053
802
  sourceRef: frozenTarget.ref,
1054
803
  interpreter: frozenTarget.interpreter,
@@ -1066,8 +815,6 @@ export const defaultUnitDispatcher = async (request, feedback) => {
1066
815
  byteLength: frozenTarget.byteLength,
1067
816
  sha256: frozenTarget.contentHash,
1068
817
  }, materialized.file);
1069
- if (frozenTarget.executable)
1070
- command[0] = frozenTarget.executable.absolutePath;
1071
818
  return await runExecUnit({
1072
819
  unitId: request.unitId,
1073
820
  exec: {
@@ -1090,15 +837,9 @@ export const defaultUnitDispatcher = async (request, feedback) => {
1090
837
  if (frozenTarget.kind === "shell") {
1091
838
  if (frozenTarget.cwdIdentity)
1092
839
  assertFrozenDirectoryContained(frozenTarget.cwdIdentity);
1093
- if (frozenTarget.executable) {
1094
- assertFrozenExecutableIdentity(frozenTarget.executable, `unit ${request.unitId} executable`);
1095
- }
1096
- const command = [...frozenTarget.exec.command];
1097
- if (frozenTarget.executable)
1098
- command[0] = frozenTarget.executable.absolutePath;
1099
840
  return runExecUnit({
1100
841
  unitId: request.unitId,
1101
- exec: { ...frozenTarget.exec, command: command },
842
+ exec: frozenTarget.exec,
1102
843
  baseDir: request.cwd ?? frozenTarget.cwdIdentity?.realCwd ?? process.cwd(),
1103
844
  ...(request.env ? { env: request.env } : {}),
1104
845
  ...(request.execContext ? { context: request.execContext } : {}),