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,328 @@
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
+ /**
5
+ * Durable-v4 symbolic workflow environments.
6
+ *
7
+ * Env-file values are the deliberately narrow live-value exception to a
8
+ * frozen workflow plan. Freeze persists only the qualified owner, physical
9
+ * containment, exact key set, and secret-token topology. Materialization
10
+ * later reads current values directly through that descriptor, without
11
+ * consulting config, the index, or authored workflow source.
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import dotenv from "dotenv";
16
+ import { assetPathForName } from "../../core/asset/asset-placement.js";
17
+ import { isWithin } from "../../core/common.js";
18
+ import { NotFoundError, UsageError } from "../../core/errors.js";
19
+ import { captureGuardedExecutionSource, GuardedExecutionSourceCollector } from "../../execution/guarded-source.js";
20
+ const SECRET_TOKEN_RE = /\$\{secret:([A-Za-z0-9_./-]+)\}/g;
21
+ const ENVIRONMENT_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
22
+ const QUALIFIED_ENV_REF_RE = /^([^/]+)\/\/env\/(.+)$/;
23
+ /** Freeze value-free descriptors in authored ref order. */
24
+ export function freezeWorkflowEnvironment(refs, options) {
25
+ const collector = options.collector ?? new GuardedExecutionSourceCollector();
26
+ const logical = new Set();
27
+ const physical = new Map();
28
+ const out = [];
29
+ for (const [precedence, inputRef] of refs.entries()) {
30
+ const resolved = options.resolveRef(inputRef);
31
+ const match = QUALIFIED_ENV_REF_RE.exec(resolved.ref);
32
+ if (!match || match[1] !== resolved.bundle) {
33
+ throw new UsageError(`Workflow env ref ${JSON.stringify(inputRef)} did not resolve to a canonical fully-qualified owner.`, "INVALID_FLAG_VALUE");
34
+ }
35
+ if (logical.has(resolved.ref)) {
36
+ throw new UsageError(`Workflow environment contains duplicate ref ${resolved.ref}.`, "INVALID_FLAG_VALUE");
37
+ }
38
+ logical.add(resolved.ref);
39
+ trackParentDirectories(collector, resolved.root, resolved.path);
40
+ const retained = collector.capture(resolved.path, resolved.root, { authored: true });
41
+ const captured = collector.bindIdentity(resolved.path, resolved.root, {
42
+ ref: resolved.ref,
43
+ bundle: resolved.bundle,
44
+ adapter: resolved.adapter,
45
+ file: retained.relativePath,
46
+ hash: retained.sha256,
47
+ });
48
+ const physicalKey = `${captured.containmentPhysicalIdentity}\0${captured.physicalIdentity}`;
49
+ const alias = physical.get(physicalKey);
50
+ if (alias !== undefined && alias !== resolved.ref) {
51
+ throw new UsageError(`${resolved.ref} aliases the same physical environment source as ${alias} under a different logical owner.`, "RESOURCE_ALREADY_EXISTS");
52
+ }
53
+ physical.set(physicalKey, resolved.ref);
54
+ const parsed = dotenv.parse(Buffer.from(captured.bytesBase64, "base64"));
55
+ const keys = sortedCodePoints(Object.keys(parsed));
56
+ const secretNames = secretTokenNames(parsed);
57
+ out.push(Object.freeze({
58
+ kind: "env-ref",
59
+ ref: resolved.ref,
60
+ owner: Object.freeze({
61
+ bundle: resolved.bundle,
62
+ adapter: resolved.adapter,
63
+ requestedRoot: captured.containmentRoot,
64
+ realRoot: captured.containmentRealPath,
65
+ rootPhysicalIdentity: captured.containmentPhysicalIdentity,
66
+ requestedPath: captured.sourcePath,
67
+ realPath: captured.realPath,
68
+ relativePath: captured.relativePath,
69
+ }),
70
+ keys: Object.freeze(keys),
71
+ secretNames: Object.freeze(secretNames),
72
+ precedence,
73
+ }));
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Materialize all current values atomically from frozen descriptors. Nothing
79
+ * is returned until every owner/key/token check and every secret lookup has
80
+ * succeeded.
81
+ */
82
+ export function materializeFrozenWorkflowEnvironment(descriptors, options = {}) {
83
+ const values = {};
84
+ const sensitive = new Set();
85
+ const audits = [];
86
+ const seenRefs = new Set();
87
+ let priorPrecedence = -1;
88
+ for (const descriptor of descriptors) {
89
+ if (!descriptor || typeof descriptor !== "object")
90
+ invalid("environment descriptor must be an object");
91
+ if (descriptor.kind === "literal") {
92
+ exactKeys(descriptor, ["kind", "name", "value"]);
93
+ environmentName(descriptor.name);
94
+ if (typeof descriptor.value !== "string")
95
+ invalid("literal environment value must be a string");
96
+ values[descriptor.name] = descriptor.value;
97
+ if (descriptor.value)
98
+ sensitive.add(descriptor.value);
99
+ continue;
100
+ }
101
+ if (descriptor.kind === "pass-through") {
102
+ exactKeys(descriptor, ["kind", "name"]);
103
+ environmentName(descriptor.name);
104
+ const value = options.readPassThrough ? options.readPassThrough(descriptor.name) : process.env[descriptor.name];
105
+ if (value !== undefined) {
106
+ values[descriptor.name] = value;
107
+ if (value)
108
+ sensitive.add(value);
109
+ }
110
+ continue;
111
+ }
112
+ if (descriptor.kind !== "env-ref")
113
+ invalid("environment descriptor has an unsupported kind");
114
+ exactKeys(descriptor, ["kind", "ref", "owner", "keys", "secretNames", "precedence"]);
115
+ validateEnvRefDescriptor(descriptor);
116
+ if (seenRefs.has(descriptor.ref))
117
+ invalid(`duplicate environment ref ${descriptor.ref}`);
118
+ if (descriptor.precedence <= priorPrecedence)
119
+ invalid("env-ref precedence must be strictly increasing");
120
+ seenRefs.add(descriptor.ref);
121
+ priorPrecedence = descriptor.precedence;
122
+ const source = options.readEnvFile ? options.readEnvFile(descriptor) : readFrozenEnvironmentFile(descriptor.owner);
123
+ const parsed = dotenv.parse(typeof source === "string" ? source : Buffer.from(source));
124
+ const keys = sortedCodePoints(Object.keys(parsed));
125
+ const tokenNames = secretTokenNames(parsed);
126
+ if (!sameStrings(keys, descriptor.keys)) {
127
+ invalid(`environment ${descriptor.ref} key set changed after it was frozen`);
128
+ }
129
+ if (!sameStrings(tokenNames, descriptor.secretNames)) {
130
+ invalid(`environment ${descriptor.ref} secret-token topology changed after it was frozen`);
131
+ }
132
+ const secretValues = new Map();
133
+ for (const name of descriptor.secretNames) {
134
+ const raw = options.readSecret
135
+ ? options.readSecret({ name, descriptor })
136
+ : readFrozenSecret(descriptor.owner, name);
137
+ if (raw === undefined) {
138
+ throw new NotFoundError(`Environment ${descriptor.ref} references missing secret ${name}; nothing was materialized.`, "FILE_NOT_FOUND");
139
+ }
140
+ const value = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
141
+ secretValues.set(name, value);
142
+ if (value)
143
+ sensitive.add(value);
144
+ }
145
+ for (const [key, rawValue] of Object.entries(parsed)) {
146
+ const resolved = rawValue.replace(SECRET_TOKEN_RE, (_token, name) => {
147
+ const secret = secretValues.get(name);
148
+ if (secret === undefined)
149
+ invalid(`environment ${descriptor.ref} secret ${name} was not materialized`);
150
+ return secret;
151
+ });
152
+ values[key] = resolved;
153
+ if (resolved)
154
+ sensitive.add(resolved);
155
+ }
156
+ audits.push(Object.freeze({
157
+ eventType: "env_access",
158
+ ref: descriptor.ref,
159
+ keys: Object.freeze([...descriptor.keys]),
160
+ secretNames: Object.freeze([...descriptor.secretNames]),
161
+ }));
162
+ }
163
+ return {
164
+ values,
165
+ sensitiveValues: [...sensitive],
166
+ audits,
167
+ };
168
+ }
169
+ function validateEnvRefDescriptor(descriptor) {
170
+ const match = QUALIFIED_ENV_REF_RE.exec(descriptor.ref);
171
+ if (!match || match[1] !== descriptor.owner.bundle)
172
+ invalid("env-ref has a noncanonical qualified owner");
173
+ exactKeys(descriptor.owner, [
174
+ "bundle",
175
+ "adapter",
176
+ "requestedRoot",
177
+ "realRoot",
178
+ "rootPhysicalIdentity",
179
+ "requestedPath",
180
+ "realPath",
181
+ "relativePath",
182
+ ]);
183
+ for (const value of [descriptor.owner.bundle, descriptor.owner.adapter, descriptor.owner.rootPhysicalIdentity]) {
184
+ if (typeof value !== "string" || !value)
185
+ invalid("env-ref owner identity is incomplete");
186
+ }
187
+ for (const value of [
188
+ descriptor.owner.requestedRoot,
189
+ descriptor.owner.realRoot,
190
+ descriptor.owner.requestedPath,
191
+ descriptor.owner.realPath,
192
+ ]) {
193
+ if (typeof value !== "string" || !path.isAbsolute(value))
194
+ invalid("env-ref owner paths must be absolute");
195
+ }
196
+ const relative = descriptor.owner.relativePath;
197
+ if (typeof relative !== "string" ||
198
+ !relative ||
199
+ relative.includes("\\") ||
200
+ relative.startsWith("../") ||
201
+ path.isAbsolute(relative)) {
202
+ invalid("env-ref owner relative path is noncanonical");
203
+ }
204
+ if (path.resolve(descriptor.owner.requestedRoot, relative) !== path.resolve(descriptor.owner.requestedPath)) {
205
+ invalid("env-ref requested path does not match its owner");
206
+ }
207
+ if (path.resolve(descriptor.owner.realRoot, relative) !== path.resolve(descriptor.owner.realPath)) {
208
+ invalid("env-ref real path does not match its owner");
209
+ }
210
+ sortedUnique(descriptor.keys, "env-ref keys", ENVIRONMENT_NAME_RE);
211
+ sortedUnique(descriptor.secretNames, "env-ref secret names", /^[A-Za-z0-9_./-]+$/);
212
+ if (!Number.isSafeInteger(descriptor.precedence) || descriptor.precedence < 0) {
213
+ invalid("env-ref precedence is invalid");
214
+ }
215
+ }
216
+ function readFrozenEnvironmentFile(owner) {
217
+ const rootLstat = fs.lstatSync(owner.requestedRoot, { bigint: true });
218
+ if (rootLstat.isSymbolicLink() || !rootLstat.isDirectory())
219
+ invalid("environment owner root physical identity changed");
220
+ const currentRealRoot = fs.realpathSync(owner.requestedRoot);
221
+ if (currentRealRoot !== owner.realRoot ||
222
+ physicalIdentity(currentRealRoot, rootLstat) !== owner.rootPhysicalIdentity) {
223
+ invalid("environment owner root physical identity changed");
224
+ }
225
+ const captured = captureGuardedExecutionSource(owner.requestedPath, owner.requestedRoot, { authored: true });
226
+ if (captured.realPath !== owner.realPath ||
227
+ captured.relativePath !== owner.relativePath ||
228
+ captured.containmentRealPath !== owner.realRoot ||
229
+ captured.containmentPhysicalIdentity !== owner.rootPhysicalIdentity) {
230
+ invalid("environment owner/path physical identity changed");
231
+ }
232
+ return Buffer.from(captured.bytesBase64, "base64");
233
+ }
234
+ function readFrozenSecret(owner, name) {
235
+ const secretsRoot = path.join(owner.requestedRoot, "secrets");
236
+ const secretPath = assetPathForName("secret", secretsRoot, name);
237
+ if (!isWithin(secretPath, secretsRoot))
238
+ invalid(`secret name ${name} escapes its frozen owner`);
239
+ let descriptor;
240
+ try {
241
+ const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
242
+ descriptor = fs.openSync(secretPath, fs.constants.O_RDONLY | noFollow);
243
+ const before = fs.fstatSync(descriptor, { bigint: true });
244
+ if (!before.isFile())
245
+ invalid(`secret ${name} is not a regular file`);
246
+ const bytes = fs.readFileSync(descriptor);
247
+ const after = fs.fstatSync(descriptor, { bigint: true });
248
+ if (before.dev !== after.dev ||
249
+ before.ino !== after.ino ||
250
+ before.size !== after.size ||
251
+ before.mtimeNs !== after.mtimeNs) {
252
+ invalid(`secret ${name} changed while it was read`);
253
+ }
254
+ return bytes;
255
+ }
256
+ catch (cause) {
257
+ if (cause.code === "ENOENT")
258
+ return undefined;
259
+ throw cause;
260
+ }
261
+ finally {
262
+ if (descriptor !== undefined)
263
+ fs.closeSync(descriptor);
264
+ }
265
+ }
266
+ function trackParentDirectories(collector, rootInput, fileInput) {
267
+ const root = path.resolve(rootInput);
268
+ const parent = path.dirname(path.resolve(fileInput));
269
+ const relative = path.relative(root, parent);
270
+ if (relative.startsWith("..") || path.isAbsolute(relative))
271
+ invalid("environment source escapes its owner root");
272
+ let current = root;
273
+ collector.trackDirectory(current, root);
274
+ if (relative === "")
275
+ return;
276
+ for (const segment of relative.split(path.sep)) {
277
+ current = path.join(current, segment);
278
+ collector.trackDirectory(current, root);
279
+ }
280
+ }
281
+ function secretTokenNames(values) {
282
+ const names = new Set();
283
+ for (const value of Object.values(values)) {
284
+ for (const match of value.matchAll(SECRET_TOKEN_RE)) {
285
+ const name = match[1];
286
+ if (name)
287
+ names.add(name);
288
+ }
289
+ }
290
+ return sortedCodePoints([...names]);
291
+ }
292
+ function sortedCodePoints(values) {
293
+ return values.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
294
+ }
295
+ function sortedUnique(values, label, pattern) {
296
+ if (!Array.isArray(values))
297
+ invalid(`${label} must be an array`);
298
+ let prior;
299
+ for (const value of values) {
300
+ if (typeof value !== "string" || !pattern.test(value) || value.includes(".."))
301
+ invalid(`${label} is invalid`);
302
+ if (prior !== undefined && prior >= value)
303
+ invalid(`${label} must be sorted and unique`);
304
+ prior = value;
305
+ }
306
+ }
307
+ function sameStrings(left, right) {
308
+ return left.length === right.length && left.every((value, index) => value === right[index]);
309
+ }
310
+ function exactKeys(value, keys) {
311
+ const allowed = new Set(keys);
312
+ for (const key of Object.keys(value))
313
+ if (!allowed.has(key))
314
+ invalid(`environment descriptor contains unknown key ${key}`);
315
+ for (const key of keys)
316
+ if (!Object.hasOwn(value, key))
317
+ invalid(`environment descriptor is missing ${key}`);
318
+ }
319
+ function environmentName(value) {
320
+ if (typeof value !== "string" || !ENVIRONMENT_NAME_RE.test(value))
321
+ invalid("environment name is invalid");
322
+ }
323
+ function physicalIdentity(realPath, stat) {
324
+ return stat.ino === 0n ? `path:${realPath}` : `inode:${stat.dev}:${stat.ino}`;
325
+ }
326
+ function invalid(message) {
327
+ throw new UsageError(`Invalid frozen workflow environment: ${message}.`, "INVALID_FLAG_VALUE");
328
+ }
@@ -0,0 +1,122 @@
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 { createHash } from "node:crypto";
5
+ import path from "node:path";
6
+ import { parseBundleRef } from "../../core/asset/asset-ref.js";
7
+ import { UsageError } from "../../core/errors.js";
8
+ import { GuardedExecutionSourceCollector } from "../../execution/guarded-source.js";
9
+ import { defaultMapConcurrency, workflowMaxConcurrency } from "../concurrency-policy.js";
10
+ import { compileWorkflowPlan } from "./compile.js";
11
+ import { canonicalJson } from "./plan-hash.js";
12
+ import { decodeWorkflowPlanV4, WORKFLOW_IR_V4_VERSION, } from "./schema-v4.js";
13
+ import { resolveWorkflowSourceV4 } from "./source-freeze-v4.js";
14
+ /** Compile, resolve, and freeze one executable plan without publishing any state. */
15
+ export async function compileResolveFreezeWorkflowV4(asset, config, options = {}) {
16
+ const sourceCollector = options.sourceCollector ?? new GuardedExecutionSourceCollector();
17
+ const workflowSource = captureWorkflowSource(asset, sourceCollector);
18
+ const resolved = await resolveWorkflowSourceV4(asset, workflowSource, config, sourceCollector);
19
+ const compiled = compileWorkflowPlan(resolved.sourceIr, asset.title, resolved.units);
20
+ if (!compiled.ok) {
21
+ throw new UsageError(compiled.errors.map((error) => `${asset.path}:${error.line}: ${error.message}`).join("\n"), "INVALID_FLAG_VALUE");
22
+ }
23
+ const steps = compiled.plan.steps.map((step) => {
24
+ const frozenJudge = step.gate.criteria.length === 0 ? null : resolved.judges.get(step.stepId);
25
+ if (step.gate.criteria.length > 0 && !frozenJudge) {
26
+ throw new Error(`resolved workflow judge missing for step ${step.stepId}`);
27
+ }
28
+ const gate = Object.freeze({ ...step.gate, maxLoops: step.gate.maxLoops ?? 1, frozenJudge: frozenJudge ?? null });
29
+ if (!step.root) {
30
+ const { root: _root, ...withoutRoot } = step;
31
+ return Object.freeze({ ...withoutRoot, gate });
32
+ }
33
+ const frozen = resolved.units.get(step.stepId);
34
+ if (!frozen)
35
+ throw new Error(`resolved workflow target missing for step ${step.stepId}`);
36
+ const root = step.root.kind === "map"
37
+ ? {
38
+ ...step.root,
39
+ concurrency: step.root.concurrency ?? defaultMapConcurrency(config.workflow?.defaultMapConcurrency),
40
+ template: freezeResolvedUnit(step.root.template, frozen),
41
+ }
42
+ : freezeResolvedUnit(step.root, frozen);
43
+ return Object.freeze({ ...step, root, gate });
44
+ });
45
+ const sourceReadSet = sourceCollector
46
+ .snapshot()
47
+ .sources.filter((source) => Boolean(source.identity))
48
+ .map((source) => sourceSnapshot(source));
49
+ const plan = decodeWorkflowPlanV4({
50
+ irVersion: WORKFLOW_IR_V4_VERSION,
51
+ title: compiled.plan.title,
52
+ ...(compiled.plan.params ? { params: compiled.plan.params } : {}),
53
+ ...(compiled.plan.paramSchemas ? { paramSchemas: compiled.plan.paramSchemas } : {}),
54
+ ...(compiled.plan.budget ? { budget: compiled.plan.budget } : {}),
55
+ execution: { maxConcurrency: workflowMaxConcurrency(config.workflow?.maxConcurrency) },
56
+ sourceReadSet,
57
+ steps,
58
+ });
59
+ return Object.freeze({
60
+ plan,
61
+ warnings: compiled.warnings,
62
+ ...(resolved.engineAnnouncement ? { engineAnnouncement: resolved.engineAnnouncement } : {}),
63
+ sourceCollector,
64
+ });
65
+ }
66
+ function freezeResolvedUnit(unit, resolved) {
67
+ const frozenTarget = resolved.target.kind === "shell"
68
+ ? Object.freeze({
69
+ ...resolved.target,
70
+ contentHash: createHash("sha256")
71
+ .update("akm.workflow.shell.v1\0")
72
+ .update(canonicalJson({
73
+ exec: resolved.target.exec,
74
+ environment: resolved.environment,
75
+ cwdIdentity: resolved.target.cwdIdentity,
76
+ }))
77
+ .digest("hex"),
78
+ })
79
+ : resolved.target;
80
+ const { exec: _exec, ...common } = unit;
81
+ return Object.freeze({
82
+ ...common,
83
+ isolation: common.isolation ?? "none",
84
+ frozenTarget,
85
+ environment: Object.freeze([...resolved.environment]),
86
+ });
87
+ }
88
+ function captureWorkflowSource(asset, collector) {
89
+ const root = path.resolve(asset.sourcePath);
90
+ const file = path.resolve(asset.path);
91
+ collector.trackDirectory(root, root);
92
+ const parent = path.dirname(file);
93
+ const relativeParent = path.relative(root, parent);
94
+ if (relativeParent.startsWith("..") || path.isAbsolute(relativeParent)) {
95
+ throw new UsageError(`${file} resolves outside its workflow source root.`, "PATH_ESCAPE_VIOLATION");
96
+ }
97
+ let current = root;
98
+ for (const segment of relativeParent === "" ? [] : relativeParent.split(path.sep)) {
99
+ current = path.join(current, segment);
100
+ collector.trackDirectory(current, root);
101
+ }
102
+ const captured = collector.capture(file, root, { authored: true });
103
+ const parsed = parseBundleRef(asset.ref);
104
+ if (!parsed.bundle || !asset.adapterId) {
105
+ throw new UsageError(`Workflow ${asset.ref} has no fully-qualified bundle/adapter owner for durable publication.`, "INVALID_FLAG_VALUE");
106
+ }
107
+ return collector.bindIdentity(file, root, {
108
+ ref: asset.ref,
109
+ bundle: parsed.bundle,
110
+ adapter: asset.adapterId,
111
+ file: captured.relativePath,
112
+ hash: captured.sha256,
113
+ });
114
+ }
115
+ function sourceSnapshot(source) {
116
+ return Object.freeze({
117
+ identity: source.identity,
118
+ containmentPhysicalIdentity: source.containmentPhysicalIdentity,
119
+ physicalIdentity: source.physicalIdentity,
120
+ size: source.size,
121
+ });
122
+ }
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { createHash } from "node:crypto";
16
16
  import { utf8Bytes, WORKFLOW_MAX_JSON_DEPTH, WORKFLOW_MAX_PLAN_BYTES } from "../resource-limits.js";
17
- import { decodeWorkflowPlanV3 } from "./schema.js";
17
+ import { decodeWorkflowPlanV4, WORKFLOW_IR_V4_VERSION } from "./schema-v4.js";
18
18
  /** sha256 hex of the canonical (recursively sorted-keys) JSON of the plan. */
19
19
  export function computePlanHash(plan) {
20
20
  return createHash("sha256").update(canonicalPlanJson(plan)).digest("hex");
@@ -23,12 +23,12 @@ export function computePlanHash(plan) {
23
23
  export function canonicalPlanJson(plan) {
24
24
  return canonicalJson(plan);
25
25
  }
26
- /** Canonical JSON used by every v3 plan and input hash. */
26
+ /** Canonical JSON used by every plan and input hash. */
27
27
  export function canonicalJson(value) {
28
28
  return JSON.stringify(sortKeys(value));
29
29
  }
30
30
  /** Decode, require stored canonical bytes, then verify the stored SHA-256. */
31
- export function decodeCanonicalPlan(runId, planJson, planHash) {
31
+ export function decodeCanonicalPlan(runId, planJson, planHash, expectedVersion) {
32
32
  if (utf8Bytes(planJson) > WORKFLOW_MAX_PLAN_BYTES)
33
33
  throw new Error(`Workflow run ${runId} frozen plan exceeds the 2 MiB resource limit.`);
34
34
  let parsed;
@@ -38,13 +38,19 @@ export function decodeCanonicalPlan(runId, planJson, planHash) {
38
38
  catch {
39
39
  throw new Error(`Workflow run ${runId} has corrupt frozen plan JSON.`);
40
40
  }
41
- const plan = decodeWorkflowPlanV3(parsed);
42
- const canonical = canonicalPlanJson(plan);
43
- if (planJson !== canonical)
41
+ const canonicalWire = canonicalJson(parsed);
42
+ if (planJson !== canonicalWire)
44
43
  throw new Error(`Workflow run ${runId} has noncanonical frozen plan JSON.`);
45
- const actual = computePlanHash(plan);
44
+ const actual = createHash("sha256").update(planJson).digest("hex");
46
45
  if (!planHash || !/^[0-9a-f]{64}$/.test(planHash) || actual !== planHash)
47
46
  throw new Error(`Workflow run ${runId} frozen plan integrity check failed.`);
47
+ if (expectedVersion !== undefined && expectedVersion !== null && expectedVersion !== WORKFLOW_IR_V4_VERSION) {
48
+ throw new Error(`Workflow run ${runId} uses unsupported workflow IR version ${expectedVersion}; this runtime supports only workflow IR version 4.`);
49
+ }
50
+ const plan = decodeWorkflowPlanV4(parsed);
51
+ const canonical = canonicalPlanJson(plan);
52
+ if (planJson !== canonical)
53
+ throw new Error(`Workflow run ${runId} has noncanonical frozen plan JSON.`);
48
54
  return plan;
49
55
  }
50
56
  function sortKeys(value, depth = 0) {