akm-cli 0.9.1 → 0.9.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -0,0 +1,281 @@
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
+ * Pure task-v3 runtime projection.
6
+ *
7
+ * This is the only bridge from authored task-v3 source into executable work.
8
+ * It performs all source/config/asset reads before the task runner reserves a
9
+ * durable attempt and returns immutable snapshots. In particular, script work
10
+ * contains frozen bytes and their digest, never a path that can be reread when
11
+ * a delayed or resumed dispatch begins.
12
+ */
13
+ import { createHash } from "node:crypto";
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import { prepareCommandInvocation, } from "../commands/command/command-execution.js";
17
+ import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
18
+ import { ConfigError, NotFoundError, UsageError } from "../core/errors.js";
19
+ import { DURATION_UNITS, parseDuration } from "../core/time.js";
20
+ import { captureFrozenDirectoryIdentity } from "../execution/directory-identity.js";
21
+ import { isPortableExecutionAgentSelector } from "../execution/source.js";
22
+ import { requireAuthorizedExecutionPlan } from "../integrations/agent/execution-cascade.js";
23
+ import { lowerResolvedExecutionRequest } from "../integrations/agent/execution-lowering.js";
24
+ import { resolveAssetPath } from "../sources/resolve.js";
25
+ import { detectSecretShapedParams } from "../workflows/exec/param-secrets.js";
26
+ import { compileWorkflowPlan } from "../workflows/ir/compile.js";
27
+ import { compileWorkflowSource } from "../workflows/source-ir/compile.js";
28
+ import { isInferredSecretName } from "./log-redaction.js";
29
+ import { isBunStandaloneMain } from "./resolve-akm-bin.js";
30
+ import { SCHEDULED_TASK_CONTEXT_KEYS } from "./scheduler-invocation.js";
31
+ const SCRIPT_INTERPRETERS = Object.freeze({
32
+ ".sh": "sh",
33
+ ".ts": "bun",
34
+ ".js": "bun",
35
+ ".ps1": "powershell",
36
+ ".cmd": "cmd",
37
+ ".bat": "cmd",
38
+ ".py": "python",
39
+ ".rb": "ruby",
40
+ ".go": "go",
41
+ ".pl": "perl",
42
+ ".php": "php",
43
+ ".lua": "lua",
44
+ ".r": "rscript",
45
+ ".swift": "swift",
46
+ ".kt": "kotlin",
47
+ ".kts": "kotlin",
48
+ });
49
+ function own(value, key) {
50
+ return value !== undefined && Object.hasOwn(value, key);
51
+ }
52
+ function environmentSnapshot(environment) {
53
+ const out = Object.create(null);
54
+ for (const key of Object.keys(environment ?? {}).sort()) {
55
+ const raw = environment?.[key];
56
+ if (raw === undefined)
57
+ continue;
58
+ const value = String(raw);
59
+ if (isInferredSecretName(key) || detectSecretShapedParams({ [key]: value }).length > 0) {
60
+ throw new UsageError(`Task v3 env.${key} is a secret-shaped literal env value. Store credentials in a secret/env binding rather than durable task source.`, "INVALID_FLAG_VALUE");
61
+ }
62
+ Object.defineProperty(out, key, { value, enumerable: true, configurable: false, writable: false });
63
+ }
64
+ return Object.freeze(out);
65
+ }
66
+ /** Merge source env with the authoritative, closed scheduler directory context. */
67
+ function commandEnvironmentSnapshot(environment, schedulerContext) {
68
+ const out = Object.create(null);
69
+ for (const key of Object.keys(environment)) {
70
+ Object.defineProperty(out, key, { value: environment[key], enumerable: true, configurable: true, writable: true });
71
+ }
72
+ for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
73
+ const value = schedulerContext?.[key];
74
+ if (!value)
75
+ continue;
76
+ Object.defineProperty(out, key, { value, enumerable: true, configurable: true, writable: true });
77
+ }
78
+ return Object.freeze(out);
79
+ }
80
+ function normalizeTimeout(value) {
81
+ if (value === undefined || value === null || typeof value === "number")
82
+ return value;
83
+ const parsed = parseDuration(value, DURATION_UNITS);
84
+ if (parsed === null)
85
+ throw new UsageError(`Invalid task timeout ${JSON.stringify(value)}.`, "INVALID_FLAG_VALUE");
86
+ return parsed;
87
+ }
88
+ function qualifyOwnedRef(ref, context) {
89
+ const parsed = parseBundleRef(ref);
90
+ const bundle = parsed.bundle ?? context.bundleName;
91
+ return { parsed, qualified: makeBundleRef(bundle, parsed.conceptId) };
92
+ }
93
+ function currentExecutionValues(document, context, environment) {
94
+ const akm = document.akm;
95
+ const agent = akm?.agent;
96
+ return Object.freeze({
97
+ ...(own(akm, "agent")
98
+ ? {
99
+ agent: typeof agent === "string" && isPortableExecutionAgentSelector(agent)
100
+ ? qualifyOwnedRef(agent, context).qualified
101
+ : agent,
102
+ }
103
+ : {}),
104
+ ...(own(akm, "engine") ? { engine: akm?.engine } : {}),
105
+ ...(own(akm, "model") ? { model: akm?.model } : {}),
106
+ ...(own(akm, "inference") ? { inference: akm?.inference } : {}),
107
+ ...(own(akm, "outputSchema") ? { outputSchema: akm?.outputSchema } : {}),
108
+ ...(own(akm, "tools") ? { tools: akm?.tools } : {}),
109
+ ...(own(akm, "timeout") ? { timeout: akm?.timeout } : {}),
110
+ workspace: context.bundleRoot,
111
+ environment,
112
+ });
113
+ }
114
+ function base(document, context, environment) {
115
+ const timeoutMs = normalizeTimeout(document.akm?.timeout);
116
+ return Object.freeze({
117
+ taskId: context.taskId,
118
+ taskRef: context.taskRef,
119
+ enabled: document.akm?.enabled !== false,
120
+ environment,
121
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
122
+ redact: Object.freeze([...(document.akm?.redact ?? [])]),
123
+ });
124
+ }
125
+ async function resolvedOwnedAsset(qualified, type, context) {
126
+ const parsed = parseBundleRef(qualified);
127
+ const prefix = `${type}s/`;
128
+ const name = parsed.conceptId.slice(prefix.length);
129
+ if (context.resolveAsset) {
130
+ const resolved = await context.resolveAsset({ bundle: parsed.bundle, type, name, ref: qualified });
131
+ return typeof resolved === "string"
132
+ ? Object.freeze({ file: resolved, bundleRoot: context.bundleRoot })
133
+ : Object.freeze({ file: resolved.file, bundleRoot: resolved.bundleRoot });
134
+ }
135
+ if (parsed.bundle !== context.bundleName) {
136
+ throw new NotFoundError(`Task target ${JSON.stringify(qualified)} names bundle ${JSON.stringify(parsed.bundle)}, but no bundle resolver was provided.`, "ASSET_NOT_FOUND");
137
+ }
138
+ return Object.freeze({
139
+ file: await resolveAssetPath(context.bundleRoot, type, name),
140
+ bundleRoot: context.bundleRoot,
141
+ });
142
+ }
143
+ function scriptInterpreter(extension, ref) {
144
+ const interpreter = SCRIPT_INTERPRETERS[extension];
145
+ if (!interpreter) {
146
+ throw new UsageError(`Task v3 script target ${JSON.stringify(ref)} has no closed runtime interpreter for extension ${JSON.stringify(extension)}.`, "INVALID_FLAG_VALUE");
147
+ }
148
+ if (interpreter !== "bun")
149
+ return interpreter;
150
+ if (!process.versions.bun) {
151
+ throw new UsageError(`Task v3 script target ${JSON.stringify(ref)} requires Bun for ${extension} execution, but this runtime cannot provide it.`, "INVALID_FLAG_VALUE");
152
+ }
153
+ return isBunStandaloneMain() ? "bun-standalone" : "bun";
154
+ }
155
+ function captureDirectoryIdentity(bundleRoot, workingDirectory) {
156
+ try {
157
+ return captureFrozenDirectoryIdentity(bundleRoot, workingDirectory);
158
+ }
159
+ catch (cause) {
160
+ if (cause instanceof UsageError)
161
+ throw cause;
162
+ throw new UsageError(`Task working directory ${JSON.stringify(workingDirectory ?? ".")} cannot be physically verified: ${cause instanceof Error ? cause.message : String(cause)}`, "INVALID_FLAG_VALUE");
163
+ }
164
+ }
165
+ function defaultTaskShell(platform) {
166
+ return platform === "win32" ? "powershell" : "sh";
167
+ }
168
+ function validatePreparedCommand(invocation, context) {
169
+ const request = requireAuthorizedExecutionPlan(invocation.plan);
170
+ if (!request.engine.name) {
171
+ throw new ConfigError(`Task ${JSON.stringify(context.taskRef)} has no resolved execution engine. Configure defaults.engine or akm.engine before running it.`, "INVALID_CONFIG_FILE");
172
+ }
173
+ // Lowering is pure. Running it before the durable-attempt boundary proves
174
+ // the selected target is transport-projectable; dispatch repeats the same
175
+ // deterministic projection from this frozen request/config snapshot.
176
+ lowerResolvedExecutionRequest(request, invocation.config);
177
+ return invocation;
178
+ }
179
+ function validateWorkflowRuntimeSource(file, workspaceRoot, readFile) {
180
+ const source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readFile(file, workspaceRoot));
181
+ const compiled = compileWorkflowSource(source, { path: file, workspaceRoot });
182
+ if (!compiled.ok) {
183
+ const detail = compiled.errors.map((error) => `${error.path}:${error.line}: ${error.message}`).join("; ");
184
+ throw new UsageError(`Task workflow target is not projectable: ${detail}`, "INVALID_FLAG_VALUE");
185
+ }
186
+ const planned = compileWorkflowPlan(compiled.ir, path.basename(file, path.extname(file)));
187
+ if (!planned.ok) {
188
+ const detail = planned.errors.map((error) => `${file}:${error.line}: ${error.message}`).join("; ");
189
+ throw new UsageError(`Task workflow target is not projectable: ${detail}`, "INVALID_FLAG_VALUE");
190
+ }
191
+ }
192
+ /** Project one canonical task-v3 source into immutable executable work. */
193
+ export async function prepareTaskV3Execution(document, context) {
194
+ const environment = environmentSnapshot(document.env);
195
+ const commandEnvironment = commandEnvironmentSnapshot(environment, context.schedulerContext);
196
+ const common = base(document, context, environment);
197
+ if (document.target.kind === "run") {
198
+ const cwdIdentity = captureDirectoryIdentity(context.bundleRoot, document.target.workingDirectory);
199
+ return Object.freeze({
200
+ ...common,
201
+ kind: "shell",
202
+ command: document.target.run,
203
+ shell: document.target.shell ?? defaultTaskShell(context.platform ?? process.platform),
204
+ cwd: cwdIdentity.realCwd,
205
+ cwdIdentity,
206
+ });
207
+ }
208
+ const target = document.target.uses;
209
+ if (target.kind === "github-action") {
210
+ throw new UsageError(`GitHub action ${JSON.stringify(target.ref)} is recognized but remote action acquisition is unsupported in 0.9.2.`, "INVALID_FLAG_VALUE");
211
+ }
212
+ if (target.kind === "builtin-command") {
213
+ const command = document.target.command;
214
+ if (!command)
215
+ throw new Error("invariant: parsed built-in command target has no command action");
216
+ const action = command.kind === "stored"
217
+ ? {
218
+ ref: qualifyOwnedRef(command.ref, context).qualified,
219
+ ...(command.arguments !== undefined ? { arguments: command.arguments } : {}),
220
+ }
221
+ : { content: command.content, ...(command.arguments !== undefined ? { arguments: command.arguments } : {}) };
222
+ const invocation = validatePreparedCommand(await (context.prepareCommand ?? prepareCommandInvocation)({
223
+ action,
224
+ config: context.config,
225
+ invocationKind: "task",
226
+ current: currentExecutionValues(document, context, commandEnvironment),
227
+ ...(context.commandSourceLoader ? { sourceLoader: context.commandSourceLoader } : {}),
228
+ }), context);
229
+ return Object.freeze({ ...common, kind: "command", invocation });
230
+ }
231
+ const { qualified } = qualifyOwnedRef(target.ref, context);
232
+ if (target.kind === "command") {
233
+ if (document.target.with !== undefined) {
234
+ throw new UsageError("Task v3 command refs do not accept with; use akm/command with {ref, arguments} for portable arguments.", "INVALID_FLAG_VALUE");
235
+ }
236
+ const invocation = validatePreparedCommand(await (context.prepareCommand ?? prepareCommandInvocation)({
237
+ action: { ref: qualified },
238
+ config: context.config,
239
+ invocationKind: "task",
240
+ current: currentExecutionValues(document, context, commandEnvironment),
241
+ ...(context.commandSourceLoader ? { sourceLoader: context.commandSourceLoader } : {}),
242
+ }), context);
243
+ return Object.freeze({ ...common, kind: "command", invocation });
244
+ }
245
+ if (target.kind === "workflow") {
246
+ if (Object.keys(environment).length > 0) {
247
+ throw new UsageError("Task v3 workflow env cannot be consumed by the durable workflow runtime in 0.9.2; remove env or use a command target.", "INVALID_FLAG_VALUE");
248
+ }
249
+ const resolved = await resolvedOwnedAsset(qualified, "workflow", context);
250
+ validateWorkflowRuntimeSource(resolved.file, resolved.bundleRoot, context.readFile ?? ((targetPath) => fs.readFileSync(targetPath)));
251
+ return Object.freeze({
252
+ ...common,
253
+ kind: "workflow",
254
+ ref: qualified,
255
+ params: Object.freeze({ ...(document.target.with ?? {}) }),
256
+ ...(document.akm?.maxSteps !== undefined ? { maxSteps: document.akm.maxSteps } : {}),
257
+ ...(document.akm?.maxRetries !== undefined ? { maxRetries: document.akm.maxRetries } : {}),
258
+ });
259
+ }
260
+ if (document.target.with !== undefined) {
261
+ throw new UsageError("Task v3 script refs do not accept with.", "INVALID_FLAG_VALUE");
262
+ }
263
+ const resolved = await resolvedOwnedAsset(qualified, "script", context);
264
+ const file = resolved.file;
265
+ const extension = path.extname(file).toLowerCase();
266
+ const raw = (context.readFile ?? ((targetPath) => fs.readFileSync(targetPath)))(file, resolved.bundleRoot);
267
+ const bytes = Uint8Array.from(raw);
268
+ const cwdIdentity = captureDirectoryIdentity(resolved.bundleRoot);
269
+ return Object.freeze({
270
+ ...common,
271
+ kind: "script",
272
+ sourceRef: qualified,
273
+ interpreter: scriptInterpreter(extension, qualified),
274
+ extension,
275
+ bytesBase64: Buffer.from(bytes).toString("base64"),
276
+ byteLength: bytes.byteLength,
277
+ sha256: createHash("sha256").update(bytes).digest("hex"),
278
+ cwd: cwdIdentity.realCwd,
279
+ cwdIdentity,
280
+ });
281
+ }
@@ -0,0 +1,272 @@
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
+ * Secret-free input to every native scheduler backend.
6
+ *
7
+ * The binding deliberately contains only stable source identity, trigger
8
+ * identity, and the public CLI tail. Source content, action inputs,
9
+ * environment values, resolved requests, and credentials must never cross
10
+ * this boundary or be persisted by an OS scheduler.
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
14
+ import { UsageError } from "../core/errors.js";
15
+ import { normaliseTaskConceptId } from "./task-id.js";
16
+ export function compileTaskSchedulerBindings(input) {
17
+ const id = normaliseTaskConceptId(input.id);
18
+ const ref = assertQualifiedRef(input.qualifiedRef, "task");
19
+ const bundle = parseBundleRef(ref).bundle;
20
+ if (!bundle)
21
+ throw new Error("invariant: qualified scheduler task ref lost its bundle");
22
+ const invocation = Object.freeze(["task", "run", id, "--bundle", bundle, "--scheduled"]);
23
+ return Object.freeze(input.schedules.map((schedule) => {
24
+ const bindingId = schedule.ordinal === 0 ? id : digestBindingId("task", ref, schedule.ordinal);
25
+ return freezeBinding({
26
+ id: bindingId,
27
+ nativeId: schedulerNativeBindingId(bindingId),
28
+ logicalSource: { kind: "task", ref },
29
+ cron: schedule.cron,
30
+ source: schedule.source,
31
+ ordinal: schedule.ordinal,
32
+ enabled: input.enabled,
33
+ invocation,
34
+ });
35
+ }));
36
+ }
37
+ export function compileWorkflowSchedulerBindings(input) {
38
+ const ref = assertQualifiedRef(input.qualifiedRef, "workflow");
39
+ const invocation = Object.freeze(["workflow", "run", ref]);
40
+ return Object.freeze(input.schedules.map((schedule) => {
41
+ const bindingId = digestBindingId("workflow", ref, schedule.ordinal);
42
+ return freezeBinding({
43
+ id: bindingId,
44
+ nativeId: bindingId,
45
+ logicalSource: { kind: "workflow", ref },
46
+ cron: schedule.cron,
47
+ source: schedule.source,
48
+ ordinal: schedule.ordinal,
49
+ enabled: true,
50
+ invocation,
51
+ ...(input.executionEvidenceDigest !== undefined
52
+ ? { executionEvidenceDigest: assertSchedulerExecutionEvidenceDigest(input.executionEvidenceDigest) }
53
+ : {}),
54
+ });
55
+ }));
56
+ }
57
+ export function assertSchedulerExecutionEvidenceDigest(value) {
58
+ if (!/^[a-f0-9]{64}$/u.test(value)) {
59
+ throw new UsageError("Scheduler workflow execution evidence must be a lowercase SHA-256 digest.", "INVALID_FLAG_VALUE");
60
+ }
61
+ return value;
62
+ }
63
+ /**
64
+ * Map a logical binding id to the flat portable token persisted by native
65
+ * schedulers. Existing flat ids are byte-for-byte stable; only standalone
66
+ * component-relative ids need an encoded native spelling.
67
+ */
68
+ export function schedulerNativeBindingId(id) {
69
+ if (!id.includes("/"))
70
+ return id;
71
+ const digest = createHash("sha256")
72
+ .update(JSON.stringify(["nested-task", id]))
73
+ .digest("hex")
74
+ .slice(0, 32);
75
+ return `task-${digest}`;
76
+ }
77
+ /** Portable collision key for scheduler namespaces with Windows semantics. */
78
+ export function schedulerNativeArtifactKey(nativeId) {
79
+ return nativeId.toLowerCase().replace(/\.+$/u, "");
80
+ }
81
+ export function schedulerNativeArtifactsForKey(artifacts, nativeId) {
82
+ const key = schedulerNativeArtifactKey(nativeId);
83
+ return artifacts.filter((artifact) => schedulerNativeArtifactKey(artifact.nativeId) === key);
84
+ }
85
+ export function assertSchedulerNativeArtifactCardinality(artifacts, nativeId, expectedCount) {
86
+ const matches = schedulerNativeArtifactsForKey(artifacts, nativeId);
87
+ if (matches.length !== expectedCount) {
88
+ const expectedState = expectedCount === 0 ? "absence" : "one present owner";
89
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(nativeId)} changed: normalized cardinality ${matches.length} violates the expected ${expectedState} before mutation.`, "RESOURCE_ALREADY_EXISTS");
90
+ }
91
+ return matches[0];
92
+ }
93
+ /** Return a logical owner only when the public invocation proves the mapping. */
94
+ export function schedulerLogicalBindingOwner(nativeId, invocation) {
95
+ return schedulerNativeArtifactOwner(nativeId, invocation)?.logicalId;
96
+ }
97
+ export function schedulerNativeArtifactOwner(nativeId, invocation) {
98
+ if (invocation[0] === "workflow" && invocation[1] === "run") {
99
+ return { logicalId: nativeId, logicalKind: "workflow" };
100
+ }
101
+ const taskId = invocation[0] === "task" && invocation[1] === "run" ? invocation[2] : undefined;
102
+ const bundleIndex = invocation.indexOf("--bundle", 3);
103
+ if (!taskId || bundleIndex === -1 || !invocation[bundleIndex + 1])
104
+ return undefined;
105
+ return {
106
+ logicalId: schedulerNativeBindingId(taskId) === nativeId ? taskId : nativeId,
107
+ logicalKind: "task",
108
+ };
109
+ }
110
+ export function assertSchedulerNativeArtifactOwner(nativeId, intended, invocation) {
111
+ const intendedNativeId = intended.nativeId ?? schedulerNativeBindingId(intended.id);
112
+ if (nativeId === intendedNativeId && invocation && sameInvocation(invocation, intended.invocation))
113
+ return;
114
+ const description = invocation ? JSON.stringify(invocation) : "an unproven or malformed invocation";
115
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(nativeId)} belongs to ${description}, not the exact ${intended.logicalSource.kind} owner ${JSON.stringify(intended.logicalSource.ref)} with binding ${JSON.stringify(intended.id)}.`, "RESOURCE_ALREADY_EXISTS");
116
+ }
117
+ /** Recover binding identity without treating the native hash as source ownership. */
118
+ export function schedulerLogicalBindingId(nativeId, invocation) {
119
+ return schedulerLogicalBindingOwner(nativeId, invocation) ?? nativeId;
120
+ }
121
+ export function schedulerBindingNativeId(binding) {
122
+ return binding.nativeId ?? schedulerNativeBindingId(binding.id);
123
+ }
124
+ export function assertSchedulerRemovalArtifact(nativeId, expected, invocation, fingerprint) {
125
+ const normalized = normalizeRemovalExpectation(expected);
126
+ assertSchedulerExpectationIdentity(normalized);
127
+ const fingerprintMatches = normalized.fingerprint === undefined || normalized.fingerprint === fingerprint;
128
+ if (nativeId === normalized.nativeId &&
129
+ invocation !== undefined &&
130
+ sameInvocation(invocation, normalized.invocation) &&
131
+ fingerprintMatches) {
132
+ return;
133
+ }
134
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(nativeId)} changed owner or fingerprint; refusing to remove an unverified ${normalized.logicalSource.kind} owner ${JSON.stringify(normalized.logicalSource.ref)}.`, "RESOURCE_ALREADY_EXISTS");
135
+ }
136
+ export function assertSchedulerExpectationIdentity(expected, binding) {
137
+ const identity = canonicalSchedulerIdentity(expected.logicalSource, expected.ordinal, expected.invocation);
138
+ if (identity.bindingId !== expected.bindingId ||
139
+ identity.nativeId !== expected.nativeId ||
140
+ !sameInvocation(identity.invocation, expected.invocation) ||
141
+ (binding !== undefined &&
142
+ (binding.id !== expected.bindingId ||
143
+ schedulerBindingNativeId(binding) !== expected.nativeId ||
144
+ binding.ordinal !== expected.ordinal ||
145
+ binding.logicalSource.kind !== expected.logicalSource.kind ||
146
+ binding.logicalSource.ref !== expected.logicalSource.ref ||
147
+ !sameInvocation(binding.invocation, expected.invocation)))) {
148
+ throw new UsageError("Scheduler mutation expectation has a forged or inconsistent binding identity.", "INVALID_FLAG_VALUE");
149
+ }
150
+ if (expected.state === "absent" && expected.fingerprint !== undefined) {
151
+ throw new UsageError("An absent scheduler expectation cannot carry prior artifact state.", "INVALID_FLAG_VALUE");
152
+ }
153
+ }
154
+ export function assertSchedulerMutationArtifact(artifact, expected) {
155
+ assertSchedulerExpectationIdentity(expected);
156
+ if (expected.state === "absent") {
157
+ if (artifact === undefined)
158
+ return;
159
+ }
160
+ else if (artifact !== undefined &&
161
+ artifact.nativeId === expected.nativeId &&
162
+ artifact.bindingId === expected.bindingId &&
163
+ artifact.invocation !== undefined &&
164
+ sameInvocation(artifact.invocation, expected.invocation) &&
165
+ expected.fingerprint !== undefined &&
166
+ artifact.fingerprint === expected.fingerprint) {
167
+ return;
168
+ }
169
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(expected.nativeId)} changed from its frozen ${expected.state} state; refusing mutation.`, "RESOURCE_ALREADY_EXISTS");
170
+ }
171
+ export function assertSchedulerRollbackArtifact(artifact, expected) {
172
+ const matches = expected.allowed.some((state) => {
173
+ if (state.state === "absent")
174
+ return artifact === undefined;
175
+ if (!artifact || artifact.nativeId !== expected.nativeId)
176
+ return false;
177
+ if (artifact.fingerprint !== state.fingerprint || artifact.bindingId !== state.bindingId)
178
+ return false;
179
+ return state.invocation === undefined
180
+ ? artifact.invocation === undefined
181
+ : artifact.invocation !== undefined && sameInvocation(artifact.invocation, state.invocation);
182
+ });
183
+ if (matches)
184
+ return;
185
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(expected.nativeId)} changed after the transaction began; refusing rollback over a concurrent owner or fingerprint.`, "RESOURCE_ALREADY_EXISTS");
186
+ }
187
+ /**
188
+ * Validate a rollback target from one complete native inventory. Rollback
189
+ * guards may allow either the transaction-written state or the prior state,
190
+ * but neither state permits two portable-key-equivalent artifacts.
191
+ */
192
+ export function assertSchedulerRollbackArtifactCardinality(artifacts, expected) {
193
+ const matches = schedulerNativeArtifactsForKey(artifacts, expected.nativeId);
194
+ if (matches.length > 1) {
195
+ throw new UsageError(`Native scheduler artifact ${JSON.stringify(expected.nativeId)} changed after the transaction began: normalized rollback cardinality ${matches.length} exceeds one.`, "RESOURCE_ALREADY_EXISTS");
196
+ }
197
+ const artifact = matches[0];
198
+ assertSchedulerRollbackArtifact(artifact, expected);
199
+ return artifact;
200
+ }
201
+ export function canonicalSchedulerIdentity(logicalSource, ordinal, invocation) {
202
+ const parsed = parseBundleRef(logicalSource.ref);
203
+ if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== logicalSource.ref) {
204
+ throw new UsageError("Scheduler expectation requires one canonical fully-qualified source.", "INVALID_FLAG_VALUE");
205
+ }
206
+ if (!Number.isSafeInteger(ordinal) || ordinal < 0) {
207
+ throw new UsageError("Scheduler expectation ordinal must be a non-negative integer.", "INVALID_FLAG_VALUE");
208
+ }
209
+ if (logicalSource.kind === "workflow") {
210
+ const canonicalInvocation = ["workflow", "run", logicalSource.ref];
211
+ if (!sameInvocation(invocation, canonicalInvocation)) {
212
+ throw new UsageError("Workflow scheduler expectation invocation does not match its qualified source.", "INVALID_FLAG_VALUE");
213
+ }
214
+ const bindingId = digestBindingId("workflow", logicalSource.ref, ordinal);
215
+ return Object.freeze({ bindingId, nativeId: bindingId, invocation: Object.freeze(canonicalInvocation) });
216
+ }
217
+ const taskId = invocation[0] === "task" && invocation[1] === "run" ? invocation[2] : undefined;
218
+ if (!taskId || normaliseTaskConceptId(taskId) !== taskId) {
219
+ throw new UsageError("Task scheduler expectation has an invalid public task id.", "INVALID_FLAG_VALUE");
220
+ }
221
+ const canonicalInvocation = ["task", "run", taskId, "--bundle", parsed.bundle, "--scheduled"];
222
+ if (!sameInvocation(invocation, canonicalInvocation)) {
223
+ throw new UsageError("Task scheduler expectation invocation does not match its qualified source.", "INVALID_FLAG_VALUE");
224
+ }
225
+ if (parsed.conceptId !== taskId && parsed.conceptId !== `tasks/${taskId}`) {
226
+ throw new UsageError("Task scheduler expectation id does not match its qualified source.", "INVALID_FLAG_VALUE");
227
+ }
228
+ const bindingId = ordinal === 0 ? taskId : digestBindingId("task", logicalSource.ref, ordinal);
229
+ return Object.freeze({
230
+ bindingId,
231
+ nativeId: schedulerNativeBindingId(bindingId),
232
+ invocation: Object.freeze(canonicalInvocation),
233
+ });
234
+ }
235
+ /** Recover the authored schedule ordinal from the exact source/binding identity. */
236
+ export function schedulerBindingOrdinal(bindingId, logicalSource, invocation) {
237
+ if (logicalSource.kind === "task" && bindingId === invocation[2])
238
+ return 0;
239
+ for (let ordinal = 0; ordinal < 4096; ordinal += 1) {
240
+ if (digestBindingId(logicalSource.kind, logicalSource.ref, ordinal) === bindingId)
241
+ return ordinal;
242
+ }
243
+ return undefined;
244
+ }
245
+ function digestBindingId(kind, ref, ordinal) {
246
+ const prefix = kind === "workflow" ? "wf" : "task";
247
+ const digest = createHash("sha256")
248
+ .update(JSON.stringify([kind, ref, ordinal]))
249
+ .digest("hex")
250
+ .slice(0, 32);
251
+ return `${prefix}-${digest}`;
252
+ }
253
+ function assertQualifiedRef(value, kind) {
254
+ const parsed = parseBundleRef(value);
255
+ if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== value) {
256
+ throw new UsageError(`${kind} scheduler bindings require one canonical fully-qualified ref.`, "INVALID_FLAG_VALUE");
257
+ }
258
+ return value;
259
+ }
260
+ function freezeBinding(binding) {
261
+ return Object.freeze({
262
+ ...binding,
263
+ logicalSource: Object.freeze({ ...binding.logicalSource }),
264
+ invocation: Object.freeze([...binding.invocation]),
265
+ });
266
+ }
267
+ function sameInvocation(left, right) {
268
+ return left.length === right.length && left.every((value, index) => value === right[index]);
269
+ }
270
+ function normalizeRemovalExpectation(expected) {
271
+ return { ...expected, state: "present" };
272
+ }