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,525 @@
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
+ * Additive durable workflow plan v4.
6
+ *
7
+ * This is the sole executable workflow plan. Older stored plans are rejected
8
+ * at the run-storage boundary instead of being replayed through a second
9
+ * runtime architecture.
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ import path from "node:path";
13
+ import { UsageError } from "../../core/errors.js";
14
+ import { decodeFrozenExecutableIdentity } from "../../execution/executable-identity.js";
15
+ import { canonicalResolvedExecutionRequest, decodeResolvedExecutionRequest, } from "../../execution/resolved-request.js";
16
+ import { decodeExecutionSourceIdentity } from "../../execution/source.js";
17
+ import { decodeFrozenRunnerSpec } from "../../integrations/agent/execution-lowering.js";
18
+ import { decodeWorkflowExecSpec, validateWorkflowPlanStructure, } from "./schema.js";
19
+ export const WORKFLOW_IR_V4_VERSION = 4;
20
+ /** Strict v4 corruption gate. No authored source or config is consulted here. */
21
+ export function decodeWorkflowPlanV4(input, hooks = {}) {
22
+ const raw = record(input, "plan");
23
+ if (raw.irVersion !== WORKFLOW_IR_V4_VERSION)
24
+ fail("irVersion must be 4");
25
+ assertKeys(raw, ["irVersion", "title", "params", "paramSchemas", "budget", "execution", "steps", "sourceReadSet"], "plan");
26
+ if (!Object.hasOwn(raw, "sourceReadSet"))
27
+ fail("sourceReadSet is required");
28
+ const sourceReadSet = decodeSourceReadSet(raw.sourceReadSet);
29
+ validateWorkflowPlanStructure(raw, {
30
+ expectedVersion: WORKFLOW_IR_V4_VERSION,
31
+ planExtraKeys: ["sourceReadSet"],
32
+ unitExtraKeys: ["frozenTarget", "environment"],
33
+ gateExtraKeys: ["frozenJudge"],
34
+ }, hooks);
35
+ const rawSteps = raw.steps;
36
+ const requiredSources = [];
37
+ const steps = rawSteps.map((rawStep, index) => {
38
+ const step = rawStep;
39
+ const sourceStep = record(rawSteps[index], `step ${step.stepId}`);
40
+ const gate = decodeGateV4(sourceStep.gate, step.stepId, requiredSources);
41
+ if (!step.root)
42
+ return Object.freeze({ ...step, gate });
43
+ const rawRoot = record(sourceStep.root, `step ${step.stepId} root`);
44
+ const root = rawRoot.kind === "map"
45
+ ? (() => {
46
+ const { template: _template, ...map } = rawRoot;
47
+ return {
48
+ ...map,
49
+ template: decodeUnitV4(record(rawRoot.template, `map ${String(rawRoot.id)} template`), requiredSources),
50
+ };
51
+ })()
52
+ : decodeUnitV4(rawRoot, requiredSources);
53
+ return Object.freeze({ ...step, root, gate });
54
+ });
55
+ assertRequiredSources(sourceReadSet, requiredSources);
56
+ return Object.freeze({
57
+ ...raw,
58
+ irVersion: WORKFLOW_IR_V4_VERSION,
59
+ sourceReadSet,
60
+ steps,
61
+ });
62
+ }
63
+ function decodeUnitV4(raw, requiredSources) {
64
+ const id = raw.id;
65
+ if (!Object.hasOwn(raw, "frozenTarget"))
66
+ fail(`unit ${id} frozenTarget is required`);
67
+ if (!Object.hasOwn(raw, "environment"))
68
+ fail(`unit ${id} environment is required`);
69
+ const environment = decodeEnvironment(raw.environment, id);
70
+ const frozenTarget = decodeFrozenTarget(raw.frozenTarget, raw, environment, requiredSources);
71
+ const { frozenTarget: _target, environment: _environment, ...core } = raw;
72
+ return Object.freeze({
73
+ ...core,
74
+ frozenTarget,
75
+ environment: Object.freeze(environment),
76
+ });
77
+ }
78
+ function decodeGateV4(value, stepId, requiredSources) {
79
+ const gate = record(value, `gate ${stepId}`);
80
+ const criteria = gate.criteria;
81
+ if (criteria.length === 0) {
82
+ if (gate.frozenJudge !== null)
83
+ fail(`gate ${stepId} without criteria cannot have a frozen judge target`);
84
+ return Object.freeze({
85
+ kind: "gate",
86
+ id: gate.id,
87
+ stepId,
88
+ criteria,
89
+ maxLoops: gate.maxLoops,
90
+ frozenJudge: null,
91
+ });
92
+ }
93
+ if (!Object.hasOwn(gate, "frozenJudge") || gate.frozenJudge === null) {
94
+ fail(`gate ${stepId} with criteria requires a frozen judge target`);
95
+ }
96
+ const identity = {
97
+ kind: "unit",
98
+ id: gate.id,
99
+ instructions: criteria.join("\n"),
100
+ onError: "fail",
101
+ isolation: "none",
102
+ };
103
+ return Object.freeze({
104
+ kind: "gate",
105
+ id: gate.id,
106
+ stepId,
107
+ criteria,
108
+ maxLoops: gate.maxLoops,
109
+ frozenJudge: decodeCommandTarget(record(gate.frozenJudge, `gate ${stepId} frozenJudge`), identity, requiredSources),
110
+ });
111
+ }
112
+ function decodeFrozenTarget(value, unit, environment, requiredSources) {
113
+ const target = record(value, `unit ${unit.id} frozenTarget`);
114
+ if (target.kind === "command")
115
+ return decodeCommandTarget(target, unit, requiredSources);
116
+ if (target.kind === "shell")
117
+ return decodeShellTarget(target, unit, environment);
118
+ if (target.kind === "script")
119
+ return decodeScriptTarget(target, unit, requiredSources);
120
+ fail(`unit ${unit.id} frozenTarget has unsupported kind ${String(target.kind)}`);
121
+ }
122
+ function decodeCommandTarget(target, unit, requiredSources) {
123
+ assertKeys(target, ["kind", "ref", "contentHash", "request", "runner", "concurrency", "cwdIdentity", "executable", "gitCommitOid"], `unit ${unit.id} command target`);
124
+ if (target.ref !== null && typeof target.ref !== "string")
125
+ fail(`unit ${unit.id} command target ref is invalid`);
126
+ const request = decodeResolvedExecutionRequest(target.request);
127
+ if (request.authorization.status === "denied")
128
+ fail(`unit ${unit.id} command authorization is denied by policy`);
129
+ if (Object.hasOwn(request.runtime, "environment")) {
130
+ fail(`unit ${unit.id} resolved request runtime.environment is live and cannot be persisted`);
131
+ }
132
+ const contentHash = digest(target.contentHash, `unit ${unit.id} command contentHash`);
133
+ const actualContentHash = sha256(request.command.content);
134
+ if (contentHash !== actualContentHash)
135
+ fail(`unit ${unit.id} command contentHash does not match request content`);
136
+ const runner = decodeFrozenRunnerSpec(target.runner);
137
+ if (target.concurrency !== undefined &&
138
+ (!Number.isSafeInteger(target.concurrency) ||
139
+ target.concurrency < 1 ||
140
+ target.concurrency > 64)) {
141
+ fail(`unit ${unit.id} frozen target concurrency is invalid`);
142
+ }
143
+ if (runner.engine !== request.engine.name || runner.kind !== request.engine.kind) {
144
+ fail(`unit ${unit.id} frozen runner must match the resolved request engine`);
145
+ }
146
+ const source = request.command.source;
147
+ if (source) {
148
+ requiredSources.push(source);
149
+ if (target.ref !== source.ref)
150
+ fail(`unit ${unit.id} command target ref must match request source`);
151
+ }
152
+ else if (target.ref !== null) {
153
+ fail(`unit ${unit.id} inline command target ref must be null`);
154
+ }
155
+ if (request.persona)
156
+ requiredSources.push(request.persona.source);
157
+ const cwdIdentity = Object.hasOwn(target, "cwdIdentity")
158
+ ? decodeDirectoryIdentity(target.cwdIdentity, unit.id)
159
+ : undefined;
160
+ const executable = Object.hasOwn(target, "executable")
161
+ ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
162
+ : undefined;
163
+ if (runner.kind === "agent" && (cwdIdentity !== undefined || executable !== undefined)) {
164
+ if (!cwdIdentity || !executable)
165
+ fail(`unit ${unit.id} CLI command target requires cwdIdentity and executable`);
166
+ if (runner.profile.bin !== executable.requested && runner.profile.bin !== executable.absolutePath) {
167
+ fail(`unit ${unit.id} executable does not match the frozen runner bin`);
168
+ }
169
+ }
170
+ else if (runner.kind !== "agent" && executable !== undefined) {
171
+ fail(`unit ${unit.id} non-CLI target cannot carry a host executable`);
172
+ }
173
+ const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
174
+ // Force the shared request canonicalizer across every accepted wire request.
175
+ canonicalResolvedExecutionRequest(request);
176
+ return Object.freeze({
177
+ kind: "command",
178
+ ref: target.ref,
179
+ contentHash,
180
+ request,
181
+ runner,
182
+ ...(target.concurrency !== undefined ? { concurrency: target.concurrency } : {}),
183
+ ...(cwdIdentity ? { cwdIdentity } : {}),
184
+ ...(executable ? { executable } : {}),
185
+ ...(gitCommitOid ? { gitCommitOid } : {}),
186
+ });
187
+ }
188
+ function decodeShellTarget(target, unit, environment) {
189
+ assertKeys(target, ["kind", "contentHash", "exec", "cwdIdentity", "executable", "gitCommitOid"], `unit ${unit.id} shell target`);
190
+ const exec = decodeWorkflowExecSpec(target.exec, `unit ${unit.id} shell target exec`);
191
+ const cwdIdentity = decodeDirectoryIdentity(target.cwdIdentity, unit.id);
192
+ const executable = Object.hasOwn(target, "executable")
193
+ ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
194
+ : undefined;
195
+ if (executable && exec.command[0] !== executable.requested && exec.command[0] !== executable.absolutePath) {
196
+ fail(`unit ${unit.id} shell executable does not match the frozen command`);
197
+ }
198
+ const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
199
+ const contentHash = digest(target.contentHash, `unit ${unit.id} shell contentHash`);
200
+ const expected = createHash("sha256")
201
+ .update("akm.workflow.shell.v1\0")
202
+ .update(canonicalJsonLocal({ exec, environment, cwdIdentity }))
203
+ .digest("hex");
204
+ if (contentHash !== expected)
205
+ fail(`unit ${unit.id} shell contentHash does not match its frozen dispatch`);
206
+ return Object.freeze({
207
+ kind: "shell",
208
+ contentHash,
209
+ exec,
210
+ cwdIdentity,
211
+ ...(executable ? { executable } : {}),
212
+ ...(gitCommitOid ? { gitCommitOid } : {}),
213
+ });
214
+ }
215
+ function decodeScriptTarget(target, unit, requiredSources) {
216
+ assertKeys(target, [
217
+ "kind",
218
+ "ref",
219
+ "contentHash",
220
+ "exec",
221
+ "interpreter",
222
+ "extension",
223
+ "bytesBase64",
224
+ "byteLength",
225
+ "cwdIdentity",
226
+ "materialization",
227
+ "executable",
228
+ "gitCommitOid",
229
+ ], `unit ${unit.id} script target`);
230
+ const exec = decodeWorkflowExecSpec(target.exec, `unit ${unit.id} script target exec`);
231
+ if (typeof target.ref !== "string" || !target.ref.includes("//"))
232
+ fail(`unit ${unit.id} script ref is invalid`);
233
+ if (typeof target.interpreter !== "string" || !target.interpreter)
234
+ fail(`unit ${unit.id} script interpreter is invalid`);
235
+ if (typeof target.extension !== "string" || !/^\.[A-Za-z0-9]+$/.test(target.extension)) {
236
+ fail(`unit ${unit.id} script extension is invalid`);
237
+ }
238
+ if (typeof target.bytesBase64 !== "string")
239
+ fail(`unit ${unit.id} script bytesBase64 is invalid`);
240
+ const bytes = Buffer.from(target.bytesBase64, "base64");
241
+ if (bytes.toString("base64") !== target.bytesBase64)
242
+ fail(`unit ${unit.id} script bytesBase64 is noncanonical`);
243
+ if (!Number.isSafeInteger(target.byteLength) || target.byteLength !== bytes.byteLength) {
244
+ fail(`unit ${unit.id} script byteLength does not match frozen bytes`);
245
+ }
246
+ const contentHash = digest(target.contentHash, `unit ${unit.id} script contentHash`);
247
+ if (contentHash !== sha256(bytes))
248
+ fail(`unit ${unit.id} script contentHash does not match frozen bytes`);
249
+ if (target.materialization !== "ephemeral-0700-delete")
250
+ fail(`unit ${unit.id} script materialization is invalid`);
251
+ const cwdIdentity = decodeDirectoryIdentity(target.cwdIdentity, unit.id);
252
+ const executable = Object.hasOwn(target, "executable")
253
+ ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
254
+ : undefined;
255
+ const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
256
+ requiredSources.push({ ref: target.ref, bundle: "", adapter: "", file: "", hash: contentHash });
257
+ return Object.freeze({
258
+ kind: "script",
259
+ ref: target.ref,
260
+ contentHash,
261
+ exec,
262
+ interpreter: target.interpreter,
263
+ extension: target.extension,
264
+ bytesBase64: target.bytesBase64,
265
+ byteLength: target.byteLength,
266
+ cwdIdentity,
267
+ materialization: "ephemeral-0700-delete",
268
+ ...(executable ? { executable } : {}),
269
+ ...(gitCommitOid ? { gitCommitOid } : {}),
270
+ });
271
+ }
272
+ function decodeGitCommitOid(value, unit) {
273
+ if (unit.isolation === "worktree") {
274
+ if (typeof value !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value)) {
275
+ fail(`unit ${unit.id} worktree target requires a canonical gitCommitOid`);
276
+ }
277
+ return value;
278
+ }
279
+ if (value !== undefined)
280
+ fail(`unit ${unit.id} gitCommitOid is only valid for worktree isolation`);
281
+ return undefined;
282
+ }
283
+ function decodeDirectoryIdentity(value, unitId) {
284
+ const identity = record(value, `unit ${unitId} cwdIdentity`);
285
+ const fields = [
286
+ "requestedRoot",
287
+ "realRoot",
288
+ "rootDevice",
289
+ "rootInode",
290
+ "requestedCwd",
291
+ "realCwd",
292
+ "cwdDevice",
293
+ "cwdInode",
294
+ ];
295
+ assertKeys(identity, fields, `unit ${unitId} cwdIdentity`);
296
+ for (const field of fields) {
297
+ if (typeof identity[field] !== "string" || !identity[field])
298
+ fail(`unit ${unitId} cwdIdentity.${field} is invalid`);
299
+ }
300
+ for (const field of ["requestedRoot", "realRoot", "requestedCwd", "realCwd"]) {
301
+ if (!path.isAbsolute(identity[field]))
302
+ fail(`unit ${unitId} cwdIdentity.${field} must be absolute`);
303
+ }
304
+ return Object.freeze(identity);
305
+ }
306
+ function decodeEnvironment(value, unitId) {
307
+ if (!Array.isArray(value))
308
+ fail(`unit ${unitId} environment must be an array`);
309
+ const out = [];
310
+ const names = new Set();
311
+ for (const [index, raw] of value.entries()) {
312
+ const binding = record(raw, `unit ${unitId} environment[${index}]`);
313
+ if (binding.kind === "literal") {
314
+ assertKeys(binding, ["kind", "name", "value"], `unit ${unitId} environment[${index}]`);
315
+ environmentName(binding.name, unitId, index);
316
+ if (typeof binding.value !== "string")
317
+ fail(`unit ${unitId} literal environment value must be a string`);
318
+ if (literalLooksSecret(binding.name, binding.value)) {
319
+ fail(`unit ${unitId} literal environment value is secret-shaped`);
320
+ }
321
+ out.push(Object.freeze({ kind: "literal", name: binding.name, value: binding.value }));
322
+ }
323
+ else if (binding.kind === "pass-through") {
324
+ assertKeys(binding, ["kind", "name"], `unit ${unitId} environment[${index}]`);
325
+ environmentName(binding.name, unitId, index);
326
+ out.push(Object.freeze({ kind: "pass-through", name: binding.name }));
327
+ }
328
+ else if (binding.kind === "env-ref") {
329
+ assertKeys(binding, ["kind", "ref", "owner", "keys", "secretNames", "precedence"], `unit ${unitId} environment[${index}]`);
330
+ if (typeof binding.ref !== "string" || !binding.ref.includes("//")) {
331
+ fail(`unit ${unitId} environment[${index}] env-ref must be fully qualified`);
332
+ }
333
+ const owner = decodeEnvironmentOwner(binding.owner, binding.ref, unitId, index);
334
+ const keys = sortedUniqueStrings(binding.keys, `unit ${unitId} environment[${index}] keys`, (value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value));
335
+ const secretNames = sortedUniqueStrings(binding.secretNames, `unit ${unitId} environment[${index}] secretNames`, (value) => /^[A-Za-z0-9_./-]+$/.test(value) && !value.includes(".."));
336
+ if (!Number.isSafeInteger(binding.precedence) || binding.precedence < 0) {
337
+ fail(`unit ${unitId} environment[${index}] precedence is invalid`);
338
+ }
339
+ out.push(Object.freeze({
340
+ kind: "env-ref",
341
+ ref: binding.ref,
342
+ owner,
343
+ keys: Object.freeze(keys),
344
+ secretNames: Object.freeze(secretNames),
345
+ precedence: binding.precedence,
346
+ }));
347
+ }
348
+ else {
349
+ fail(`unit ${unitId} environment[${index}] has unsupported kind`);
350
+ }
351
+ if (binding.kind !== "env-ref") {
352
+ const name = binding.name;
353
+ if (names.has(name))
354
+ fail(`unit ${unitId} environment contains duplicate name ${name}`);
355
+ names.add(name);
356
+ }
357
+ }
358
+ return out;
359
+ }
360
+ function decodeEnvironmentOwner(value, ref, unitId, index) {
361
+ const label = `unit ${unitId} environment[${index}] owner`;
362
+ const owner = record(value, label);
363
+ const fields = [
364
+ "bundle",
365
+ "adapter",
366
+ "requestedRoot",
367
+ "realRoot",
368
+ "rootPhysicalIdentity",
369
+ "requestedPath",
370
+ "realPath",
371
+ "relativePath",
372
+ ];
373
+ assertKeys(owner, fields, label);
374
+ for (const field of fields) {
375
+ if (typeof owner[field] !== "string" || !owner[field])
376
+ fail(`${label}.${field} is invalid`);
377
+ }
378
+ if (!ref.startsWith(`${owner.bundle}//`))
379
+ fail(`${label}.bundle does not own env-ref ${ref}`);
380
+ for (const field of ["requestedRoot", "realRoot", "requestedPath", "realPath"]) {
381
+ if (!path.isAbsolute(owner[field]))
382
+ fail(`${label}.${field} must be absolute`);
383
+ }
384
+ const relativePath = owner.relativePath;
385
+ if (relativePath.includes("\\") ||
386
+ relativePath === "." ||
387
+ relativePath.startsWith("../") ||
388
+ path.isAbsolute(relativePath)) {
389
+ fail(`${label}.relativePath is not canonical`);
390
+ }
391
+ if (path.resolve(owner.requestedRoot, relativePath) !== path.resolve(owner.requestedPath)) {
392
+ fail(`${label}.requestedPath does not match relativePath`);
393
+ }
394
+ if (path.resolve(owner.realRoot, relativePath) !== path.resolve(owner.realPath)) {
395
+ fail(`${label}.realPath does not match relativePath`);
396
+ }
397
+ return Object.freeze(owner);
398
+ }
399
+ function sortedUniqueStrings(value, label, accepts) {
400
+ if (!Array.isArray(value))
401
+ fail(`${label} must be an array`);
402
+ const out = [];
403
+ let prior;
404
+ for (const item of value) {
405
+ if (typeof item !== "string" || !accepts(item))
406
+ fail(`${label} contains an invalid value`);
407
+ if (prior !== undefined && compareCodePoints(prior, item) >= 0)
408
+ fail(`${label} must be sorted and unique`);
409
+ prior = item;
410
+ out.push(item);
411
+ }
412
+ return out;
413
+ }
414
+ function decodeSourceReadSet(value) {
415
+ if (!Array.isArray(value) || value.length === 0)
416
+ fail("sourceReadSet must contain at least one source identity");
417
+ const out = [];
418
+ const logical = new Set();
419
+ const physical = new Map();
420
+ let priorKey;
421
+ for (const [index, raw] of value.entries()) {
422
+ const snapshot = record(raw, `sourceReadSet[${index}]`);
423
+ assertKeys(snapshot, ["identity", "containmentPhysicalIdentity", "physicalIdentity", "size"], `sourceReadSet[${index}]`);
424
+ const identity = decodeExecutionSourceIdentity(snapshot.identity, `sourceReadSet[${index}].identity`);
425
+ const key = sourceKey(identity);
426
+ if (logical.has(key))
427
+ fail(`sourceReadSet contains duplicate identity key ${key}`);
428
+ if (typeof snapshot.containmentPhysicalIdentity !== "string" || !snapshot.containmentPhysicalIdentity) {
429
+ fail(`sourceReadSet[${index}] containment physical identity is invalid`);
430
+ }
431
+ if (typeof snapshot.physicalIdentity !== "string" || !snapshot.physicalIdentity) {
432
+ fail(`sourceReadSet[${index}] physical identity is invalid`);
433
+ }
434
+ if (!Number.isSafeInteger(snapshot.size) || snapshot.size < 0)
435
+ fail(`sourceReadSet[${index}] size is invalid`);
436
+ const physicalKey = `${snapshot.containmentPhysicalIdentity}\0${snapshot.physicalIdentity}`;
437
+ const alias = physical.get(physicalKey);
438
+ if (alias !== undefined && alias !== key)
439
+ fail(`sourceReadSet logical refs alias the same physical file identity`);
440
+ physical.set(physicalKey, key);
441
+ if (priorKey !== undefined && compareCodePoints(priorKey, key) >= 0) {
442
+ fail("sourceReadSet is not in canonical ref, adapter, file sort order");
443
+ }
444
+ logical.add(key);
445
+ priorKey = key;
446
+ out.push(Object.freeze({
447
+ identity,
448
+ containmentPhysicalIdentity: snapshot.containmentPhysicalIdentity,
449
+ physicalIdentity: snapshot.physicalIdentity,
450
+ size: snapshot.size,
451
+ }));
452
+ }
453
+ return out;
454
+ }
455
+ function assertRequiredSources(readSet, required) {
456
+ for (const identity of required) {
457
+ const found = readSet.find((snapshot) => snapshot.identity.ref === identity.ref);
458
+ if (!found)
459
+ fail(`source read-set is missing required command/persona/script source ${identity.ref}`);
460
+ if (identity.bundle && sourceKey(found.identity) !== sourceKey(identity)) {
461
+ fail(`source read-set identity mismatch for ${identity.ref}`);
462
+ }
463
+ if (found.identity.hash !== identity.hash)
464
+ fail(`source read-set hash mismatch for ${identity.ref}`);
465
+ }
466
+ }
467
+ function sourceKey(identity) {
468
+ return `${identity.ref}\0${identity.adapter}\0${identity.file}`;
469
+ }
470
+ function environmentName(value, unitId, index) {
471
+ if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
472
+ fail(`unit ${unitId} environment[${index}] has an invalid name`);
473
+ }
474
+ }
475
+ function literalLooksSecret(name, value) {
476
+ const key = name.toLowerCase();
477
+ if (/(?:secret|token|password|passwd|api[_-]?key|access[_-]?key|private[_-]?key|credential|bearer|client_secret)/.test(key)) {
478
+ return true;
479
+ }
480
+ const candidate = value.trim();
481
+ if (candidate.length < 20 || /\s/.test(candidate))
482
+ return false;
483
+ if (/^(?:sk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|xox[baprs]-|AKIA|ASIA|AIza|ya29\.|-----BEGIN)/.test(candidate)) {
484
+ return true;
485
+ }
486
+ const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((pattern) => pattern.test(candidate)).length;
487
+ return (candidate.length >= 24 && classes >= 3) || (candidate.length >= 32 && /^[A-Za-z0-9+/=_-]+$/.test(candidate));
488
+ }
489
+ function digest(value, label) {
490
+ if (typeof value !== "string" || !/^[0-9a-f]{64}$/.test(value))
491
+ fail(`${label} must be a SHA-256 digest`);
492
+ return value;
493
+ }
494
+ function sha256(value) {
495
+ return createHash("sha256").update(value).digest("hex");
496
+ }
497
+ function compareCodePoints(left, right) {
498
+ return left < right ? -1 : left > right ? 1 : 0;
499
+ }
500
+ function canonicalJsonLocal(value) {
501
+ const sort = (entry) => {
502
+ if (Array.isArray(entry))
503
+ return entry.map(sort);
504
+ if (entry && typeof entry === "object") {
505
+ return Object.fromEntries(Object.entries(entry)
506
+ .sort(([left], [right]) => compareCodePoints(left, right))
507
+ .map(([key, nested]) => [key, sort(nested)]));
508
+ }
509
+ return entry;
510
+ };
511
+ return JSON.stringify(sort(value));
512
+ }
513
+ function record(value, label) {
514
+ if (!value || typeof value !== "object" || Array.isArray(value))
515
+ fail(`${label} must be an object`);
516
+ return value;
517
+ }
518
+ function assertKeys(value, allowed, label) {
519
+ for (const key of Object.keys(value))
520
+ if (!allowed.includes(key))
521
+ fail(`${label} contains unknown key ${key}`);
522
+ }
523
+ function fail(message) {
524
+ throw new UsageError(`Invalid frozen workflow plan v4: ${message}.`, "INVALID_JSON_ARGUMENT");
525
+ }