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
@@ -37,10 +37,12 @@ import { ConfigError } from "../../core/errors.js";
37
37
  import { getTaskLogDir } from "../../core/paths.js";
38
38
  import { resolveAkmInvocation } from "../resolve-akm-bin.js";
39
39
  import { parseSchedule, translateToSchtasks } from "../schedule.js";
40
- import { buildScheduledTaskInvocation, parseScheduledTaskArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
40
+ import { assertSchedulerExecutionEvidenceDigest, assertSchedulerExpectationIdentity, assertSchedulerMutationArtifact, assertSchedulerNativeArtifactCardinality, assertSchedulerNativeArtifactOwner, assertSchedulerRemovalArtifact, assertSchedulerRollbackArtifactCardinality, schedulerBindingNativeId, schedulerLogicalBindingId, schedulerLogicalBindingOwner, schedulerNativeArtifactKey, schedulerNativeBindingId, } from "../scheduler-binding.js";
41
+ import { buildScheduledBindingInvocation, parseScheduledBindingArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
41
42
  import { escapeXml, nodeExec, nodeFs, normalizeXmlForUtf16File, runOrThrow, } from "./exec-utils.js";
42
43
  export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
43
44
  const SIGNATURE_PREFIX = "akm:v1:";
45
+ const SCHTASKS_SNAPSHOT = Symbol("akm-schtasks-binding-snapshot");
44
46
  export function SCHTASKS_BACKEND(options = {}) {
45
47
  const exec = options.exec ?? defaultSchtasksExec();
46
48
  const fsLike = options.fs ?? defaultSchtasksFs();
@@ -50,10 +52,13 @@ export function SCHTASKS_BACKEND(options = {}) {
50
52
  const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
51
53
  const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, process.env.PATH ?? ""));
52
54
  const userSid = options.userSid ?? resolveCurrentUserSid(exec);
53
- const taskName = (id) => `${folder}${id}`;
55
+ const taskName = (nativeId) => `${folder}${nativeId}`;
54
56
  return {
55
57
  name: "schtasks",
56
- install(task, opts) {
58
+ install(task, opts, expected) {
59
+ if (expected)
60
+ assertSchedulerExpectationIdentity(expected, task);
61
+ const nativeId = schedulerBindingNativeId(task);
57
62
  const xml = normalizeXmlForUtf16File(buildSchtasksXml(task, akmArgv, logDir, {
58
63
  folderPrefix: folder,
59
64
  contextPath: opts?.contextPath ?? defaultContextPath,
@@ -61,29 +66,55 @@ export function SCHTASKS_BACKEND(options = {}) {
61
66
  binding: [...(opts?.binding ?? akmArgv)],
62
67
  ...(opts?.target !== undefined ? { target: opts.target } : {}),
63
68
  }));
64
- const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(task.id), "/XML"], {
65
- isOk: (r) => r.status === 0 || isMissingTaskResult(r),
66
- message: (r) => `schtasks /Query failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
67
- });
69
+ const initialArtifact = expected
70
+ ? assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, expected.state === "absent" ? 0 : 1)
71
+ : undefined;
72
+ const existingNativeId = expected ? initialArtifact?.nativeId : nativeId;
73
+ const query = existingNativeId
74
+ ? runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(existingNativeId), "/XML"], {
75
+ isOk: (r) => r.status === 0 || isMissingTaskResult(r),
76
+ message: (r) => `schtasks /Query failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
77
+ })
78
+ : { status: 1, stdout: "", stderr: "missing" };
79
+ if (expected) {
80
+ assertSchedulerMutationArtifact(query.status === 0 && existingNativeId ? schtasksArtifact(existingNativeId, query.stdout) : undefined, expected);
81
+ }
68
82
  let previous;
69
83
  if (query.status === 0) {
84
+ if (!expected) {
85
+ assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(query.stdout)?.invocation);
86
+ }
70
87
  const enabled = taskXmlEnabled(query.stdout);
71
88
  if (enabled === undefined) {
72
- throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(task.id)}"; refusing to replace it.`, "INVALID_CONFIG_FILE");
89
+ throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(nativeId)}"; refusing to replace it.`, "INVALID_CONFIG_FILE");
73
90
  }
74
91
  previous = { xml: normalizeXmlForUtf16File(query.stdout), enabled };
75
92
  }
76
93
  fsLike.ensureDir(logDir);
77
- const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${task.id}-${Date.now()}.xml`);
94
+ const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${nativeId}-${Date.now()}.xml`);
78
95
  fsLike.writeFile(tmpFile, xml);
79
96
  try {
97
+ if (expected) {
98
+ const finalArtifact = assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, expected.state === "absent" ? 0 : 1);
99
+ const finalNativeId = finalArtifact?.nativeId;
100
+ const current = finalNativeId
101
+ ? runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(finalNativeId), "/XML"], {
102
+ isOk: (result) => result.status === 0 || isMissingTaskResult(result),
103
+ message: (result) => `schtasks /Query failed during final install CAS (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
104
+ })
105
+ : undefined;
106
+ assertSchedulerMutationArtifact(current?.status === 0 && finalNativeId ? schtasksArtifact(finalNativeId, current.stdout) : undefined, expected);
107
+ }
108
+ else {
109
+ assertSchtasksArtifactUnchanged(exec, taskName(nativeId), query, nativeId, task);
110
+ }
80
111
  try {
81
112
  // /F forces overwrite if a task with the same name exists.
82
- runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(task.id), "/XML", tmpFile, "/F"], {
113
+ runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
83
114
  message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
84
115
  });
85
116
  if (!task.enabled) {
86
- runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(task.id), "/DISABLE"], {
117
+ runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
87
118
  message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
88
119
  });
89
120
  }
@@ -92,7 +123,7 @@ export function SCHTASKS_BACKEND(options = {}) {
92
123
  const rollbackErrors = [];
93
124
  if (previous === undefined) {
94
125
  try {
95
- const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(task.id), "/F"]);
126
+ const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"]);
96
127
  if (remove.status !== 0 && !isMissingTaskResult(remove)) {
97
128
  rollbackErrors.push(new ConfigError(`schtasks /Delete during rollback failed: ${remove.stderr || remove.stdout || "no output"}.`, "INVALID_CONFIG_FILE"));
98
129
  }
@@ -104,11 +135,11 @@ export function SCHTASKS_BACKEND(options = {}) {
104
135
  else {
105
136
  try {
106
137
  fsLike.writeFile(tmpFile, previous.xml);
107
- runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(task.id), "/XML", tmpFile, "/F"], {
138
+ runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
108
139
  message: (r) => `schtasks /Create during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
109
140
  });
110
141
  const stateFlag = previous.enabled ? "/ENABLE" : "/DISABLE";
111
- runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(task.id), stateFlag], {
142
+ runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), stateFlag], {
112
143
  message: (r) => `schtasks /Change ${stateFlag} during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
113
144
  });
114
145
  }
@@ -127,78 +158,48 @@ export function SCHTASKS_BACKEND(options = {}) {
127
158
  fsLike.removeFile(tmpFile);
128
159
  }
129
160
  },
130
- uninstall(id) {
131
- runOrThrow(exec, ["schtasks", "/Delete", "/TN", taskName(id), "/F"], {
161
+ uninstall(nativeId, expected) {
162
+ if (expected) {
163
+ assertSchedulerExpectationIdentity({ ...expected, state: "present" });
164
+ const artifact = assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, 1);
165
+ const currentNativeId = artifact?.nativeId ?? nativeId;
166
+ const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(currentNativeId), "/XML"], {
167
+ isOk: (result) => result.status === 0 || isMissingTaskResult(result),
168
+ message: (result) => `schtasks /Query failed during final removal ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
169
+ });
170
+ if (current.status !== 0) {
171
+ throw new ConfigError(`Task Scheduler artifact ${JSON.stringify(nativeId)} disappeared after coherent inspection.`, "INVALID_CONFIG_FILE");
172
+ }
173
+ assertSchedulerRemovalArtifact(currentNativeId, expected, extractSchtasksInvocation(current.stdout)?.invocation, installedSignature(current.stdout));
174
+ }
175
+ runOrThrow(exec, ["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"], {
132
176
  isOk: (r) => r.status === 0 || /cannot find/i.test(r.stderr ?? ""),
133
177
  message: (r) => `schtasks /Delete failed: ${r.stderr || r.stdout || "no output"}.`,
134
178
  });
135
179
  },
136
- setEnabled(id, enabled) {
180
+ setEnabled(nativeId, enabled) {
137
181
  const flag = enabled ? "/ENABLE" : "/DISABLE";
138
- runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(id), flag], {
182
+ runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), flag], {
139
183
  message: (r) => `schtasks /Change ${flag} failed: ${r.stderr || r.stdout || "no output"}.`,
140
184
  });
141
185
  },
142
186
  list() {
143
- const r = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
144
- message: (res) => `schtasks /Query failed (exit ${res.status}): ${res.stderr || res.stdout || "no output"}.`,
145
- });
146
- const ids = [];
147
- for (const line of (r.stdout ?? "").split(/\r?\n/)) {
148
- const m = line.match(/^"([^"]+)",/);
149
- if (!m)
150
- continue;
151
- const name = m[1];
152
- if (name.startsWith(folder)) {
153
- ids.push(name.slice(folder.length));
154
- }
155
- }
156
- const refs = [];
157
- for (const id of ids) {
158
- const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
159
- message: (r) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
160
- });
161
- const signature = installedSignature(query.stdout);
162
- const installed = extractSchtasksInvocation(query.stdout);
163
- // An invocation that no longer parses (e.g. a pre-0.9 `tasks run`
164
- // entry surviving the scheduler-ABI respelling) is an orphan of its
165
- // marker id, not a hard failure: omit it so `akmTasksSync` treats the
166
- // id as "not present" and reinstalls it from the current task file.
167
- if (!installed)
168
- continue;
169
- refs.push({
170
- id,
171
- ...(signature !== undefined ? { signature } : {}),
172
- ...(installed.target !== undefined ? { target: installed.target } : {}),
173
- binding: installed.binding,
174
- contextPath: installed.contextPath,
175
- });
176
- }
177
- return refs;
187
+ return [...inspectSchtasksState(exec, folder, taskName).installed];
178
188
  },
179
189
  listForRebind() {
180
- const r = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
181
- message: (res) => `schtasks /Query failed (exit ${res.status}): ${res.stderr || res.stdout || "no output"}.`,
182
- });
183
- const refs = [];
184
- for (const line of (r.stdout ?? "").split(/\r?\n/)) {
185
- const match = line.match(/^"([^"]+)",/);
186
- const name = match?.[1];
187
- if (!name?.startsWith(folder))
188
- continue;
189
- const id = name.slice(folder.length);
190
- const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
191
- message: (result) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
192
- });
193
- const signature = installedSignature(query.stdout);
194
- const installed = extractSchtasksInvocation(query.stdout);
195
- refs.push({
196
- id,
197
- ...(signature !== undefined ? { signature } : {}),
198
- ...(installed?.target !== undefined ? { target: installed.target } : {}),
199
- });
200
- }
201
- return refs;
190
+ return listSchtasksForRebind(exec, folder, taskName);
191
+ },
192
+ listNativeArtifacts() {
193
+ return [...inspectSchtasksState(exec, folder, taskName).artifacts];
194
+ },
195
+ inspectBindings() {
196
+ return inspectSchtasksState(exec, folder, taskName);
197
+ },
198
+ snapshotBindings(ids) {
199
+ return snapshotSchtasksBindings(ids, exec, folder, taskName);
200
+ },
201
+ restoreBindings(snapshot, expectedCurrent) {
202
+ restoreSchtasksBindings(snapshot, { exec, fsLike, folder, taskName }, expectedCurrent);
202
203
  },
203
204
  expectedSignature(task, opts) {
204
205
  const signature = taskXmlSignature(buildSchtasksXml(task, akmArgv, logDir, {
@@ -214,6 +215,181 @@ export function SCHTASKS_BACKEND(options = {}) {
214
215
  },
215
216
  };
216
217
  }
218
+ function listSchtasksForRebind(exec, folder, taskName) {
219
+ const result = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
220
+ message: (query) => `schtasks /Query failed (exit ${query.status}): ${query.stderr || query.stdout || "no output"}.`,
221
+ });
222
+ const refs = [];
223
+ for (const line of (result.stdout ?? "").split(/\r?\n/)) {
224
+ const name = line.match(/^"([^"]+)",/)?.[1];
225
+ if (!name?.startsWith(folder))
226
+ continue;
227
+ const id = name.slice(folder.length);
228
+ const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
229
+ message: (current) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${current.status}): ${current.stderr || current.stdout || "no output"}.`,
230
+ });
231
+ const signature = installedSignature(query.stdout);
232
+ const installed = extractSchtasksInvocation(query.stdout);
233
+ const ref = {
234
+ id: installed ? schedulerLogicalBindingId(id, installed.invocation) : id,
235
+ ...(signature !== undefined ? { signature } : {}),
236
+ ...(installed?.target !== undefined ? { target: installed.target } : {}),
237
+ };
238
+ Object.defineProperty(ref, "nativeId", { value: id });
239
+ if (installed)
240
+ Object.defineProperty(ref, "invocation", { value: Object.freeze([...installed.invocation]) });
241
+ refs.push(ref);
242
+ }
243
+ return refs;
244
+ }
245
+ function inspectSchtasksState(exec, folder, taskName) {
246
+ const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
247
+ message: (result) => `schtasks /Query failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
248
+ });
249
+ const installed = [];
250
+ const artifacts = [];
251
+ for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
252
+ const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(nativeId), "/XML"], {
253
+ message: (result) => `schtasks /Query /XML for "${taskName(nativeId)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
254
+ });
255
+ const artifact = schtasksArtifact(nativeId, query.stdout);
256
+ artifacts.push(artifact);
257
+ const parsed = extractSchtasksInvocation(query.stdout);
258
+ if (!parsed)
259
+ continue;
260
+ const ref = {
261
+ id: schedulerLogicalBindingId(nativeId, parsed.invocation),
262
+ ...(artifact.fingerprint !== undefined ? { signature: artifact.fingerprint } : {}),
263
+ ...(parsed.target !== undefined ? { target: parsed.target } : {}),
264
+ binding: parsed.binding,
265
+ contextPath: parsed.contextPath,
266
+ };
267
+ Object.defineProperty(ref, "nativeId", { value: nativeId });
268
+ Object.defineProperty(ref, "invocation", { value: Object.freeze([...parsed.invocation]) });
269
+ installed.push(ref);
270
+ }
271
+ return Object.freeze({ installed: Object.freeze(installed), artifacts: Object.freeze(artifacts) });
272
+ }
273
+ function schtasksNativeIds(stdout, folder) {
274
+ const ids = [];
275
+ for (const line of (stdout ?? "").split(/\r?\n/)) {
276
+ const match = line.match(/^"([^"]+)",/);
277
+ const name = match?.[1];
278
+ if (name?.startsWith(folder))
279
+ ids.push(name.slice(folder.length));
280
+ }
281
+ return ids;
282
+ }
283
+ function schtasksArtifact(nativeId, xml) {
284
+ const parsed = extractSchtasksInvocation(xml);
285
+ const owner = parsed ? schedulerLogicalBindingOwner(nativeId, parsed.invocation) : undefined;
286
+ const artifact = parsed
287
+ ? {
288
+ nativeId,
289
+ ...(owner !== undefined ? { bindingId: owner } : {}),
290
+ invocation: Object.freeze([...parsed.invocation]),
291
+ }
292
+ : { nativeId };
293
+ const fingerprint = installedSignature(xml);
294
+ if (fingerprint !== undefined)
295
+ Object.defineProperty(artifact, "fingerprint", { value: fingerprint });
296
+ return artifact;
297
+ }
298
+ function snapshotSchtasksBindings(ids, exec, folder, taskName) {
299
+ const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
300
+ message: (result) => `schtasks /Query failed while snapshotting bindings (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
301
+ });
302
+ const requestedKeys = new Set(ids.map(schedulerNativeArtifactKey));
303
+ const enumeratedIds = new Set(ids);
304
+ for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
305
+ if (requestedKeys.has(schedulerNativeArtifactKey(nativeId)))
306
+ enumeratedIds.add(nativeId);
307
+ }
308
+ const entries = [...enumeratedIds].map((id) => {
309
+ const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
310
+ isOk: (result) => result.status === 0 || isMissingTaskResult(result),
311
+ message: (result) => `schtasks /Query failed while snapshotting "${taskName(id)}": ${result.stderr || result.stdout || "no output"}.`,
312
+ });
313
+ if (query.status !== 0)
314
+ return Object.freeze({ id });
315
+ const enabled = taskXmlEnabled(query.stdout);
316
+ if (enabled === undefined) {
317
+ throw new ConfigError(`schtasks /Query returned an unreadable definition while snapshotting "${taskName(id)}".`, "INVALID_CONFIG_FILE");
318
+ }
319
+ return Object.freeze({ id, xml: normalizeXmlForUtf16File(query.stdout), enabled });
320
+ });
321
+ const artifacts = entries.flatMap((entry) => entry.xml === undefined ? [] : [schtasksArtifact(entry.id, entry.xml)]);
322
+ return Object.freeze({
323
+ kind: SCHTASKS_SNAPSHOT,
324
+ nativeIds: Object.freeze([...ids]),
325
+ artifacts: Object.freeze(artifacts),
326
+ entries: Object.freeze(entries),
327
+ });
328
+ }
329
+ function restoreSchtasksBindings(snapshot, context, expectedCurrent) {
330
+ if (!isSchtasksBindingSnapshot(snapshot)) {
331
+ throw new ConfigError("Invalid Task Scheduler snapshot.", "INVALID_CONFIG_FILE");
332
+ }
333
+ const errors = [];
334
+ let rollbackInventory;
335
+ if (expectedCurrent) {
336
+ try {
337
+ rollbackInventory = inspectSchtasksState(context.exec, context.folder, context.taskName);
338
+ }
339
+ catch (error) {
340
+ errors.push(error);
341
+ }
342
+ }
343
+ for (const entry of snapshot.entries) {
344
+ try {
345
+ if (expectedCurrent) {
346
+ if (!rollbackInventory)
347
+ continue;
348
+ const expected = expectedCurrent.find((candidate) => candidate.nativeId === entry.id);
349
+ if (!expected) {
350
+ throw new ConfigError(`Missing Task Scheduler rollback expectation for ${JSON.stringify(entry.id)}.`, "INVALID_CONFIG_FILE");
351
+ }
352
+ assertSchedulerRollbackArtifactCardinality(rollbackInventory.artifacts, expected);
353
+ }
354
+ restoreSchtasksEntry(entry, context);
355
+ }
356
+ catch (error) {
357
+ errors.push(error);
358
+ }
359
+ }
360
+ if (errors.length > 0) {
361
+ throw new AggregateError(errors, `Failed to completely restore ${errors.length} Task Scheduler operation(s).`);
362
+ }
363
+ }
364
+ function restoreSchtasksEntry(entry, context) {
365
+ if (entry.xml === undefined) {
366
+ runOrThrow(context.exec, ["schtasks", "/Delete", "/TN", context.taskName(entry.id), "/F"], {
367
+ isOk: (result) => result.status === 0 || isMissingTaskResult(result),
368
+ message: (result) => `schtasks /Delete failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
369
+ });
370
+ return;
371
+ }
372
+ const tmpFile = path.join(context.fsLike.tmpdir(), `akm-task-restore-${schedulerNativeBindingId(entry.id)}-${Date.now()}.xml`);
373
+ context.fsLike.writeFile(tmpFile, entry.xml);
374
+ try {
375
+ runOrThrow(context.exec, ["schtasks", "/Create", "/TN", context.taskName(entry.id), "/XML", tmpFile, "/F"], {
376
+ message: (result) => `schtasks /Create failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
377
+ });
378
+ const stateFlag = entry.enabled === false ? "/DISABLE" : "/ENABLE";
379
+ runOrThrow(context.exec, ["schtasks", "/Change", "/TN", context.taskName(entry.id), stateFlag], {
380
+ message: (result) => `schtasks /Change ${stateFlag} failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
381
+ });
382
+ }
383
+ finally {
384
+ context.fsLike.removeFile(tmpFile);
385
+ }
386
+ }
387
+ function isSchtasksBindingSnapshot(value) {
388
+ return (typeof value === "object" &&
389
+ value !== null &&
390
+ value.kind === SCHTASKS_SNAPSHOT &&
391
+ Array.isArray(value.entries));
392
+ }
217
393
  /**
218
394
  * Recover the bundle name embedded as a `--bundle <bundle>` pair in the
219
395
  * PowerShell `<Arguments>` of an installed Task Scheduler definition. Returns
@@ -230,7 +406,7 @@ export function extractSchtasksInvocation(xml) {
230
406
  const invocationStart = findPowerShellInvocationOperator(commandLine);
231
407
  if (invocationStart === undefined)
232
408
  return undefined;
233
- return parseScheduledTaskArgv(parsePowerShellSingleQuotedArgs(commandLine, invocationStart + 1));
409
+ return parseScheduledBindingArgv(parsePowerShellSingleQuotedArgs(commandLine, invocationStart + 1));
234
410
  }
235
411
  function findPowerShellInvocationOperator(script) {
236
412
  let inSingleQuote = false;
@@ -285,13 +461,32 @@ function parsePowerShellSingleQuotedArgs(script, start) {
285
461
  }
286
462
  return argv;
287
463
  }
464
+ /** Close the read/prepare-to-/Create ownership race at the native boundary. */
465
+ function assertSchtasksArtifactUnchanged(exec, nativeTaskName, previous, nativeId, task) {
466
+ // This runs outside install's rollback region: no scheduler mutation has
467
+ // occurred, so a raced owner must remain untouched rather than be restored
468
+ // from the stale first read.
469
+ const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", nativeTaskName, "/XML"], {
470
+ isOk: (result) => result.status === 0 || isMissingTaskResult(result),
471
+ message: (result) => `schtasks /Query failed during final ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
472
+ });
473
+ const changed = current.status !== previous.status ||
474
+ (current.status === 0 && normalizeXmlForUtf16File(current.stdout) !== normalizeXmlForUtf16File(previous.stdout));
475
+ if (changed) {
476
+ throw new ConfigError(`schtasks task "${nativeTaskName}" changed while it was being prepared; refusing to replace an unverified owner.`, "INVALID_CONFIG_FILE");
477
+ }
478
+ if (current.status === 0) {
479
+ assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(current.stdout)?.invocation);
480
+ }
481
+ }
288
482
  export function buildSchtasksXml(task, akmArgv, logDir, options) {
289
483
  const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
290
484
  const now = options.now ? options.now() : new Date();
291
485
  const definition = buildSchtasksDefinition(task, options.binding ?? akmArgv, logDir, folder, options.contextPath, options.userSid, options.target);
292
486
  const triggerXml = renderSchtasksTrigger(definition.trigger, now);
487
+ const nativeId = schedulerBindingNativeId(task);
293
488
  return schtasksTemplate
294
- .replaceAll("{{TASK_ID}}", escapeXml(task.id))
489
+ .replaceAll("{{TASK_ID}}", escapeXml(nativeId))
295
490
  .replaceAll("{{FOLDER}}", escapeXml(folder))
296
491
  .replace("{{SIGNATURE}}", definition.signature)
297
492
  .replace("{{TRIGGER_XML}}", triggerXml)
@@ -301,19 +496,23 @@ export function buildSchtasksXml(task, akmArgv, logDir, options) {
301
496
  .replace("{{ARGS}}", escapeXml(definition.args))
302
497
  .replace("{{LOG_PATH}}", escapeXml(definition.logPath));
303
498
  }
304
- function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid, target) {
305
- const spec = parseSchedule(task.schedule, "schtasks");
499
+ function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid, _target) {
500
+ const spec = parseSchedule(task.cron, "schtasks");
306
501
  const trigger = translateToSchtasks(spec);
307
- const invocation = buildScheduledTaskInvocation(akmArgv, task.id, contextPath, target);
502
+ const invocation = buildScheduledBindingInvocation(akmArgv, contextPath, task.invocation);
308
503
  const invoke = `& ${invocation.argv.map((arg) => quotePowerShell(arg)).join(" ")}`;
309
- const script = `${invoke}; exit $LASTEXITCODE`;
504
+ const executionEvidence = task.executionEvidenceDigest === undefined
505
+ ? ""
506
+ : `$null=${quotePowerShell(`akm-workflow-evidence:${assertSchedulerExecutionEvidenceDigest(task.executionEvidenceDigest)}`)}; `;
507
+ const script = `${executionEvidence}${invoke}; exit $LASTEXITCODE`;
310
508
  const command = "powershell.exe";
311
509
  const args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script].map(quoteArg).join(" ");
312
- const logPath = path.join(logDir, `${task.id}.log`);
510
+ const nativeId = schedulerBindingNativeId(task);
511
+ const logPath = path.join(logDir, `${nativeId}.log`);
313
512
  // The boundary changes on reinstall, and enabled state can change via /Change.
314
513
  // Keep both outside the stored definition fingerprint so no-op sync stays stable.
315
514
  const fingerprint = createHash("sha256")
316
- .update(JSON.stringify({ folder, id: task.id, trigger, command, args, logPath, userSid }))
515
+ .update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid }))
317
516
  .digest("hex");
318
517
  return { trigger, command, args, logPath, signature: `${SIGNATURE_PREFIX}${fingerprint}` };
319
518
  }
@@ -22,7 +22,7 @@
22
22
  import fs from "node:fs";
23
23
  import path from "node:path";
24
24
  import { getDirname } from "../runtime.js";
25
- import { parseTaskDocument } from "./parser.js";
25
+ import { parseTaskV3Yaml } from "./source-v3.js";
26
26
  /** Directory holding the bundled task template categories. */
27
27
  const TASKS_ASSETS_DIR = path.join(getDirname(import.meta.url), "../assets/tasks");
28
28
  /**
@@ -67,20 +67,20 @@ export function listEmbeddedTasks() {
67
67
  }
68
68
  let task;
69
69
  try {
70
- task = parseTaskDocument({ yaml, filePath, id });
70
+ task = parseTaskV3Yaml({ yaml, filePath, workspaceRoot: TASKS_ASSETS_DIR });
71
71
  }
72
72
  catch {
73
73
  continue;
74
74
  }
75
- if (task.target.kind !== "command")
75
+ if (task.target.kind !== "run" || task.akm?.schedule === undefined)
76
76
  continue;
77
77
  tasks.push({
78
78
  id,
79
79
  label: `${category}/${id}`,
80
- command: task.target.cmd.join(" "),
81
- schedule: task.schedule,
82
- description: task.description ?? "",
83
- enabled: task.enabled,
80
+ command: task.target.run,
81
+ schedule: task.akm.schedule,
82
+ description: task.akm.description ?? "",
83
+ enabled: task.akm.enabled !== false,
84
84
  yaml,
85
85
  });
86
86
  }
@@ -0,0 +1,50 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { createHash } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { assertNever } from "../core/assert.js";
9
+ import { STANDALONE_FROZEN_SCRIPT_ARG } from "./standalone-script-entry.js";
10
+ export function materializeFrozenScript(script) {
11
+ const bytes = Buffer.from(script.bytesBase64, "base64");
12
+ const digest = createHash("sha256").update(bytes).digest("hex");
13
+ if (bytes.byteLength !== script.byteLength || digest !== script.sha256) {
14
+ throw new Error(`Frozen script snapshot ${script.sourceRef} failed its byte/hash integrity check.`);
15
+ }
16
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), "akm-task-script-"));
17
+ const file = path.join(directory, `snapshot${script.extension}`);
18
+ fs.writeFileSync(file, bytes, { mode: 0o700 });
19
+ return Object.freeze({ directory, file });
20
+ }
21
+ export function frozenScriptCommand(script, materializedPath) {
22
+ switch (script.interpreter) {
23
+ case "bun":
24
+ return [process.execPath, materializedPath];
25
+ case "bun-standalone":
26
+ return [process.execPath, STANDALONE_FROZEN_SCRIPT_ARG, materializedPath];
27
+ case "powershell":
28
+ return ["powershell", "-NoProfile", "-NonInteractive", "-File", materializedPath];
29
+ case "cmd":
30
+ return ["cmd", "/d", "/s", "/c", materializedPath];
31
+ case "go":
32
+ return ["go", "run", materializedPath];
33
+ case "kotlin":
34
+ return script.extension === ".kts" ? ["kotlinc", "-script", materializedPath] : ["kotlin", materializedPath];
35
+ case "sh":
36
+ case "python":
37
+ case "ruby":
38
+ case "perl":
39
+ case "php":
40
+ case "lua":
41
+ case "rscript":
42
+ case "swift":
43
+ return [script.interpreter, materializedPath];
44
+ default:
45
+ return assertNever(script.interpreter, "frozenScriptCommand");
46
+ }
47
+ }
48
+ export function cleanupFrozenScript(materialized) {
49
+ fs.rmSync(materialized.directory, { recursive: true, force: true });
50
+ }
@@ -24,12 +24,16 @@ import path from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
25
  import { ConfigError } from "../core/errors.js";
26
26
  import { mainPath as runtimeMainPath } from "../runtime.js";
27
+ /** True only for Bun's virtual main path inside a compiled standalone executable. */
28
+ export function isBunStandaloneMain(mainPath = runtimeMainPath) {
29
+ return (mainPath?.startsWith("/$bunfs/") === true || (mainPath !== undefined && /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(mainPath)));
30
+ }
27
31
  export function resolveAkmInvocation(options = {}) {
28
32
  const env = options.env ?? process.env;
29
33
  const runtime = options.runtime ?? (process.versions.bun ? "bun" : "node");
30
34
  const execPath = options.execPath ?? process.execPath;
31
35
  const mainPath = options.mainPath ?? runtimeMainPath;
32
- const isStandaloneMain = mainPath?.startsWith("/$bunfs/") || (mainPath !== undefined && /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(mainPath));
36
+ const isStandaloneMain = isBunStandaloneMain(mainPath);
33
37
  if (runtime === "bun" && isStandaloneMain && execPath) {
34
38
  return { argv: [absoluteInvocationPath(execPath)], via: "standalone", kind: "standalone", eligible: true };
35
39
  }