akm-cli 0.9.1 → 0.9.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -0,0 +1,5 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /** Fixed private dispatch mode embedded into Bun standalone AKM artifacts. */
5
+ export const STANDALONE_FROZEN_SCRIPT_ARG = "__akm_internal_frozen_script_v1__";
@@ -25,6 +25,9 @@ export function validateTaskId(id) {
25
25
  if (TASK_FILE_SUFFIX_RE.test(id)) {
26
26
  throw new UsageError(`Task id "${id}" is invalid. Use the bare task id without a .yml or .yaml suffix.`, "INVALID_FLAG_VALUE");
27
27
  }
28
+ if (id.endsWith(".")) {
29
+ throw new UsageError(`Task id "${id}" is invalid. Trailing periods are not portable across native schedulers.`, "INVALID_FLAG_VALUE");
30
+ }
28
31
  if (WINDOWS_RESERVED_DEVICE_RE.test(id)) {
29
32
  throw new UsageError(`Task id "${id}" uses a reserved Windows device name. Choose a different task id.`, "INVALID_FLAG_VALUE");
30
33
  }
@@ -33,3 +36,29 @@ export function validateTaskId(id) {
33
36
  export function normaliseTaskId(raw) {
34
37
  return validateTaskId(raw.trim());
35
38
  }
39
+ /**
40
+ * Validate an adapter component-relative task identity.
41
+ *
42
+ * Native `akm` task authoring remains deliberately flat and continues to use
43
+ * {@link validateTaskId}. The standalone `akm-task` adapter, however, derives
44
+ * identity from the complete extensionless path beneath its configured
45
+ * component root. Validate every path segment with the portable task-id rules
46
+ * without collapsing the `/` separators that distinguish physical owners.
47
+ */
48
+ export function validateTaskConceptId(id) {
49
+ if (!id)
50
+ throw new UsageError("Task id must be non-empty.", "MISSING_REQUIRED_ARGUMENT");
51
+ if (id.includes("\\")) {
52
+ throw new UsageError(`Task id "${id}" is invalid. Use canonical forward slashes between path segments.`, "INVALID_FLAG_VALUE");
53
+ }
54
+ const segments = id.split("/");
55
+ if (segments.some((segment) => segment.length === 0)) {
56
+ throw new UsageError(`Task id "${id}" is invalid. Empty path segments are not allowed.`, "INVALID_FLAG_VALUE");
57
+ }
58
+ for (const segment of segments)
59
+ validateTaskId(segment);
60
+ return id;
61
+ }
62
+ export function normaliseTaskConceptId(raw) {
63
+ return validateTaskConceptId(raw.trim());
64
+ }
@@ -14,7 +14,7 @@ import { canonicalizeWorkflowName, WORKFLOW_EXTENSIONS } from "../../core/recogn
14
14
  import { warn } from "../../core/warn.js";
15
15
  import { prepareWriteTargetForMutation, resolveWriteTarget, withWriteTargetMutation } from "../../core/write-source.js";
16
16
  import { compileWorkflowPlan } from "../ir/compile.js";
17
- import { parseWorkflow } from "../parser.js";
17
+ import { compileWorkflowSource } from "../source-ir/compile.js";
18
18
  const DEFAULT_WORKFLOW_TEMPLATE = renderWorkflowTemplate("New Workflow");
19
19
  export function getWorkflowTemplate() {
20
20
  return DEFAULT_WORKFLOW_TEMPLATE;
@@ -22,33 +22,33 @@ export function getWorkflowTemplate() {
22
22
  export function buildWorkflowTemplate(name) {
23
23
  if (!name)
24
24
  return DEFAULT_WORKFLOW_TEMPLATE;
25
- // Only the H1 is customized. Step ids are STRUCTURAL under the unified format
25
+ // Only the H1 is customized. Step ids are STRUCTURAL in the Markdown authoring form
26
26
  // — each appears in `steps[].id`, as a body `## <id>` heading, and possibly in
27
27
  // another step's `inputs:` — so rewriting them from a name would have to patch
28
28
  // three places consistently to stay parseable. Generic `first-step`/
29
29
  // `second-step` are the author's to rename.
30
30
  const customized = renderWorkflowTemplate(humanizeWorkflowName(name));
31
- validateWorkflowContent(customized, `<template:${name}>`);
31
+ validateWorkflowContent(customized, `<template:${name}>.md`);
32
32
  return customized;
33
33
  }
34
34
  /** Parse AND compile `content`, so a create never writes an asset that `show`/`start`/`validate` would then reject. */
35
35
  function validateWorkflowContent(content, sourcePath) {
36
- const result = parseWorkflow(content, { path: sourcePath });
36
+ const result = compileWorkflowSource(content, { path: sourcePath });
37
37
  if (!result.ok) {
38
38
  throw new UsageError(formatWorkflowErrors(sourcePath, result.errors));
39
39
  }
40
- const compiled = compileWorkflowPlan(result.document, slugifyWorkflowStepId(sourcePath));
40
+ const compiled = compileWorkflowPlan(result.ir, slugifyWorkflowStepId(sourcePath));
41
41
  if (!compiled.ok) {
42
42
  throw new UsageError(formatWorkflowErrors(sourcePath, compiled.errors));
43
43
  }
44
44
  }
45
- /** Recognized workflow-program suffixes creating one is a lint-time usage error (workflow-format-unification). */
45
+ /** Peer YAML is executable, but `akm workflow create` emits the Markdown authoring form only. */
46
46
  const YAML_SUFFIX_RE = /\.ya?ml$/i;
47
47
  export function assertWorkflowMarkdownName(name) {
48
48
  if (!YAML_SUFFIX_RE.test(name.trim()))
49
49
  return;
50
- throw new UsageError(`Workflows are markdown-only now (workflow-format-unification) "${name}" cannot be created. ` +
51
- `Use a plain name (no ".yaml"/".yml" suffix); the orchestration graph lives in the ".md" file's frontmatter.`);
50
+ throw new UsageError(`akm workflow create is markdown-only: it emits Markdown and cannot create "${name}". ` +
51
+ `Use a plain name (no ".yaml"/".yml" suffix), or author a peer GitHub-shaped ".yml" workflow directly.`);
52
52
  }
53
53
  export function createWorkflowAsset(input) {
54
54
  assertWorkflowMarkdownName(input.name);
@@ -67,11 +67,10 @@ export function createWorkflowAsset(input) {
67
67
  path.isAbsolute(relativeAssetPath)) {
68
68
  throw new UsageError(`Resolved workflow path escapes the bundle: "${normalizedName}"`, "PATH_ESCAPE_VIOLATION");
69
69
  }
70
- // A file whose canonical name matches but whose PATH differs can only be a
71
- // case variant now (`upper.MD` vs `upper.md`) the cross-EXTENSION case this
72
- // guard also covered is gone with the unified `.md`-only format. It still
73
- // matters: on a case-insensitive filesystem the two spellings are one file,
74
- // so writing the target would silently clobber or shadow the existing asset.
70
+ // A peer `.md` or `.yml` file whose canonical name matches already owns this
71
+ // ref. Case variants (`upper.MD` vs `upper.md`) may also name one physical
72
+ // file on a case-insensitive filesystem, so writing the Markdown create
73
+ // target would silently clobber or shadow the existing asset.
75
74
  const shadowing = findExistingWorkflowPaths(typeRoot, normalizedName).find((p) => p !== assetPath);
76
75
  if (shadowing !== undefined) {
77
76
  throw new UsageError(`Workflow "${normalizedName}" already exists as ${path.relative(stashDir, shadowing)} — the ` +
@@ -159,27 +158,11 @@ export function formatWorkflowErrors(path, errors) {
159
158
  const heading = errors.length === 1 ? "Workflow has 1 error:" : `Workflow has ${errors.length} errors:`;
160
159
  return [heading, ...lines].join("\n");
161
160
  }
162
- /**
163
- * Validate a workflow filesystem path.
164
- *
165
- * Returns the parse result plus the source-relative path used. Throws
166
- * `UsageError` only when the target cannot be located on disk; parse
167
- * failures are returned as `{ ok: false, errors }` so callers can
168
- * format them however they like.
169
- */
170
- export function validateWorkflowSource(target) {
171
- const resolved = path.resolve(target);
172
- if (!fs.existsSync(resolved)) {
173
- throw new UsageError(`Workflow file not found: "${target}".`);
174
- }
175
- const content = fs.readFileSync(resolved, "utf8");
176
- return { path: target, parse: parseWorkflow(content, { path: target }) };
177
- }
178
161
  /**
179
162
  * Every file under `typeRoot` whose canonical workflow name equals
180
- * `normalizedName`. Extension matching is case-INSENSITIVE, so `foo.MD` and
181
- * `foo.md` both match the case-variant collision the create path must refuse
182
- * rather than silently clobber on a case-insensitive filesystem.
163
+ * `normalizedName`. Peer `.md`/`.yml` extension matching is case-INSENSITIVE,
164
+ * so both cross-format owners and case variants are returned for the create
165
+ * path to refuse rather than silently clobber or shadow them.
183
166
  */
184
167
  function findExistingWorkflowPaths(typeRoot, normalizedName) {
185
168
  const parent = path.join(typeRoot, path.dirname(normalizedName));
@@ -32,26 +32,32 @@ import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensit
32
32
  * the set.
33
33
  */
34
34
  export function collectWorkflowDispatchSensitiveValues(dispatch, env) {
35
- const values = new Set(Object.values(env ?? {}));
36
- const addCredential = (engine) => {
37
- if (!engine)
35
+ const values = new Set([...Object.values(env ?? {}), ...(dispatch.sensitiveValues ?? [])]);
36
+ const addCredential = (runner) => {
37
+ if (!runner)
38
38
  return;
39
- if (engine.kind === "llm") {
40
- for (const name of engine.credential?.names ?? []) {
39
+ if (runner.kind === "llm") {
40
+ for (const name of runner.credential?.names ?? []) {
41
41
  const value = process.env[name]?.trim();
42
42
  if (value)
43
43
  values.add(value);
44
44
  }
45
45
  return;
46
46
  }
47
- for (const name of engine.envPassthrough) {
47
+ for (const name of runner.profile.envPassthrough ?? []) {
48
48
  const value = process.env[name];
49
49
  if (!isEnvPassthroughValueSafeToExpose(name, value) && value)
50
50
  values.add(value);
51
51
  }
52
+ if (runner.kind === "sdk") {
53
+ for (const name of runner.fallbackCredential?.names ?? []) {
54
+ const value = process.env[name]?.trim();
55
+ if (value)
56
+ values.add(value);
57
+ }
58
+ }
52
59
  };
53
- addCredential(dispatch.engine);
54
- addCredential(dispatch.fallbackEngine);
60
+ addCredential(dispatch.runner);
55
61
  return collectSensitiveValues(values);
56
62
  }
57
63
  /**
@@ -30,8 +30,7 @@
30
30
  * - ALLOWLISTED ENVIRONMENT. The child does NOT inherit akm's environment: it
31
31
  * starts EMPTY and receives exactly {@link EXEC_DEFAULT_ENV_PASSTHROUGH}
32
32
  * plus the unit's `exec.passEnv`, then the resolved `env:` bindings, then
33
- * the engine-authored `AKM_*` context. `exec.inheritEnv` opts back into full
34
- * inheritance. See {@link childEnv}.
33
+ * the engine-authored `AKM_*` context. See {@link childEnv}.
35
34
  *
36
35
  * Secrets: `env` values reaching this module are already resolved from `env:`
37
36
  * bindings by NAME (`resolveEnvBinding`) — the plan never carries inline secrets
@@ -48,7 +47,7 @@
48
47
  import fs from "node:fs";
49
48
  import path from "node:path";
50
49
  import { isWithinAsync } from "../../core/common.js";
51
- import { COMMON_SPAWN_ENV_PASSTHROUGH, collectAllowlistedEnv, supplementPathForSchedulerContext, WIN32_SPAWN_ENV_FLOOR, } from "../../core/spawn-env.js";
50
+ import { COMMON_SPAWN_ENV_PASSTHROUGH, collectAllowlistedEnv, WIN32_SPAWN_ENV_FLOOR } from "../../core/spawn-env.js";
52
51
  import { runManagedSubprocess, streamCaptureFailure, } from "../../core/subprocess.js";
53
52
  import { warn } from "../../core/warn.js";
54
53
  import { execContextLimits, utf8Bytes, WORKFLOW_EXEC_OUTPUT_TRUNCATED_MARKER, WORKFLOW_MAX_EXEC_OUTPUT_BYTES, WORKFLOW_UNIT_DIAGNOSTIC_CLIP, } from "../resource-limits.js";
@@ -113,8 +112,8 @@ const EXEC_STDERR_DIAGNOSTIC_CLIP = WORKFLOW_UNIT_DIAGNOSTIC_CLIP - 500;
113
112
  * demand, exactly as the agent passthrough list does
114
113
  * (`integrations/agent/profiles.ts`, DRIFT-6).
115
114
  *
116
- * Deliberately ABSENT and reachable only through `exec.passEnv` / `env:` /
117
- * `exec.inheritEnv`: credentials of every kind, cloud/CI vars, and the proxy
115
+ * Deliberately ABSENT and reachable only through `exec.passEnv` / `env:`:
116
+ * credentials of every kind, cloud/CI vars, and the proxy
118
117
  * family (`HTTP_PROXY` & friends) — proxy URLs routinely embed credentials,
119
118
  * which is why akm's redaction policy already treats URL-shaped passthrough
120
119
  * values as credential-bearing.
@@ -460,9 +459,8 @@ async function isExistingDirectory(candidate) {
460
459
  /**
461
460
  * The child's environment, in three layers with fixed precedence:
462
461
  *
463
- * 1. the BASE — an allowlist by default ({@link EXEC_DEFAULT_ENV_PASSTHROUGH}
464
- * plus the unit's `exec.passEnv` names), or the akm process's whole
465
- * environment when the unit opted in with `exec.inheritEnv`;
462
+ * 1. the BASE — {@link EXEC_DEFAULT_ENV_PASSTHROUGH} plus the unit's
463
+ * `exec.passEnv` names;
466
464
  * 2. the unit's resolved `env:` bindings;
467
465
  * 3. the engine-authored `AKM_*` context, LAST so a workflow-supplied binding
468
466
  * can never shadow the ids/item the engine is telling the command the
@@ -490,14 +488,9 @@ async function isExistingDirectory(candidate) {
490
488
  * {@link collectAllowlistedEnv} does it here, so there is one mechanism to
491
489
  * review instead of two.
492
490
  *
493
- * `inheritEnv` is the honest escape hatch for a command that genuinely needs the
494
- * caller's whole environment. It passes everything through, PATH included —
495
- * supplemented for scheduler contexts exactly as {@link collectAllowlistedEnv}
496
- * does it, because the MORE permissive branch must never hand a command a WORSE
497
- * PATH than the restrictive default does under cron/launchd.
498
491
  */
499
492
  function childEnv(exec, bindings, context) {
500
- const env = exec.inheritEnv ? inheritedProcessEnv() : collectAllowlistedEnv(execAllowlist(exec));
493
+ const env = collectAllowlistedEnv(execAllowlist(exec));
501
494
  for (const [name, value] of Object.entries(bindings ?? {}))
502
495
  env[name] = value;
503
496
  for (const [name, value] of Object.entries(context ?? {}))
@@ -508,20 +501,6 @@ function childEnv(exec, bindings, context) {
508
501
  function execAllowlist(exec) {
509
502
  return exec.passEnv ? [...EXEC_DEFAULT_ENV_PASSTHROUGH, ...exec.passEnv] : [...EXEC_DEFAULT_ENV_PASSTHROUGH];
510
503
  }
511
- /**
512
- * The akm process's own environment (`exec.inheritEnv`), with the same
513
- * scheduler-context PATH supplementation {@link collectAllowlistedEnv} applies.
514
- */
515
- function inheritedProcessEnv() {
516
- const env = {};
517
- for (const [name, value] of Object.entries(process.env)) {
518
- if (typeof value === "string")
519
- env[name] = value;
520
- }
521
- if (env.PATH !== undefined)
522
- env.PATH = supplementPathForSchedulerContext(env.PATH);
523
- return env;
524
- }
525
504
  /** `argv[0]` plus its argument count — never the full argv, which can carry values. */
526
505
  function describeCommand(command) {
527
506
  const rest = command.length - 1;
@@ -2,7 +2,7 @@
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
- * The gate judge built from a frozen v3 engine catalog.
5
+ * The gate judge built from the workflow's frozen current execution target.
6
6
  *
7
7
  * Two contracts this module owns, both of which the rest of the engine already
8
8
  * enforces for ordinary units and neither of which the judge may opt out of:
@@ -13,10 +13,10 @@
13
13
  * notes. So the judge outcome goes through the SAME scrub every unit outcome
14
14
  * goes through: {@link collectWorkflowDispatchSensitiveValues} +
15
15
  * {@link withDispatchRedaction} (exec/dispatch-redaction.ts). Nothing about
16
- * a judge call reaches durable state before that scrub. BOTH branches below
17
- * state it that way the agent branch wrapping the injected dispatcher, the
18
- * llm branch wrapping its own chatCompletion call so a change to that one
19
- * contract cannot reach only one of the two judge paths.
16
+ * a judge call reaches durable state before that scrub. Agent and direct-LLM
17
+ * judges share one prepared/lowered dispatch request; the manual completion
18
+ * path uses the same config-free dispatcher without opening an executor
19
+ * import cycle.
20
20
  *
21
21
  * 2. **Identity.** The dispatch request carries the REAL run/step and the gate's
22
22
  * real node/unit ids — the same ids `journalGateEvaluationStart/Finish` write
@@ -25,17 +25,22 @@
25
25
  * The identity is threaded in from the caller that writes the row
26
26
  * ({@link SummaryJudge}'s `identity` argument); it is never synthesized here.
27
27
  *
28
- * A judge dispatch carries NO `env` bindings: a gate judge is an
29
- * {@link IrInvocation} (`IrGateNode.judge`), which has no `env` key, so there is
30
- * nothing authored to thread — and a step's unit `env` is scoped to the WORK,
28
+ * A judge dispatch carries NO `env` bindings: the normalized gate target has
29
+ * no environment key, so there is nothing authored to thread — and a step's
30
+ * unit environment is scoped to the WORK,
31
31
  * not to the verifier. Redaction is unaffected: the sensitive-value set below
32
32
  * still covers the judge engine's credential and unsafe passthrough values.
33
+ * Lowering notices are live execution metadata: units expose a typed result,
34
+ * while judges retain their public string contract and emit only the common
35
+ * lowerer's prompt/body-free notice projection through `warn()`. Current
36
+ * result/evidence journal writers intentionally exclude these diagnostics;
37
+ * this boundary makes no claim about future persistence ownership.
33
38
  *
34
39
  * @module workflows/exec/frozen-judge
35
40
  */
36
- import { ConfigError } from "../../core/errors.js";
41
+ import { warn } from "../../core/warn.js";
37
42
  import { collectWorkflowDispatchSensitiveValues, withDispatchRedaction } from "./dispatch-redaction.js";
38
- import { materializeFrozenLlm } from "./unit-dispatch.js";
43
+ import { dispatchWorkflowExecution, prepareWorkflowExecution, } from "./unit-dispatch.js";
39
44
  /** The gate's node id — identical to what `journalGateEvaluationStart` writes. */
40
45
  export function gateNodeId(stepId) {
41
46
  return `${stepId}.gate`;
@@ -50,96 +55,59 @@ export function gateNodeId(stepId) {
50
55
  function dispatchIdentity(owner, identity) {
51
56
  return identity ?? { ...owner, unitId: gateNodeId(owner.stepId) };
52
57
  }
53
- /** Build a gate judge from a v3 catalog entry without consulting live config. */
54
- export function frozenSummaryJudge(plan, invocation, signal, dispatcher, owner) {
55
- if (!invocation)
56
- return null;
57
- const engine = plan.execution?.engines[invocation.engine];
58
- if (!engine)
59
- throw new ConfigError(`Frozen gate engine "${invocation.engine}" is unavailable.`, "INVALID_CONFIG_FILE");
60
- if (engine.kind === "agent") {
61
- if (!dispatcher) {
62
- throw new ConfigError(`Frozen agent gate engine "${invocation.engine}" has no unit dispatcher.`, "INVALID_CONFIG_FILE");
58
+ function warnLoweringNotices(...groups) {
59
+ const emitted = new Set();
60
+ for (const notices of groups) {
61
+ for (const notice of notices ?? []) {
62
+ const key = JSON.stringify([notice.code, notice.severity, notice.adapter, notice.field, notice.message]);
63
+ if (emitted.has(key))
64
+ continue;
65
+ emitted.add(key);
66
+ warn(`Workflow judge lowering notice (${notice.code}; ${notice.adapter}; ${notice.field}): ${notice.message}`);
63
67
  }
64
- const fallbackEngine = engine.fallbackLlmEngine ? plan.execution.engines[engine.fallbackLlmEngine] : undefined;
65
- const fallback = fallbackEngine?.kind === "llm" ? fallbackEngine : undefined;
66
- // Scrub at the seam: every outcome this judge's dispatcher returns — and
67
- // every error it throws — is redacted BEFORE the verdict (or the failure
68
- // message) can reach the gate row / the blocked step's notes. Identical
69
- // contract to the llm branch below and to the unit path, including the
70
- // failureReason downgrade when redaction altered it.
71
- const dispatch = withDispatchRedaction(dispatcher);
72
- return async ({ system, user }, identity) => {
73
- const id = dispatchIdentity(owner, identity);
74
- // Collected per dispatch, not once per judge: the engine PAIR is fixed at
75
- // build time but its secrets are not — they are read from `process.env`,
76
- // which the dispatch itself re-reads, so a build-time set would miss a
77
- // credential set or rotated between the two reads. Same collector the unit
78
- // path uses; see the module doc on why `env` is absent here but the
79
- // credential/passthrough values are still collected.
80
- const sensitiveValues = collectWorkflowDispatchSensitiveValues({ engine, ...(fallback ? { fallbackEngine: fallback } : {}) }, undefined);
81
- const outcome = await dispatch({
82
- runId: id.runId,
83
- stepId: id.stepId,
84
- unitId: id.unitId,
85
- nodeId: gateNodeId(id.stepId),
86
- prompt: user,
87
- systemPrompt: system,
88
- engine,
89
- ...(fallback ? { fallbackEngine: fallback } : {}),
90
- invocation,
91
- timeoutMs: invocation.timeoutMs,
92
- ...(sensitiveValues.length > 0 ? { sensitiveValues } : {}),
93
- ...(signal ? { signal } : {}),
94
- });
95
- if (!outcome.ok) {
96
- throw new Error(outcome.error || `Verification engine "${invocation.engine}" failed.`);
97
- }
98
- return outcome.text ?? "";
99
- };
100
68
  }
101
- // The llm judge's ONE call, shaped as a {@link UnitDispatcher} so this branch
102
- // states its redaction through the SAME {@link withDispatchRedaction} contract
103
- // the agent branch above uses — including the throw, which a transport error
104
- // takes and which becomes the blocked step's notes (`judgeFailure` in
105
- // step-work.ts).
106
- //
107
- // Inline chatCompletion rather than the injected unit dispatcher ON PURPOSE:
108
- // the manual completion path (`runtime/runs.ts`) builds an llm judge with no
109
- // dispatcher, and a static runs → native-executor edge would close an import
110
- // cycle. The materialization itself is the shared {@link materializeFrozenLlm},
111
- // so the two llm dispatch paths cannot drift.
112
- const dispatch = withDispatchRedaction(async (request) => {
113
- const { chatCompletion } = await import("../../llm/client.js");
114
- const text = await chatCompletion(materializeFrozenLlm(engine, invocation), [
115
- { role: "system", content: request.systemPrompt ?? "" },
116
- { role: "user", content: request.prompt },
117
- ], {
118
- timeoutMs: request.timeoutMs,
119
- ...(signal ? { signal } : {}),
120
- });
121
- return { ok: true, text };
122
- });
69
+ }
70
+ /** Build a gate judge from the normalized frozen target without consulting live config. */
71
+ export function frozenSummaryJudge(target, signal, dispatcher, owner) {
72
+ if (!target)
73
+ return null;
74
+ const dispatch = withDispatchRedaction(dispatcher ?? dispatchWorkflowExecution);
123
75
  return async ({ system, user }, identity) => {
124
76
  const id = dispatchIdentity(owner, identity);
125
- // An llm judge's only secret is its own credential, which
126
- // `materializeFrozenLlm` resolves out of `process.env` inside the dispatch
127
- // below — so the set is collected here, per dispatch, and cannot predate the
128
- // value the call actually authenticates with.
129
- const sensitiveValues = collectWorkflowDispatchSensitiveValues({ engine }, undefined);
130
- const outcome = await dispatch({
77
+ const commonRequest = {
131
78
  runId: id.runId,
132
79
  stepId: id.stepId,
133
80
  unitId: id.unitId,
134
81
  nodeId: gateNodeId(id.stepId),
82
+ ...(id.attempt !== undefined ? { attempt: id.attempt } : {}),
83
+ ...(id.dispatchId !== undefined ? { dispatchId: id.dispatchId } : {}),
135
84
  prompt: user,
136
85
  systemPrompt: system,
137
- engine,
138
- invocation,
139
- timeoutMs: invocation.timeoutMs,
140
- ...(sensitiveValues.length > 0 ? { sensitiveValues } : {}),
141
86
  ...(signal ? { signal } : {}),
87
+ };
88
+ const request = {
89
+ ...commonRequest,
90
+ frozenTarget: target,
91
+ timeoutMs: target.runner.timeoutMs ?? null,
92
+ };
93
+ // Lowering and authorization precede every live credential/passthrough
94
+ // sample. The injected dispatcher may be a test seam, but it receives the
95
+ // exact same already-validated request and redaction declaration.
96
+ const lowered = prepareWorkflowExecution(request);
97
+ const sensitiveValues = collectWorkflowDispatchSensitiveValues({ runner: target.runner }, undefined);
98
+ const outcome = await dispatch({
99
+ ...request,
100
+ ...(sensitiveValues.length > 0 ? { sensitiveValues } : {}),
142
101
  });
143
- return outcome.text;
102
+ if (outcome.usage && id.recordTokens) {
103
+ id.recordTokens((outcome.usage.inputTokens ?? 0) + (outcome.usage.outputTokens ?? 0) + (outcome.usage.reasoningTokens ?? 0));
104
+ }
105
+ // Only the common lowerer's own sanitized notices are loggable here. An
106
+ // injected dispatcher is not trusted to supply prompt/body-free messages.
107
+ warnLoweringNotices(lowered.notices);
108
+ if (!outcome.ok) {
109
+ throw new Error(outcome.error || `Verification engine "${target.request.engine.name}" failed.`);
110
+ }
111
+ return outcome.text ?? "";
144
112
  };
145
113
  }
@@ -0,0 +1,23 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { canonicalJson } from "../ir/plan-hash.js";
5
+ /**
6
+ * Merge safe lowerer diagnostics in first-seen order.
7
+ *
8
+ * Notices are live execution metadata only: this helper is deliberately used
9
+ * by executor/report/output paths, never by result/evidence journal writers.
10
+ * Canonical JSON includes `details`, so two genuinely different structured
11
+ * diagnostics are not collapsed merely because their headline fields match.
12
+ */
13
+ export function mergeLoweringNotices(...groups) {
14
+ const merged = new Map();
15
+ for (const group of groups) {
16
+ for (const notice of group ?? []) {
17
+ const key = canonicalJson(notice);
18
+ if (!merged.has(key))
19
+ merged.set(key, notice);
20
+ }
21
+ }
22
+ return merged.size > 0 ? [...merged.values()] : undefined;
23
+ }