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,786 +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
- * Strict, adapter-neutral workflow source IR.
6
- *
7
- * This is deliberately separate from the durable workflow plan in `../ir`.
8
- * Source adapters produce this representation; the durable freeze lane owns
9
- * resolving executable targets and creating resumable dispatch state.
10
- */
11
- import { types as utilTypes } from "node:util";
12
- import { bundleRefToString, parseBundleRef } from "../../core/asset/asset-ref.js";
13
- import { compareCodePoints, wellFormedUnicode } from "../../core/common.js";
14
- import { validateExtraParams } from "../../core/extra-params.js";
15
- import { checkJsonSchemaDefinition } from "../../core/json-schema.js";
16
- import { parseReference } from "../program/expressions.js";
17
- import { PROGRAM_RETRY_REASONS } from "../program/schema.js";
18
- import { jsonBytes, WORKFLOW_ENGINE_NAME_PATTERN, WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_CONCURRENCY, WORKFLOW_MAX_ENGINE_NAME_LENGTH, WORKFLOW_MAX_EXTRA_PARAMS_BYTES, WORKFLOW_MAX_SCHEMA_BYTES, WORKFLOW_MAX_TIMEOUT_MS, } from "../resource-limits.js";
19
- import { canonicalizeWorkflowCron, canonicalizeWorkflowRun, canonicalizeWorkflowWorkingDirectory, classifyWorkflowStepUses, rejectNulInArgv, validateWorkflowBuiltinCommand, WorkflowSourceSemanticError, } from "./semantics.js";
20
- export const WORKFLOW_SOURCE_IR_VERSION = 1;
21
- export const WORKFLOW_SOURCE_IR_MAX_BYTES = 2 * 1024 * 1024;
22
- export const WORKFLOW_SOURCE_IR_MAX_DEPTH = 64;
23
- export const WORKFLOW_SOURCE_IR_MAX_NODES = 50_000;
24
- export const WORKFLOW_SOURCE_EXTENSION_OWNER_PATTERN = /^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)\/[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
25
- export const WORKFLOW_SOURCE_HOST_SHELLS = ["bash", "sh", "zsh", "pwsh", "powershell", "cmd"];
26
- const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
27
- const HOST_SHELLS = new Set(WORKFLOW_SOURCE_HOST_SHELLS);
28
- /** Strictly decode untrusted plain data before it reaches source lowering. */
29
- export function decodeWorkflowSourceIrV1(input, options = {}) {
30
- const decoded = snapshotPlainData(input);
31
- const root = record(decoded, "source IR");
32
- keys(root, [
33
- "sourceIrVersion",
34
- "name",
35
- "description",
36
- "tags",
37
- "params",
38
- "outputs",
39
- "defaults",
40
- "budget",
41
- "preamble",
42
- "triggers",
43
- "jobs",
44
- "extensions",
45
- "source",
46
- ], "source IR");
47
- if (root.sourceIrVersion !== WORKFLOW_SOURCE_IR_VERSION)
48
- fail("sourceIrVersion must be 1");
49
- nonEmptyString(root.name, "name");
50
- optionalString(root.description, "description");
51
- optionalString(root.preamble, "preamble");
52
- optionalStringList(root.tags, "tags", 256);
53
- validateParams(root.params);
54
- validateOutputs(root.outputs);
55
- validateUnit(root.defaults, "defaults", true);
56
- validateBudget(root.budget);
57
- span(root.source, "source");
58
- extensions(root.extensions, "extensions");
59
- if (!Array.isArray(root.triggers) || root.triggers.length === 0 || root.triggers.length > 64) {
60
- fail("triggers must contain 1 through 64 entries");
61
- }
62
- validateTriggers(root.triggers);
63
- if (!Array.isArray(root.jobs) || root.jobs.length !== 1) {
64
- fail("jobs must contain exactly 1 entry");
65
- }
66
- const jobIds = new Set();
67
- for (const [index, job] of root.jobs.entries())
68
- validateJob(job, index, jobIds, options);
69
- for (const job of root.jobs) {
70
- for (const need of job.needs)
71
- if (!jobIds.has(need))
72
- fail(`job ${job.id} needs missing job ${need}`);
73
- }
74
- return decoded;
75
- }
76
- function validateTrigger(value, index) {
77
- const trigger = record(value, `trigger ${index}`);
78
- if (trigger.kind === "workflow_dispatch") {
79
- keys(trigger, ["kind", "source"], `trigger ${index}`);
80
- span(trigger.source, `trigger ${index} source`);
81
- return;
82
- }
83
- if (trigger.kind !== "schedule")
84
- fail(`trigger ${index} has an unsupported kind`);
85
- keys(trigger, ["kind", "cron", "ordinal", "source"], `trigger ${index}`);
86
- nonEmptyString(trigger.cron, `trigger ${index} cron`);
87
- try {
88
- trigger.cron = canonicalizeWorkflowCron(trigger.cron);
89
- }
90
- catch (cause) {
91
- semanticFail(cause, `trigger ${index} cron`);
92
- }
93
- if (!Number.isSafeInteger(trigger.ordinal) || trigger.ordinal < 0) {
94
- fail(`trigger ${index} ordinal must be a non-negative integer`);
95
- }
96
- span(trigger.source, `trigger ${index} source`);
97
- }
98
- function validateTriggers(value) {
99
- let nextScheduleOrdinal = 0;
100
- let manualSeen = false;
101
- for (const [index, trigger] of value.entries()) {
102
- validateTrigger(trigger, index);
103
- const decoded = trigger;
104
- if (decoded.kind === "schedule") {
105
- if (manualSeen || decoded.ordinal !== nextScheduleOrdinal) {
106
- fail("triggers are not in canonical schedule-then-manual order");
107
- }
108
- nextScheduleOrdinal++;
109
- }
110
- else {
111
- if (manualSeen)
112
- fail("triggers contain duplicate workflow_dispatch entries");
113
- manualSeen = true;
114
- }
115
- }
116
- }
117
- function validateJob(value, index, jobIds, options) {
118
- const job = record(value, `job ${index}`);
119
- keys(job, ["id", "name", "needs", "steps", "extensions", "source"], `job ${index}`);
120
- const id = sourceId(job.id, `job ${index} id`);
121
- if (jobIds.has(id))
122
- fail(`duplicate job id ${id}`);
123
- jobIds.add(id);
124
- optionalString(job.name, `job ${id} name`);
125
- stringList(job.needs, `job ${id} needs`, 256, true);
126
- const needs = job.needs;
127
- if (needs.some((need, needIndex) => needIndex > 0 && compareCodePoints(needs[needIndex - 1] ?? "", need) > 0)) {
128
- fail(`job ${id} needs are not in canonical order`);
129
- }
130
- if (!Array.isArray(job.steps) || job.steps.length === 0 || job.steps.length > 256) {
131
- fail(`job ${id} steps must contain 1 through 256 entries`);
132
- }
133
- const stepIds = new Set();
134
- for (const [stepIndex, step] of job.steps.entries()) {
135
- validateStep(step, id, stepIndex, stepIds, options);
136
- }
137
- validateRouteTargets(job.steps, id);
138
- extensions(job.extensions, `job ${id} extensions`);
139
- span(job.source, `job ${id} source`);
140
- }
141
- function validateStep(value, jobId, index, stepIds, options) {
142
- const step = record(value, `job ${jobId} step ${index}`);
143
- keys(step, [
144
- "id",
145
- "name",
146
- "uses",
147
- "run",
148
- "commandMode",
149
- "exec",
150
- "with",
151
- "env",
152
- "shell",
153
- "workingDirectory",
154
- "unit",
155
- "map",
156
- "route",
157
- "inputs",
158
- "output",
159
- "gate",
160
- "instructions",
161
- "extensions",
162
- "source",
163
- ], `job ${jobId} step ${index}`);
164
- const id = sourceId(step.id, `job ${jobId} step ${index} id`);
165
- if (stepIds.has(id))
166
- fail(`job ${jobId} has duplicate step id ${id}`);
167
- stepIds.add(id);
168
- optionalString(step.name, `step ${id} name`);
169
- const hasUses = typeof step.uses === "string" && step.uses.length > 0;
170
- const hasRun = typeof step.run === "string" && step.run.length > 0;
171
- const hasExec = step.exec !== undefined;
172
- const hasRoute = step.route !== undefined;
173
- const targetCount = Number(hasUses) + Number(hasRun) + Number(hasExec);
174
- if ((hasRoute && targetCount !== 0) || (!hasRoute && targetCount !== 1)) {
175
- fail(`step ${id} must contain exactly one of uses, run, or exec unless it is a route`);
176
- }
177
- if (step.uses !== undefined && !hasUses)
178
- fail(`step ${id} uses must be a non-empty string`);
179
- if (step.run !== undefined && !hasRun)
180
- fail(`step ${id} run must be a non-empty string`);
181
- // A-N3: captured at the outer scope so scalarRecord's task-scoped widening
182
- // (below) can consult it without re-classifying `step.uses`.
183
- let usesTarget;
184
- if (hasUses) {
185
- let target;
186
- try {
187
- target = classifyWorkflowStepUses(step.uses);
188
- }
189
- catch (cause) {
190
- semanticFail(cause, `step ${id} uses`);
191
- }
192
- usesTarget = target;
193
- if (target.kind === "builtin-command") {
194
- if (step.commandMode !== "literal" &&
195
- step.commandMode !== "portable-template" &&
196
- step.commandMode !== "stored-ref") {
197
- fail(`step ${id} akm/command requires an explicit commandMode`);
198
- }
199
- try {
200
- validateWorkflowBuiltinCommand(step.with, step.commandMode);
201
- }
202
- catch (cause) {
203
- semanticFail(cause, `step ${id} uses`);
204
- }
205
- }
206
- else if (step.commandMode !== undefined) {
207
- fail(`step ${id} commandMode is legal only with uses akm/command`);
208
- }
209
- }
210
- else if (step.commandMode !== undefined) {
211
- fail(`step ${id} commandMode is legal only with uses akm/command`);
212
- }
213
- if (hasRun) {
214
- try {
215
- step.run = canonicalizeWorkflowRun(step.run);
216
- }
217
- catch (cause) {
218
- semanticFail(cause, `step ${id} run`);
219
- }
220
- }
221
- validateExec(step.exec, `step ${id} exec`, options);
222
- // A-N3, widened in P3a (spec docs/plans/specs/p3a-plan-v5-child-freeze.md
223
- // §4.2 step 7, row B-10, A-N8): the "must be a scalar" restriction narrows
224
- // to non-task, non-workflow targets only. A tasks/<ref> or workflows/<ref>
225
- // step's with: may carry any JSON value the bounded document front end
226
- // already accepts; freeze (src/workflows/freeze/**) decides what a
227
- // declared input/param actually accepts.
228
- scalarRecord(step.with, `step ${id} with`, true, usesTarget?.kind === "task" || usesTarget?.kind === "workflow");
229
- environment(step.env, `step ${id} env`);
230
- rejectStepWithExpressions(step, id);
231
- rejectExpressionsInRecord(step.env, `step ${id} env`);
232
- if (step.with !== undefined && !hasUses)
233
- fail(`step ${id} with is legal only with uses`);
234
- if ((step.shell !== undefined || step.workingDirectory !== undefined) && !hasRun) {
235
- fail(`step ${id} shell and workingDirectory are legal only with run`);
236
- }
237
- if (step.shell !== undefined && (typeof step.shell !== "string" || !HOST_SHELLS.has(step.shell))) {
238
- fail(`step ${id} has an unsupported shell`);
239
- }
240
- optionalString(step.workingDirectory, `step ${id} workingDirectory`);
241
- if (step.workingDirectory !== undefined) {
242
- try {
243
- step.workingDirectory = canonicalizeWorkflowWorkingDirectory(step.workingDirectory, options.workspaceRoot);
244
- }
245
- catch (cause) {
246
- semanticFail(cause, `step ${id} workingDirectory`);
247
- }
248
- }
249
- validateUnit(step.unit, `step ${id} unit`, false);
250
- if (hasExec && step.unit !== undefined) {
251
- const unit = step.unit;
252
- if (unit.engine !== undefined || unit.model !== undefined || unit.llm !== undefined) {
253
- fail(`step ${id} exec cannot also select an engine, model, or llm override`);
254
- }
255
- }
256
- validateMap(step.map, `step ${id} map`);
257
- validateRoute(step.route, `step ${id} route`);
258
- optionalStringList(step.inputs, `step ${id} inputs`, Infinity);
259
- if (Array.isArray(step.inputs)) {
260
- for (const [inputIndex, input] of step.inputs.entries())
261
- validateReference(input, `step ${id} inputs[${inputIndex}]`);
262
- }
263
- validateSchema(step.output, `step ${id} output`);
264
- validateGate(step.gate, `step ${id} gate`);
265
- optionalString(step.instructions, `step ${id} instructions`);
266
- if (hasRoute && (step.map !== undefined || step.unit !== undefined || step.inputs !== undefined)) {
267
- fail(`step ${id} route cannot contain map, unit, or inputs`);
268
- }
269
- extensions(step.extensions, `step ${id} extensions`);
270
- span(step.source, `step ${id} source`);
271
- }
272
- function validateExec(value, location, options) {
273
- if (value === undefined)
274
- return;
275
- const exec = record(value, location);
276
- keys(exec, ["command", "cwd", "passEnv"], location);
277
- stringList(exec.command, `${location}.command`, Infinity, false);
278
- try {
279
- rejectNulInArgv(exec.command);
280
- }
281
- catch (cause) {
282
- semanticFail(cause, location);
283
- }
284
- optionalString(exec.cwd, `${location}.cwd`);
285
- if (exec.cwd !== undefined) {
286
- try {
287
- exec.cwd = canonicalizeWorkflowWorkingDirectory(exec.cwd, options.workspaceRoot);
288
- }
289
- catch (cause) {
290
- semanticFail(cause, `${location}.cwd`);
291
- }
292
- }
293
- if (exec.passEnv !== undefined) {
294
- stringList(exec.passEnv, `${location}.passEnv`, Infinity, false);
295
- }
296
- if (Array.isArray(exec.passEnv)) {
297
- for (const name of exec.passEnv) {
298
- if (!WORKFLOW_ENV_VAR_NAME_PATTERN.test(name))
299
- fail(`${location}.passEnv has invalid environment name ${name}`);
300
- }
301
- }
302
- }
303
- function validateUnit(value, location, defaults) {
304
- if (value === undefined)
305
- return;
306
- const unit = record(value, location);
307
- const allowed = defaults
308
- ? ["engine", "model", "llm", "timeoutMs", "onError"]
309
- : ["engine", "model", "llm", "timeoutMs", "retry", "onError", "output", "env", "isolation"];
310
- keys(unit, allowed, location);
311
- optionalString(unit.engine, `${location}.engine`);
312
- if (typeof unit.engine === "string") {
313
- const engine = unit.engine.trim();
314
- unit.engine = engine;
315
- if (!WORKFLOW_ENGINE_NAME_PATTERN.test(engine) || engine.length > WORKFLOW_MAX_ENGINE_NAME_LENGTH) {
316
- fail(`${location}.engine has an invalid engine name`);
317
- }
318
- }
319
- optionalString(unit.model, `${location}.model`);
320
- if (typeof unit.model === "string")
321
- unit.model = unit.model.trim();
322
- validateLlm(unit.llm, `${location}.llm`);
323
- if (unit.timeoutMs !== undefined &&
324
- unit.timeoutMs !== null &&
325
- (!Number.isSafeInteger(unit.timeoutMs) ||
326
- unit.timeoutMs < 1 ||
327
- unit.timeoutMs > WORKFLOW_MAX_TIMEOUT_MS)) {
328
- fail(`${location}.timeoutMs must be null or an integer from 1 through ${WORKFLOW_MAX_TIMEOUT_MS}`);
329
- }
330
- if (unit.onError !== undefined && unit.onError !== "fail" && unit.onError !== "continue") {
331
- fail(`${location}.onError must be fail or continue`);
332
- }
333
- if (!defaults) {
334
- validateRetry(unit.retry, `${location}.retry`);
335
- validateSchema(unit.output, `${location}.output`);
336
- optionalStringList(unit.env, `${location}.env`, 256);
337
- if (Array.isArray(unit.env)) {
338
- for (const [index, ref] of unit.env.entries())
339
- validateAssetRef(ref, `${location}.env[${index}]`);
340
- }
341
- if (unit.isolation !== undefined && unit.isolation !== "none" && unit.isolation !== "worktree") {
342
- fail(`${location}.isolation must be none or worktree`);
343
- }
344
- }
345
- }
346
- function validateRetry(value, location) {
347
- if (value === undefined)
348
- return;
349
- const retry = record(value, location);
350
- keys(retry, ["max", "on"], location);
351
- if (!Number.isSafeInteger(retry.max) || retry.max < 0) {
352
- fail(`${location}.max must be a non-negative integer`);
353
- }
354
- stringList(retry.on, `${location}.on`, PROGRAM_RETRY_REASONS.length, false);
355
- for (const reason of retry.on) {
356
- if (!PROGRAM_RETRY_REASONS.includes(reason)) {
357
- fail(`${location}.on contains unknown failure reason ${reason}`);
358
- }
359
- }
360
- }
361
- function validateMap(value, location) {
362
- if (value === undefined)
363
- return;
364
- const map = record(value, location);
365
- keys(map, ["over", "concurrency", "reducer"], location);
366
- nonEmptyString(map.over, `${location}.over`);
367
- validateReference(map.over, `${location}.over`);
368
- if (map.concurrency !== undefined &&
369
- (!Number.isSafeInteger(map.concurrency) ||
370
- map.concurrency < 1 ||
371
- map.concurrency > WORKFLOW_MAX_CONCURRENCY)) {
372
- fail(`${location}.concurrency must be an integer from 1 through ${WORKFLOW_MAX_CONCURRENCY}`);
373
- }
374
- if (map.reducer !== undefined && map.reducer !== "collect" && map.reducer !== "vote") {
375
- fail(`${location}.reducer must be collect or vote`);
376
- }
377
- }
378
- function validateRoute(value, location) {
379
- if (value === undefined)
380
- return;
381
- const route = record(value, location);
382
- keys(route, ["input", "branches", "defaultStepId"], location);
383
- nonEmptyString(route.input, `${location}.input`);
384
- validateReference(route.input, `${location}.input`);
385
- if (!Array.isArray(route.branches) || route.branches.length === 0) {
386
- fail(`${location}.branches must be a non-empty array`);
387
- }
388
- const matches = new Set();
389
- for (const [index, value] of route.branches.entries()) {
390
- const branch = record(value, `${location}.branches[${index}]`);
391
- keys(branch, ["match", "stepId"], `${location}.branches[${index}]`);
392
- nonEmptyString(branch.match, `${location}.branches[${index}].match`);
393
- sourceId(branch.stepId, `${location}.branches[${index}].stepId`);
394
- if (matches.has(branch.match))
395
- fail(`${location} contains duplicate match ${branch.match}`);
396
- matches.add(branch.match);
397
- }
398
- if (route.defaultStepId !== undefined)
399
- sourceId(route.defaultStepId, `${location}.defaultStepId`);
400
- }
401
- function validateGate(value, location) {
402
- if (value === undefined)
403
- return;
404
- const gate = record(value, location);
405
- keys(gate, ["maxLoops", "rubric"], location);
406
- if (gate.maxLoops !== undefined && (!Number.isSafeInteger(gate.maxLoops) || gate.maxLoops < 1)) {
407
- fail(`${location}.maxLoops must be an integer of at least 1`);
408
- }
409
- optionalString(gate.rubric, `${location}.rubric`);
410
- }
411
- function validateRouteTargets(steps, jobId) {
412
- const indexById = new Map(steps.map((step, index) => [step.id, index]));
413
- for (const [index, step] of steps.entries()) {
414
- if (!step.route)
415
- continue;
416
- const targets = [...step.route.branches.map(({ stepId }) => stepId)];
417
- if (step.route.defaultStepId !== undefined)
418
- targets.push(step.route.defaultStepId);
419
- for (const target of targets) {
420
- const targetIndex = indexById.get(target);
421
- if (targetIndex === undefined)
422
- fail(`job ${jobId} route ${step.id} targets missing step ${target}`);
423
- if (targetIndex <= index)
424
- fail(`job ${jobId} route ${step.id} must target a later step`);
425
- }
426
- }
427
- }
428
- function validateReference(value, location, expectedKind) {
429
- const parsed = parseReference(value);
430
- if (!parsed.ok)
431
- fail(`${location} is invalid: ${parsed.message}`);
432
- if (expectedKind && parsed.expr.kind !== expectedKind) {
433
- fail(`${location} must reference a step output (steps.<id>.output...), not a param`);
434
- }
435
- }
436
- function validateParams(value) {
437
- if (value === undefined)
438
- return;
439
- const params = record(value, "params");
440
- if (Object.keys(params).length === 0) {
441
- fail(`params must contain at least one entry`);
442
- }
443
- for (const [name, schema] of Object.entries(params)) {
444
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
445
- fail(`params has invalid name ${name}`);
446
- validateSchema(schema, `params.${name}`, false);
447
- }
448
- }
449
- /**
450
- * Structural re-validation of `outputs:` (P3b, spec §4.2) — the parser
451
- * (`../parser.ts`) already enforces the same rules at authoring time; this is
452
- * the corruption-gate re-check every source-IR field gets, mirroring
453
- * `validateParams` immediately above.
454
- */
455
- function validateOutputs(value) {
456
- if (value === undefined)
457
- return;
458
- const outputs = record(value, "outputs");
459
- if (Object.keys(outputs).length === 0) {
460
- fail(`outputs must contain at least one entry`);
461
- }
462
- for (const [name, declaration] of Object.entries(outputs)) {
463
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
464
- fail(`outputs has invalid name ${name}`);
465
- const decl = record(declaration, `outputs.${name}`);
466
- keys(decl, ["from", "schema"], `outputs.${name}`);
467
- nonEmptyString(decl.from, `outputs.${name}.from`);
468
- validateReference(decl.from, `outputs.${name}.from`, "stepOutput");
469
- if (decl.schema !== undefined)
470
- validateSchema(decl.schema, `outputs.${name}.schema`, false);
471
- }
472
- }
473
- function validateBudget(value) {
474
- if (value === undefined)
475
- return;
476
- const budget = record(value, "budget");
477
- keys(budget, ["maxTokens", "maxUnits"], "budget");
478
- for (const key of ["maxTokens", "maxUnits"]) {
479
- if (budget[key] !== undefined && (!Number.isSafeInteger(budget[key]) || budget[key] < 1)) {
480
- fail(`budget.${key} must be a positive integer`);
481
- }
482
- }
483
- }
484
- function validateLlm(value, location) {
485
- if (value === undefined)
486
- return;
487
- const llm = record(value, location);
488
- keys(llm, [
489
- "temperature",
490
- "maxTokens",
491
- "supportsJsonSchema",
492
- "extraParams",
493
- "contextLength",
494
- "enableThinking",
495
- "reasoningEffort",
496
- ], location);
497
- if (llm.temperature !== undefined && typeof llm.temperature !== "number") {
498
- fail(`${location}.temperature must be a finite number`);
499
- }
500
- for (const key of ["maxTokens", "contextLength"]) {
501
- if (llm[key] !== undefined && (!Number.isSafeInteger(llm[key]) || llm[key] < 1)) {
502
- fail(`${location}.${key} must be a positive integer`);
503
- }
504
- }
505
- for (const key of ["supportsJsonSchema", "enableThinking"]) {
506
- if (llm[key] !== undefined && typeof llm[key] !== "boolean")
507
- fail(`${location}.${key} must be a boolean`);
508
- }
509
- if (llm.reasoningEffort !== undefined &&
510
- (typeof llm.reasoningEffort !== "string" || llm.reasoningEffort.trim() === "")) {
511
- fail(`${location}.reasoningEffort must be a non-empty string`);
512
- }
513
- if (llm.extraParams !== undefined) {
514
- const extra = record(llm.extraParams, `${location}.extraParams`);
515
- const issues = validateExtraParams(extra);
516
- if (issues.length > 0)
517
- fail(`${location}.extraParams is invalid: ${issues[0]?.message ?? "invalid value"}`);
518
- if (jsonBytes(extra) > WORKFLOW_MAX_EXTRA_PARAMS_BYTES) {
519
- fail(`${location}.extraParams exceeds ${WORKFLOW_MAX_EXTRA_PARAMS_BYTES} bytes`);
520
- }
521
- }
522
- }
523
- function validateSchema(value, location, optional = true) {
524
- if (value === undefined && optional)
525
- return;
526
- const schema = record(value, location);
527
- if (jsonBytes(schema) > WORKFLOW_MAX_SCHEMA_BYTES)
528
- fail(`${location} exceeds ${WORKFLOW_MAX_SCHEMA_BYTES} bytes`);
529
- const issue = checkJsonSchemaDefinition(schema)[0];
530
- if (issue)
531
- fail(`${location} is invalid: ${issue.message}`);
532
- }
533
- function validateAssetRef(value, location) {
534
- try {
535
- const parsed = parseBundleRef(value);
536
- if (parsed.fragment !== undefined || bundleRefToString(parsed) !== value)
537
- fail(`${location} is not canonical`);
538
- }
539
- catch (cause) {
540
- fail(`${location} is invalid: ${cause instanceof Error ? cause.message : String(cause)}`);
541
- }
542
- }
543
- function optionalStringList(value, location, max) {
544
- if (value === undefined)
545
- return;
546
- stringList(value, location, max, true);
547
- }
548
- function rejectExpressionsInRecord(value, location) {
549
- if (value === undefined)
550
- return;
551
- for (const [key, item] of Object.entries(record(value, location))) {
552
- if (typeof item === "string" && item.includes("${{"))
553
- fail(`${location}.${key} contains an unsupported expression`);
554
- }
555
- }
556
- /** A-N3: recurses into nested values, since a task step's with: may now carry an object/array. */
557
- function containsUnsupportedExpression(value) {
558
- if (typeof value === "string")
559
- return value.includes("${{");
560
- if (Array.isArray(value))
561
- return value.some((item) => containsUnsupportedExpression(item));
562
- if (value !== null && typeof value === "object") {
563
- return Object.values(value).some((item) => containsUnsupportedExpression(item));
564
- }
565
- return false;
566
- }
567
- function rejectStepWithExpressions(step, id) {
568
- if (step.with === undefined)
569
- return;
570
- for (const [key, item] of Object.entries(record(step.with, `step ${id} with`))) {
571
- if (step.uses === "akm/command" && step.commandMode === "literal" && key === "content")
572
- continue;
573
- if (!containsUnsupportedExpression(item))
574
- continue;
575
- fail(`step ${id} with.${key} contains an unsupported expression`);
576
- }
577
- }
578
- function semanticFail(cause, location) {
579
- if (cause instanceof WorkflowSourceSemanticError)
580
- fail(`${location}: ${cause.message}`);
581
- fail(`${location}: ${cause instanceof Error ? cause.message : String(cause)}`);
582
- }
583
- function snapshotPlainData(input) {
584
- return snapshotValue(input, 0, "source IR", { nodes: 0, bytes: 0 });
585
- }
586
- function snapshotValue(value, depth, location, state) {
587
- state.nodes++;
588
- if (state.nodes > WORKFLOW_SOURCE_IR_MAX_NODES)
589
- fail("source IR exceeds the node limit");
590
- if (depth > WORKFLOW_SOURCE_IR_MAX_DEPTH)
591
- fail("source IR exceeds the depth limit");
592
- if (value === null || typeof value === "boolean") {
593
- countJsonBytes(state, value === null ? "null" : String(value));
594
- return value;
595
- }
596
- if (typeof value === "string") {
597
- if (!wellFormedUnicode(value))
598
- fail(`${location} must contain well-formed Unicode`);
599
- countJsonBytes(state, JSON.stringify(value));
600
- return value;
601
- }
602
- if (typeof value === "number") {
603
- if (!Number.isFinite(value))
604
- fail(`${location} contains a non-finite number`);
605
- const canonical = Object.is(value, -0) ? 0 : value;
606
- countJsonBytes(state, JSON.stringify(canonical));
607
- return canonical;
608
- }
609
- if (typeof value !== "object")
610
- fail(`${location} contains non-JSON data`);
611
- if (utilTypes.isProxy(value))
612
- fail(`${location} must not be a Proxy object`);
613
- return Array.isArray(value)
614
- ? snapshotArray(value, depth, location, state)
615
- : snapshotObject(value, depth, location, state);
616
- }
617
- function snapshotArray(value, depth, location, state) {
618
- if (Object.getPrototypeOf(value) !== Array.prototype)
619
- fail(`${location} must be a plain array`);
620
- const ownKeys = Reflect.ownKeys(value);
621
- const lengthDescriptor = Reflect.getOwnPropertyDescriptor(value, "length");
622
- if (!lengthDescriptor || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value)) {
623
- fail(`${location} has an invalid array length`);
624
- }
625
- const length = lengthDescriptor.value;
626
- if (length < 0 || length > WORKFLOW_SOURCE_IR_MAX_NODES)
627
- fail(`${location} exceeds the array length limit`);
628
- const expected = new Set(["length", ...Array.from({ length }, (_, index) => String(index))]);
629
- for (const key of ownKeys) {
630
- if (typeof key === "symbol")
631
- fail(`${location} contains symbol keys`);
632
- if (!expected.has(key))
633
- fail(`${location} contains unexpected array property ${key}`);
634
- }
635
- const snapshot = new Array(length);
636
- countJsonBytes(state, "[");
637
- for (let index = 0; index < length; index++) {
638
- const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index));
639
- if (!descriptor)
640
- fail(`${location} contains a sparse array`);
641
- if (!("value" in descriptor))
642
- fail(`${location}[${index}] is an accessor property`);
643
- if (!descriptor.enumerable)
644
- fail(`${location}[${index}] is non-enumerable`);
645
- if (index > 0)
646
- countJsonBytes(state, ",");
647
- snapshot[index] = snapshotValue(descriptor.value, depth + 1, `${location}[${index}]`, state);
648
- }
649
- countJsonBytes(state, "]");
650
- return snapshot;
651
- }
652
- function snapshotObject(value, depth, location, state) {
653
- if (Object.getPrototypeOf(value) !== Object.prototype)
654
- fail(`${location} must be a plain object`);
655
- const ownKeys = Reflect.ownKeys(value);
656
- if (ownKeys.length > WORKFLOW_SOURCE_IR_MAX_NODES)
657
- fail(`${location} exceeds the object key limit`);
658
- const snapshot = {};
659
- countJsonBytes(state, "{");
660
- for (const [index, key] of ownKeys.entries()) {
661
- if (typeof key === "symbol")
662
- fail(`${location} contains symbol keys`);
663
- if (!wellFormedUnicode(key))
664
- fail(`${location} contains an ill-formed key`);
665
- if (UNSAFE_KEYS.has(key))
666
- fail(`${location} contains unsafe key ${key}`);
667
- const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
668
- if (!descriptor)
669
- fail(`${location}.${key} has no stable own descriptor`);
670
- if (!("value" in descriptor))
671
- fail(`${location}.${key} is an accessor property`);
672
- if (!descriptor.enumerable)
673
- fail(`${location}.${key} is non-enumerable`);
674
- if (index > 0)
675
- countJsonBytes(state, ",");
676
- countJsonBytes(state, `${JSON.stringify(key)}:`);
677
- Object.defineProperty(snapshot, key, {
678
- configurable: true,
679
- enumerable: true,
680
- value: snapshotValue(descriptor.value, depth + 1, `${location}.${key}`, state),
681
- writable: true,
682
- });
683
- }
684
- countJsonBytes(state, "}");
685
- return snapshot;
686
- }
687
- function countJsonBytes(state, token) {
688
- state.bytes += Buffer.byteLength(token, "utf8");
689
- if (state.bytes > WORKFLOW_SOURCE_IR_MAX_BYTES)
690
- fail("source IR exceeds the byte limit");
691
- }
692
- function record(value, location) {
693
- if (value === null || typeof value !== "object" || Array.isArray(value))
694
- fail(`${location} must be an object`);
695
- return value;
696
- }
697
- function keys(value, allowed, location) {
698
- const accepted = new Set(allowed);
699
- for (const key of Object.keys(value))
700
- if (!accepted.has(key))
701
- fail(`${location} has unknown key ${key}`);
702
- }
703
- function sourceId(value, location) {
704
- nonEmptyString(value, location);
705
- if (!/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/.test(value))
706
- fail(`${location} has an invalid identifier`);
707
- return value;
708
- }
709
- function nonEmptyString(value, location) {
710
- if (typeof value !== "string" || value.trim() === "")
711
- fail(`${location} must be a non-empty string`);
712
- }
713
- function optionalString(value, location) {
714
- if (value !== undefined)
715
- nonEmptyString(value, location);
716
- }
717
- function stringList(value, location, max, allowEmpty) {
718
- if (!Array.isArray(value) || (!allowEmpty && value.length === 0) || value.length > max) {
719
- fail(`${location} must be ${allowEmpty ? "an" : "a non-empty"} array with at most ${max} entries`);
720
- }
721
- const seen = new Set();
722
- for (const [index, item] of value.entries()) {
723
- nonEmptyString(item, `${location}[${index}]`);
724
- if (seen.has(item))
725
- fail(`${location} contains duplicate ${item}`);
726
- seen.add(item);
727
- }
728
- }
729
- /**
730
- * `allowNonScalar` (A-N3, P2b; widened in P3a, A-N8): when true (a
731
- * `tasks/<ref>` or `workflows/<ref>` step's `with:`), the key-grammar check
732
- * still runs but the scalar/null value restriction is skipped entirely — a
733
- * declared object/array-typed input or param, or a `{from: "..."}`
734
- * reference, may decode. Every other target keeps the byte-identical
735
- * scalar-or-null restriction.
736
- */
737
- function scalarRecord(value, location, allowNull, allowNonScalar = false) {
738
- if (value === undefined)
739
- return;
740
- const map = record(value, location);
741
- for (const [key, item] of Object.entries(map)) {
742
- if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(key))
743
- fail(`${location} has invalid key ${key}`);
744
- if (allowNonScalar)
745
- continue;
746
- if (item === null && allowNull)
747
- continue;
748
- if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
749
- fail(`${location}.${key} must be a scalar`);
750
- }
751
- }
752
- }
753
- function environment(value, location) {
754
- if (value === undefined)
755
- return;
756
- const map = record(value, location);
757
- for (const [key, item] of Object.entries(map)) {
758
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
759
- fail(`${location} has invalid environment name ${key}`);
760
- if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
761
- fail(`${location}.${key} must be a string, number, or boolean`);
762
- }
763
- }
764
- }
765
- function extensions(value, location) {
766
- if (value === undefined)
767
- return;
768
- const map = record(value, location);
769
- for (const owner of Object.keys(map)) {
770
- if (!WORKFLOW_SOURCE_EXTENSION_OWNER_PATTERN.test(owner))
771
- fail(`${location} has invalid owner ${owner}`);
772
- }
773
- }
774
- function span(value, location) {
775
- const source = record(value, location);
776
- keys(source, ["path", "start", "end"], location);
777
- nonEmptyString(source.path, `${location}.path`);
778
- if (!Number.isSafeInteger(source.start) || source.start < 1)
779
- fail(`${location}.start is invalid`);
780
- if (!Number.isSafeInteger(source.end) || source.end < source.start) {
781
- fail(`${location}.end is invalid`);
782
- }
783
- }
784
- function fail(message) {
785
- throw new Error(`Invalid workflow source IR v1: ${message}`);
786
- }