akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -1,745 +0,0 @@
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 { compareCodePoints } from "../../core/common.js";
14
- import { UsageError } from "../../core/errors.js";
15
- import { decodeFrozenExecutableIdentity } from "../../execution/executable-identity.js";
16
- import { INPUT_NAME_PATTERN } from "../../execution/input-contract.js";
17
- import { canonicalResolvedExecutionRequest, decodeResolvedExecutionRequest, } from "../../execution/resolved-request.js";
18
- import { decodeExecutionSourceIdentity } from "../../execution/source.js";
19
- import { decodeFrozenRunnerSpec } from "../../integrations/agent/execution-lowering.js";
20
- import { parseReference } from "../program/expressions.js";
21
- import { PROGRAM_PARAM_NAME_PATTERN } from "../program/schema.js";
22
- import { utf8Bytes } from "../resource-limits.js";
23
- import { decodeWorkflowExecSpec, validateWorkflowPlanStructure, } from "./schema.js";
24
- export const WORKFLOW_IR_V5_VERSION = 5;
25
- /**
26
- * Strict v4 corruption gate. No authored source or config is consulted here.
27
- *
28
- * `depth` is the composition-depth recursion counter (0 at the root
29
- * workflow plan; +1 for every embedded child-workflow plan), and `budget` is
30
- * a MUTABLE, tree-wide running total of embedded child-plan bytes charged so
31
- * far. Both are internal to the recursive child-workflow decode path below
32
- * (`decodeChildWorkflowTarget` calls this function again on an embedded
33
- * `frozenPlan`, sharing the SAME `budget` object across the whole recursion —
34
- * mirroring `ChildCompositionContext.budget` on the freeze side) — every
35
- * external caller decodes a plan at the top level and relies on the
36
- * defaults. Re-enforces the composition depth bound AND the aggregate
37
- * embedded-bytes bound at decode as corruption gates (spec docs/plans/specs/
38
- * p3a-plan-v5-child-freeze.md §3.6 step 6, rows A-23, A-N6); the actionable
39
- * freeze-time `COMPOSITION_INVALID` gates live in
40
- * `src/workflows/freeze/targets/child-workflow.ts` (Lane B).
41
- */
42
- export function decodeWorkflowPlanV4(input, depth = 0, budget = { embeddedBytes: 0 }) {
43
- const raw = record(input, "plan");
44
- if (raw.irVersion !== WORKFLOW_IR_V5_VERSION)
45
- fail("irVersion must be 5");
46
- assertKeys(raw, ["irVersion", "title", "params", "paramSchemas", "budget", "execution", "steps", "sourceReadSet", "outputs"], "plan");
47
- if (!Object.hasOwn(raw, "sourceReadSet"))
48
- fail("sourceReadSet is required");
49
- const sourceReadSet = decodeSourceReadSet(raw.sourceReadSet);
50
- validateWorkflowPlanStructure(raw, {
51
- expectedVersion: WORKFLOW_IR_V5_VERSION,
52
- planExtraKeys: ["sourceReadSet", "outputs"],
53
- unitExtraKeys: ["frozenTarget", "environment"],
54
- gateExtraKeys: ["frozenJudge"],
55
- });
56
- const rawSteps = raw.steps;
57
- const stepIds = new Set(rawSteps.map((rawStep) => rawStep.stepId));
58
- const requiredSources = [];
59
- const steps = rawSteps.map((rawStep, index) => {
60
- const step = rawStep;
61
- const sourceStep = record(rawSteps[index], `step ${step.stepId}`);
62
- const gate = decodeGateV4(sourceStep.gate, step.stepId, requiredSources);
63
- if (!step.root)
64
- return Object.freeze({ ...step, gate });
65
- const rawRoot = record(sourceStep.root, `step ${step.stepId} root`);
66
- const root = rawRoot.kind === "map"
67
- ? (() => {
68
- const { template: _template, ...map } = rawRoot;
69
- return {
70
- ...map,
71
- template: decodeUnitV4(record(rawRoot.template, `map ${String(rawRoot.id)} template`), requiredSources, depth, budget),
72
- };
73
- })()
74
- : decodeUnitV4(rawRoot, requiredSources, depth, budget);
75
- return Object.freeze({ ...step, root, gate });
76
- });
77
- assertRequiredSources(sourceReadSet, requiredSources);
78
- const outputs = Object.hasOwn(raw, "outputs") ? decodeWorkflowOutputs(raw.outputs, stepIds) : undefined;
79
- const { outputs: _rawOutputs, ...restRaw } = raw;
80
- return Object.freeze({
81
- ...restRaw,
82
- irVersion: WORKFLOW_IR_V5_VERSION,
83
- sourceReadSet,
84
- steps,
85
- ...(outputs ? { outputs } : {}),
86
- });
87
- }
88
- /**
89
- * Decode a plan's `outputs` (P3b, spec §4.2): a corruption gate mirroring
90
- * {@link decodeInputBindings} — non-empty, sorted-unique keys (canonical wire
91
- * order, same rule `inputBindings` enforces), each name matching the
92
- * `params:` name pattern, each entry's closed key set, `from` re-parsing as a
93
- * `stepOutput` reference whose step id is declared in `plan.steps`.
94
- */
95
- function decodeWorkflowOutputs(value, stepIds) {
96
- const raw = record(value, "plan outputs");
97
- const names = Object.keys(raw);
98
- if (names.length === 0)
99
- fail("plan outputs must be a non-empty object");
100
- let prior;
101
- const outputs = {};
102
- for (const name of names) {
103
- if (!PROGRAM_PARAM_NAME_PATTERN.test(name))
104
- fail(`plan outputs has invalid name ${name}`);
105
- if (prior !== undefined && compareCodePoints(prior, name) >= 0) {
106
- fail("plan outputs must be sorted by unique name");
107
- }
108
- prior = name;
109
- const entry = record(raw[name], `plan outputs.${name}`);
110
- assertKeys(entry, ["from", "schema"], `plan outputs.${name}`);
111
- if (typeof entry.from !== "string" || !entry.from)
112
- fail(`plan outputs.${name} from is invalid`);
113
- const parsed = parseReference(entry.from);
114
- if (!parsed.ok || parsed.expr.kind !== "stepOutput") {
115
- fail(`plan outputs.${name} from must be a steps.<id>.output reference`);
116
- }
117
- if (!stepIds.has(parsed.expr.stepId)) {
118
- fail(`plan outputs.${name} from names step ${parsed.expr.stepId}, which is not in plan.steps`);
119
- }
120
- const schema = Object.hasOwn(entry, "schema") ? record(entry.schema, `plan outputs.${name} schema`) : undefined;
121
- outputs[name] = Object.freeze({ from: entry.from, ...(schema ? { schema: Object.freeze(schema) } : {}) });
122
- }
123
- return Object.freeze(outputs);
124
- }
125
- function decodeUnitV4(raw, requiredSources, depth, budget) {
126
- const id = raw.id;
127
- if (!Object.hasOwn(raw, "frozenTarget"))
128
- fail(`unit ${id} frozenTarget is required`);
129
- if (!Object.hasOwn(raw, "environment"))
130
- fail(`unit ${id} environment is required`);
131
- const environment = decodeEnvironment(raw.environment, id);
132
- const frozenTarget = decodeFrozenTarget(raw.frozenTarget, raw, environment, requiredSources, depth, budget);
133
- const { frozenTarget: _target, environment: _environment, ...core } = raw;
134
- return Object.freeze({
135
- ...core,
136
- frozenTarget,
137
- environment: Object.freeze(environment),
138
- });
139
- }
140
- function decodeGateV4(value, stepId, requiredSources) {
141
- const gate = record(value, `gate ${stepId}`);
142
- const criteria = gate.criteria;
143
- if (criteria.length === 0) {
144
- if (gate.frozenJudge !== null)
145
- fail(`gate ${stepId} without criteria cannot have a frozen judge target`);
146
- return Object.freeze({
147
- kind: "gate",
148
- id: gate.id,
149
- stepId,
150
- criteria,
151
- maxLoops: gate.maxLoops,
152
- frozenJudge: null,
153
- });
154
- }
155
- if (!Object.hasOwn(gate, "frozenJudge")) {
156
- fail(`gate ${stepId} with criteria is missing its frozenJudge field`);
157
- }
158
- if (gate.frozenJudge === null) {
159
- return Object.freeze({
160
- kind: "gate",
161
- id: gate.id,
162
- stepId,
163
- criteria,
164
- maxLoops: gate.maxLoops,
165
- frozenJudge: null,
166
- });
167
- }
168
- const identity = {
169
- kind: "unit",
170
- id: gate.id,
171
- instructions: criteria.join("\n"),
172
- onError: "fail",
173
- isolation: "none",
174
- };
175
- return Object.freeze({
176
- kind: "gate",
177
- id: gate.id,
178
- stepId,
179
- criteria,
180
- maxLoops: gate.maxLoops,
181
- frozenJudge: decodeCommandTarget(record(gate.frozenJudge, `gate ${stepId} frozenJudge`), identity, requiredSources),
182
- });
183
- }
184
- function decodeFrozenTarget(value, unit, environment, requiredSources, depth, budget) {
185
- const target = record(value, `unit ${unit.id} frozenTarget`);
186
- if (target.kind === "command")
187
- return decodeCommandTarget(target, unit, requiredSources);
188
- if (target.kind === "shell")
189
- return decodeShellTarget(target, unit, environment);
190
- if (target.kind === "script")
191
- return decodeScriptTarget(target, unit, requiredSources);
192
- if (target.kind === "child-workflow")
193
- return decodeChildWorkflowTarget(target, unit, depth, budget);
194
- fail(`unit ${unit.id} frozenTarget has unsupported kind ${String(target.kind)}`);
195
- }
196
- /**
197
- * Decode a `kind: "child-workflow"` frozen target (§3.5): the embedded
198
- * COMPLETE child plan, re-verified against its own `planHash` and this
199
- * target's own `contentHash` on every decode (rows A-20, A-21), recursively
200
- * enforcing `irVersion` 5 (row A-22, via the recursive
201
- * {@link decodeWorkflowPlanV4} call), the composition depth bound (row A-23),
202
- * and the AGGREGATE embedded-plan-bytes bound (§3.6 step 6, A-N6) as the
203
- * decoder recurses. `WORKFLOW_MAX_EMBEDDED_CHILD_PLAN_BYTES` — unlike the
204
- * depth bound above — is imported directly from
205
- * `src/workflows/resource-limits.ts` rather than reproduced: that constant
206
- * was already Lane B's when this decode-time re-enforcement was added (no
207
- * lane-ordering constraint left to honor), and re-deriving a byte cap by
208
- * hand invites drift a depth integer does not. `budget` is charged with
209
- * THIS child's own full canonical byte length AFTER it decodes (so any
210
- * further-nested grandchildren it embeds have already charged themselves,
211
- * mirroring the freeze-side `chargeEmbeddedBudget`'s post-recursion charge
212
- * order in `src/workflows/freeze/targets/child-workflow.ts`) — a rejected
213
- * plan therefore never partially charges the budget. `frozenPlan` is
214
- * covered wholesale through `planHash`, so it is deliberately NOT
215
- * re-serialized into `contentHash` (§3.5). No `requiredSources`
216
- * contribution: unlike a command/script target's own referenced source, the
217
- * child's transitive sources live in the child's OWN `sourceReadSet`,
218
- * verified by the recursive decode call itself; the PARENT's
219
- * `sourceReadSet` absorbing the child's files (row B-05) is a freeze-time
220
- * concern (Lane B), not a decode-time structural one.
221
- */
222
- function decodeChildWorkflowTarget(target, unit, depth, budget) {
223
- assertKeys(target, ["kind", "ref", "planHash", "frozenPlan", "contentHash", "via", "taskRef", "inputBindings"], `unit ${unit.id} child workflow target`);
224
- if (typeof target.ref !== "string" || !target.ref)
225
- fail(`unit ${unit.id} child workflow ref is invalid`);
226
- const planHash = digest(target.planHash, `unit ${unit.id} child workflow planHash`);
227
- if (target.via !== "direct" && target.via !== "task")
228
- fail(`unit ${unit.id} child workflow via is invalid`);
229
- const via = target.via;
230
- if (via === "task") {
231
- if (typeof target.taskRef !== "string" || !target.taskRef) {
232
- fail(`unit ${unit.id} child workflow via "task" requires a taskRef`);
233
- }
234
- }
235
- else if (target.taskRef !== undefined) {
236
- fail(`unit ${unit.id} child workflow via "direct" cannot carry a taskRef`);
237
- }
238
- const taskRef = target.taskRef;
239
- const inputBindings = decodeInputBindings(target.inputBindings, `unit ${unit.id} child workflow target`);
240
- const contentHash = digest(target.contentHash, `unit ${unit.id} child workflow contentHash`);
241
- const expectedContentHash = childWorkflowContentHash({ ref: target.ref, planHash, via, taskRef, inputBindings });
242
- if (contentHash !== expectedContentHash) {
243
- fail(`unit ${unit.id} child workflow contentHash does not match its frozen dispatch`);
244
- }
245
- const childDepth = depth + 1;
246
- const frozenPlan = decodeWorkflowPlanV4(target.frozenPlan, childDepth, budget);
247
- const embeddedPlanJson = canonicalJsonLocal(frozenPlan);
248
- const actualPlanHash = sha256(embeddedPlanJson);
249
- if (actualPlanHash !== planHash) {
250
- fail(`unit ${unit.id} child workflow embedded plan does not match its frozen planHash`);
251
- }
252
- budget.embeddedBytes += utf8Bytes(embeddedPlanJson);
253
- return Object.freeze({
254
- kind: "child-workflow",
255
- ref: target.ref,
256
- planHash,
257
- frozenPlan,
258
- contentHash,
259
- via,
260
- ...(taskRef !== undefined ? { taskRef } : {}),
261
- ...(inputBindings ? { inputBindings } : {}),
262
- });
263
- }
264
- /** §3.5's exact `contentHash` formula. */
265
- function childWorkflowContentHash(fields) {
266
- return createHash("sha256")
267
- .update("akm.workflow.child-workflow\0v1\0")
268
- .update(canonicalJsonLocal({
269
- ref: fields.ref,
270
- planHash: fields.planHash,
271
- via: fields.via,
272
- taskRef: fields.taskRef ?? null,
273
- inputBindings: fields.inputBindings ?? null,
274
- }))
275
- .digest("hex");
276
- }
277
- function decodeCommandTarget(target, unit, requiredSources) {
278
- assertKeys(target, [
279
- "kind",
280
- "ref",
281
- "contentHash",
282
- "request",
283
- "runner",
284
- "concurrency",
285
- "cwdIdentity",
286
- "executable",
287
- "gitCommitOid",
288
- "inputBindings",
289
- ], `unit ${unit.id} command target`);
290
- if (target.ref !== null && typeof target.ref !== "string")
291
- fail(`unit ${unit.id} command target ref is invalid`);
292
- const request = decodeResolvedExecutionRequest(target.request);
293
- if (request.authorization.status === "denied")
294
- fail(`unit ${unit.id} command authorization is denied by policy`);
295
- if (Object.hasOwn(request.runtime, "environment")) {
296
- fail(`unit ${unit.id} resolved request runtime.environment is live and cannot be persisted`);
297
- }
298
- const contentHash = digest(target.contentHash, `unit ${unit.id} command contentHash`);
299
- const actualContentHash = sha256(request.command.content);
300
- if (contentHash !== actualContentHash)
301
- fail(`unit ${unit.id} command contentHash does not match request content`);
302
- const runner = decodeFrozenRunnerSpec(target.runner);
303
- if (target.concurrency !== undefined &&
304
- (!Number.isSafeInteger(target.concurrency) ||
305
- target.concurrency < 1 ||
306
- target.concurrency > 64)) {
307
- fail(`unit ${unit.id} frozen target concurrency is invalid`);
308
- }
309
- if (runner.engine !== request.engine.name || runner.kind !== request.engine.kind) {
310
- fail(`unit ${unit.id} frozen runner must match the resolved request engine`);
311
- }
312
- const source = request.command.source;
313
- if (source) {
314
- requiredSources.push(source);
315
- if (target.ref !== source.ref)
316
- fail(`unit ${unit.id} command target ref must match request source`);
317
- }
318
- else if (target.ref !== null) {
319
- fail(`unit ${unit.id} inline command target ref must be null`);
320
- }
321
- if (request.persona)
322
- requiredSources.push(request.persona.source);
323
- const cwdIdentity = Object.hasOwn(target, "cwdIdentity")
324
- ? decodeDirectoryIdentity(target.cwdIdentity, unit.id)
325
- : undefined;
326
- const executable = Object.hasOwn(target, "executable")
327
- ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
328
- : undefined;
329
- if (runner.kind === "agent" && (cwdIdentity !== undefined || executable !== undefined)) {
330
- if (!cwdIdentity || !executable)
331
- fail(`unit ${unit.id} CLI command target requires cwdIdentity and executable`);
332
- if (runner.profile.bin !== executable.requested && runner.profile.bin !== executable.absolutePath) {
333
- fail(`unit ${unit.id} executable does not match the frozen runner bin`);
334
- }
335
- }
336
- else if (runner.kind !== "agent" && executable !== undefined) {
337
- fail(`unit ${unit.id} non-CLI target cannot carry a host executable`);
338
- }
339
- const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
340
- const inputBindings = decodeInputBindings(target.inputBindings, `unit ${unit.id} command target`);
341
- // Force the shared request canonicalizer across every accepted wire request.
342
- canonicalResolvedExecutionRequest(request);
343
- return Object.freeze({
344
- kind: "command",
345
- ref: target.ref,
346
- contentHash,
347
- request,
348
- runner,
349
- ...(target.concurrency !== undefined ? { concurrency: target.concurrency } : {}),
350
- ...(cwdIdentity ? { cwdIdentity } : {}),
351
- ...(executable ? { executable } : {}),
352
- ...(gitCommitOid ? { gitCommitOid } : {}),
353
- ...(inputBindings ? { inputBindings } : {}),
354
- });
355
- }
356
- function decodeShellTarget(target, unit, environment) {
357
- assertKeys(target, ["kind", "contentHash", "exec", "cwdIdentity", "executable", "gitCommitOid", "inputBindings"], `unit ${unit.id} shell target`);
358
- const exec = decodeWorkflowExecSpec(target.exec, `unit ${unit.id} shell target exec`);
359
- const cwdIdentity = decodeDirectoryIdentity(target.cwdIdentity, unit.id);
360
- const executable = Object.hasOwn(target, "executable")
361
- ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
362
- : undefined;
363
- if (executable && exec.command[0] !== executable.requested && exec.command[0] !== executable.absolutePath) {
364
- fail(`unit ${unit.id} shell executable does not match the frozen command`);
365
- }
366
- const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
367
- const contentHash = digest(target.contentHash, `unit ${unit.id} shell contentHash`);
368
- // inputBindings deliberately sits OUTSIDE this preimage (P2b A-N7): identity
369
- // coverage for a task-composed unit comes from computeUnitInputHash's own
370
- // frozenTarget field (step-work.ts), which hashes this whole target anyway.
371
- const expected = createHash("sha256")
372
- .update("akm.workflow.shell.v1\0")
373
- .update(canonicalJsonLocal({ exec, environment, cwdIdentity }))
374
- .digest("hex");
375
- if (contentHash !== expected)
376
- fail(`unit ${unit.id} shell contentHash does not match its frozen dispatch`);
377
- const inputBindings = decodeInputBindings(target.inputBindings, `unit ${unit.id} shell target`);
378
- return Object.freeze({
379
- kind: "shell",
380
- contentHash,
381
- exec,
382
- cwdIdentity,
383
- ...(executable ? { executable } : {}),
384
- ...(gitCommitOid ? { gitCommitOid } : {}),
385
- ...(inputBindings ? { inputBindings } : {}),
386
- });
387
- }
388
- function decodeScriptTarget(target, unit, requiredSources) {
389
- assertKeys(target, [
390
- "kind",
391
- "ref",
392
- "contentHash",
393
- "exec",
394
- "interpreter",
395
- "extension",
396
- "bytesBase64",
397
- "byteLength",
398
- "cwdIdentity",
399
- "materialization",
400
- "executable",
401
- "gitCommitOid",
402
- "inputBindings",
403
- ], `unit ${unit.id} script target`);
404
- const exec = decodeWorkflowExecSpec(target.exec, `unit ${unit.id} script target exec`);
405
- if (typeof target.ref !== "string" || !target.ref.includes("//"))
406
- fail(`unit ${unit.id} script ref is invalid`);
407
- if (typeof target.interpreter !== "string" || !target.interpreter)
408
- fail(`unit ${unit.id} script interpreter is invalid`);
409
- if (typeof target.extension !== "string" || !/^\.[A-Za-z0-9]+$/.test(target.extension)) {
410
- fail(`unit ${unit.id} script extension is invalid`);
411
- }
412
- if (typeof target.bytesBase64 !== "string")
413
- fail(`unit ${unit.id} script bytesBase64 is invalid`);
414
- const bytes = Buffer.from(target.bytesBase64, "base64");
415
- if (bytes.toString("base64") !== target.bytesBase64)
416
- fail(`unit ${unit.id} script bytesBase64 is noncanonical`);
417
- if (!Number.isSafeInteger(target.byteLength) || target.byteLength !== bytes.byteLength) {
418
- fail(`unit ${unit.id} script byteLength does not match frozen bytes`);
419
- }
420
- const contentHash = digest(target.contentHash, `unit ${unit.id} script contentHash`);
421
- if (contentHash !== sha256(bytes))
422
- fail(`unit ${unit.id} script contentHash does not match frozen bytes`);
423
- if (target.materialization !== "ephemeral-0700-delete")
424
- fail(`unit ${unit.id} script materialization is invalid`);
425
- const cwdIdentity = decodeDirectoryIdentity(target.cwdIdentity, unit.id);
426
- const executable = Object.hasOwn(target, "executable")
427
- ? decodeFrozenExecutableIdentity(target.executable, `unit ${unit.id} executable`)
428
- : undefined;
429
- const gitCommitOid = decodeGitCommitOid(target.gitCommitOid, unit);
430
- const inputBindings = decodeInputBindings(target.inputBindings, `unit ${unit.id} script target`);
431
- requiredSources.push({ ref: target.ref, bundle: "", adapter: "", file: "", hash: contentHash });
432
- return Object.freeze({
433
- kind: "script",
434
- ref: target.ref,
435
- contentHash,
436
- exec,
437
- interpreter: target.interpreter,
438
- extension: target.extension,
439
- bytesBase64: target.bytesBase64,
440
- byteLength: target.byteLength,
441
- cwdIdentity,
442
- materialization: "ephemeral-0700-delete",
443
- ...(executable ? { executable } : {}),
444
- ...(gitCommitOid ? { gitCommitOid } : {}),
445
- ...(inputBindings ? { inputBindings } : {}),
446
- });
447
- }
448
- /**
449
- * Decode a frozen target's `inputBindings` (P2b §3.2 A-N7): a composing
450
- * step's `with:` normalized against the composed task's declared inputs.
451
- * Closed `kind`, `INPUT_NAME_PATTERN` name, unique and sorted by name (never
452
- * `[]` — absence is the identity-preserving default). A `literal` entry
453
- * requires `value`; a `reference` entry requires a `parseReference`-valid
454
- * `from` plus the declaration's bounded `schema` (§3.6's widened reference
455
- * arm, `src/execution/input-contract.ts`).
456
- */
457
- function decodeInputBindings(value, label) {
458
- if (value === undefined)
459
- return undefined;
460
- if (!Array.isArray(value) || value.length === 0)
461
- fail(`${label} inputBindings must be a non-empty array`);
462
- let prior;
463
- const bindings = value.map((raw, index) => {
464
- const binding = record(raw, `${label} inputBindings[${index}]`);
465
- if (typeof binding.name !== "string" || !INPUT_NAME_PATTERN.test(binding.name)) {
466
- fail(`${label} inputBindings[${index}] name is invalid`);
467
- }
468
- if (prior !== undefined && compareCodePoints(prior, binding.name) >= 0) {
469
- fail(`${label} inputBindings must be sorted by unique name`);
470
- }
471
- prior = binding.name;
472
- if (binding.kind === "literal") {
473
- assertKeys(binding, ["kind", "name", "value"], `${label} inputBindings[${index}]`);
474
- if (!Object.hasOwn(binding, "value"))
475
- fail(`${label} inputBindings[${index}] literal binding requires value`);
476
- return Object.freeze({ kind: "literal", name: binding.name, value: binding.value });
477
- }
478
- if (binding.kind === "reference") {
479
- assertKeys(binding, ["kind", "name", "from", "schema"], `${label} inputBindings[${index}]`);
480
- if (typeof binding.from !== "string" || !parseReference(binding.from).ok) {
481
- fail(`${label} inputBindings[${index}] from is not a valid reference`);
482
- }
483
- const schema = record(binding.schema, `${label} inputBindings[${index}] schema`);
484
- return Object.freeze({
485
- kind: "reference",
486
- name: binding.name,
487
- from: binding.from,
488
- schema: Object.freeze(schema),
489
- });
490
- }
491
- return fail(`${label} inputBindings[${index}] has unsupported kind ${String(binding.kind)}`);
492
- });
493
- return Object.freeze(bindings);
494
- }
495
- function decodeGitCommitOid(value, unit) {
496
- if (unit.isolation === "worktree") {
497
- if (typeof value !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value)) {
498
- fail(`unit ${unit.id} worktree target requires a canonical gitCommitOid`);
499
- }
500
- return value;
501
- }
502
- if (value !== undefined)
503
- fail(`unit ${unit.id} gitCommitOid is only valid for worktree isolation`);
504
- return undefined;
505
- }
506
- function decodeDirectoryIdentity(value, unitId) {
507
- const identity = record(value, `unit ${unitId} cwdIdentity`);
508
- const fields = [
509
- "requestedRoot",
510
- "realRoot",
511
- "rootDevice",
512
- "rootInode",
513
- "requestedCwd",
514
- "realCwd",
515
- "cwdDevice",
516
- "cwdInode",
517
- ];
518
- assertKeys(identity, fields, `unit ${unitId} cwdIdentity`);
519
- for (const field of fields) {
520
- if (typeof identity[field] !== "string" || !identity[field])
521
- fail(`unit ${unitId} cwdIdentity.${field} is invalid`);
522
- }
523
- for (const field of ["requestedRoot", "realRoot", "requestedCwd", "realCwd"]) {
524
- if (!path.isAbsolute(identity[field]))
525
- fail(`unit ${unitId} cwdIdentity.${field} must be absolute`);
526
- }
527
- return Object.freeze(identity);
528
- }
529
- function decodeEnvironment(value, unitId) {
530
- if (!Array.isArray(value))
531
- fail(`unit ${unitId} environment must be an array`);
532
- const out = [];
533
- const names = new Set();
534
- for (const [index, raw] of value.entries()) {
535
- const binding = record(raw, `unit ${unitId} environment[${index}]`);
536
- if (binding.kind === "literal") {
537
- assertKeys(binding, ["kind", "name", "value"], `unit ${unitId} environment[${index}]`);
538
- environmentName(binding.name, unitId, index);
539
- if (typeof binding.value !== "string")
540
- fail(`unit ${unitId} literal environment value must be a string`);
541
- if (literalLooksSecret(binding.name, binding.value)) {
542
- fail(`unit ${unitId} literal environment value is secret-shaped`);
543
- }
544
- out.push(Object.freeze({ kind: "literal", name: binding.name, value: binding.value }));
545
- }
546
- else if (binding.kind === "pass-through") {
547
- assertKeys(binding, ["kind", "name"], `unit ${unitId} environment[${index}]`);
548
- environmentName(binding.name, unitId, index);
549
- out.push(Object.freeze({ kind: "pass-through", name: binding.name }));
550
- }
551
- else if (binding.kind === "env-ref") {
552
- assertKeys(binding, ["kind", "ref", "owner", "keys", "secretNames", "precedence"], `unit ${unitId} environment[${index}]`);
553
- if (typeof binding.ref !== "string" || !binding.ref.includes("//")) {
554
- fail(`unit ${unitId} environment[${index}] env-ref must be fully qualified`);
555
- }
556
- const owner = decodeEnvironmentOwner(binding.owner, binding.ref, unitId, index);
557
- const keys = sortedUniqueStrings(binding.keys, `unit ${unitId} environment[${index}] keys`, (value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value));
558
- const secretNames = sortedUniqueStrings(binding.secretNames, `unit ${unitId} environment[${index}] secretNames`, (value) => /^[A-Za-z0-9_./-]+$/.test(value) && !value.includes(".."));
559
- if (!Number.isSafeInteger(binding.precedence) || binding.precedence < 0) {
560
- fail(`unit ${unitId} environment[${index}] precedence is invalid`);
561
- }
562
- out.push(Object.freeze({
563
- kind: "env-ref",
564
- ref: binding.ref,
565
- owner,
566
- keys: Object.freeze(keys),
567
- secretNames: Object.freeze(secretNames),
568
- precedence: binding.precedence,
569
- }));
570
- }
571
- else {
572
- fail(`unit ${unitId} environment[${index}] has unsupported kind`);
573
- }
574
- if (binding.kind !== "env-ref") {
575
- const name = binding.name;
576
- if (names.has(name))
577
- fail(`unit ${unitId} environment contains duplicate name ${name}`);
578
- names.add(name);
579
- }
580
- }
581
- return out;
582
- }
583
- function decodeEnvironmentOwner(value, ref, unitId, index) {
584
- const label = `unit ${unitId} environment[${index}] owner`;
585
- const owner = record(value, label);
586
- const fields = [
587
- "bundle",
588
- "adapter",
589
- "requestedRoot",
590
- "realRoot",
591
- "rootPhysicalIdentity",
592
- "requestedPath",
593
- "realPath",
594
- "relativePath",
595
- ];
596
- assertKeys(owner, fields, label);
597
- for (const field of fields) {
598
- if (typeof owner[field] !== "string" || !owner[field])
599
- fail(`${label}.${field} is invalid`);
600
- }
601
- if (!ref.startsWith(`${owner.bundle}//`))
602
- fail(`${label}.bundle does not own env-ref ${ref}`);
603
- for (const field of ["requestedRoot", "realRoot", "requestedPath", "realPath"]) {
604
- if (!path.isAbsolute(owner[field]))
605
- fail(`${label}.${field} must be absolute`);
606
- }
607
- const relativePath = owner.relativePath;
608
- if (relativePath.includes("\\") ||
609
- relativePath === "." ||
610
- relativePath.startsWith("../") ||
611
- path.isAbsolute(relativePath)) {
612
- fail(`${label}.relativePath is not canonical`);
613
- }
614
- if (path.resolve(owner.requestedRoot, relativePath) !== path.resolve(owner.requestedPath)) {
615
- fail(`${label}.requestedPath does not match relativePath`);
616
- }
617
- if (path.resolve(owner.realRoot, relativePath) !== path.resolve(owner.realPath)) {
618
- fail(`${label}.realPath does not match relativePath`);
619
- }
620
- return Object.freeze(owner);
621
- }
622
- function sortedUniqueStrings(value, label, accepts) {
623
- if (!Array.isArray(value))
624
- fail(`${label} must be an array`);
625
- const out = [];
626
- let prior;
627
- for (const item of value) {
628
- if (typeof item !== "string" || !accepts(item))
629
- fail(`${label} contains an invalid value`);
630
- if (prior !== undefined && compareCodePoints(prior, item) >= 0)
631
- fail(`${label} must be sorted and unique`);
632
- prior = item;
633
- out.push(item);
634
- }
635
- return out;
636
- }
637
- function decodeSourceReadSet(value) {
638
- if (!Array.isArray(value) || value.length === 0)
639
- fail("sourceReadSet must contain at least one source identity");
640
- const out = [];
641
- const logical = new Set();
642
- const physical = new Map();
643
- let priorKey;
644
- for (const [index, raw] of value.entries()) {
645
- const snapshot = record(raw, `sourceReadSet[${index}]`);
646
- assertKeys(snapshot, ["identity", "containmentPhysicalIdentity", "physicalIdentity", "size"], `sourceReadSet[${index}]`);
647
- const identity = decodeExecutionSourceIdentity(snapshot.identity, `sourceReadSet[${index}].identity`);
648
- const key = sourceKey(identity);
649
- if (logical.has(key))
650
- fail(`sourceReadSet contains duplicate identity key ${key}`);
651
- if (typeof snapshot.containmentPhysicalIdentity !== "string" || !snapshot.containmentPhysicalIdentity) {
652
- fail(`sourceReadSet[${index}] containment physical identity is invalid`);
653
- }
654
- if (typeof snapshot.physicalIdentity !== "string" || !snapshot.physicalIdentity) {
655
- fail(`sourceReadSet[${index}] physical identity is invalid`);
656
- }
657
- if (!Number.isSafeInteger(snapshot.size) || snapshot.size < 0)
658
- fail(`sourceReadSet[${index}] size is invalid`);
659
- const physicalKey = `${snapshot.containmentPhysicalIdentity}\0${snapshot.physicalIdentity}`;
660
- const alias = physical.get(physicalKey);
661
- if (alias !== undefined && alias !== key)
662
- fail(`sourceReadSet logical refs alias the same physical file identity`);
663
- physical.set(physicalKey, key);
664
- if (priorKey !== undefined && compareCodePoints(priorKey, key) >= 0) {
665
- fail("sourceReadSet is not in canonical ref, adapter, file sort order");
666
- }
667
- logical.add(key);
668
- priorKey = key;
669
- out.push(Object.freeze({
670
- identity,
671
- containmentPhysicalIdentity: snapshot.containmentPhysicalIdentity,
672
- physicalIdentity: snapshot.physicalIdentity,
673
- size: snapshot.size,
674
- }));
675
- }
676
- return out;
677
- }
678
- function assertRequiredSources(readSet, required) {
679
- for (const identity of required) {
680
- const found = readSet.find((snapshot) => snapshot.identity.ref === identity.ref);
681
- if (!found)
682
- fail(`source read-set is missing required command/persona/script source ${identity.ref}`);
683
- if (identity.bundle && sourceKey(found.identity) !== sourceKey(identity)) {
684
- fail(`source read-set identity mismatch for ${identity.ref}`);
685
- }
686
- if (found.identity.hash !== identity.hash)
687
- fail(`source read-set hash mismatch for ${identity.ref}`);
688
- }
689
- }
690
- function sourceKey(identity) {
691
- return `${identity.ref}\0${identity.adapter}\0${identity.file}`;
692
- }
693
- function environmentName(value, unitId, index) {
694
- if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
695
- fail(`unit ${unitId} environment[${index}] has an invalid name`);
696
- }
697
- }
698
- function literalLooksSecret(name, value) {
699
- const key = name.toLowerCase();
700
- if (/(?:secret|token|password|passwd|api[_-]?key|access[_-]?key|private[_-]?key|credential|bearer|client_secret)/.test(key)) {
701
- return true;
702
- }
703
- const candidate = value.trim();
704
- if (candidate.length < 20 || /\s/.test(candidate))
705
- return false;
706
- if (/^(?:sk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|xox[baprs]-|AKIA|ASIA|AIza|ya29\.|-----BEGIN)/.test(candidate)) {
707
- return true;
708
- }
709
- const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((pattern) => pattern.test(candidate)).length;
710
- return (candidate.length >= 24 && classes >= 3) || (candidate.length >= 32 && /^[A-Za-z0-9+/=_-]+$/.test(candidate));
711
- }
712
- function digest(value, label) {
713
- if (typeof value !== "string" || !/^[0-9a-f]{64}$/.test(value))
714
- fail(`${label} must be a SHA-256 digest`);
715
- return value;
716
- }
717
- function sha256(value) {
718
- return createHash("sha256").update(value).digest("hex");
719
- }
720
- function canonicalJsonLocal(value) {
721
- const sort = (entry) => {
722
- if (Array.isArray(entry))
723
- return entry.map(sort);
724
- if (entry && typeof entry === "object") {
725
- return Object.fromEntries(Object.entries(entry)
726
- .sort(([left], [right]) => compareCodePoints(left, right))
727
- .map(([key, nested]) => [key, sort(nested)]));
728
- }
729
- return entry;
730
- };
731
- return JSON.stringify(sort(value));
732
- }
733
- function record(value, label) {
734
- if (!value || typeof value !== "object" || Array.isArray(value))
735
- fail(`${label} must be an object`);
736
- return value;
737
- }
738
- function assertKeys(value, allowed, label) {
739
- for (const key of Object.keys(value))
740
- if (!allowed.includes(key))
741
- fail(`${label} contains unknown key ${key}`);
742
- }
743
- function fail(message) {
744
- throw new UsageError(`Invalid frozen workflow plan v4: ${message}.`, "INVALID_JSON_ARGUMENT");
745
- }