akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -6,25 +6,28 @@
6
6
  *
7
7
  * Each task is registered under the `\akm\` Task Scheduler folder so the
8
8
  * backend never touches user-managed tasks. The full task definition is
9
- * sent through `schtasks /Create /TN \akm\<id> /XML <path>` so we can
9
+ * sent through `schtasks /Create /TN \akm\<id> /XML <path> /F` so we can
10
10
  * express triggers/principals/actions without quoting hell.
11
11
  *
12
12
  * Platform notes:
13
13
  * • `LogonType=InteractiveToken` means the task runs in the context of
14
14
  * the registering user only when they are logged in — there is no
15
15
  * stored password and the task will not fire at the lock screen.
16
- * • `<Principal>` records the current user SID so sync can detect identity
17
- * drift instead of silently accepting a task registered to another user.
16
+ * • `<Principal>` records the current user SID; it is part of the
17
+ * fingerprint in `<Source>`, so a sync run by another user re-registers
18
+ * the task under that user.
18
19
  * • `<DisallowStartIfOnBatteries>false</…>` and `<StopIfGoingOnBatteries>
19
- * false</…>` allow the task to run on battery — utility tasks would
20
- * otherwise be silently skipped on laptops.
21
- * • `MultipleInstancesPolicy=IgnoreNew` makes overlapping triggers safe:
22
- * while a task is still running, a new fire is dropped rather than
23
- * queued or run in parallel.
20
+ * false</…>` allow the task to run on battery.
21
+ * • `MultipleInstancesPolicy=IgnoreNew` makes overlapping triggers safe.
24
22
  * • `/Query /FO CSV /NH` (without `/V`) outputs three columns:
25
23
  * `TaskName,Next Run Time,Status` — so the regex anchors on the task
26
- * name as the leading quoted field. Adding `/V` would shift HostName
27
- * into column 0; we deliberately don't.
24
+ * name as the leading quoted field.
25
+ * • Task Scheduler runs a task with the account's own environment, so
26
+ * PATH is not carried anywhere. The Exec action has no environment of
27
+ * its own: the AKM directory environment a row sets (see
28
+ * `src/tasks/scheduler-invocation.ts`) is a `$env:NAME='value';`
29
+ * assignment ahead of the call operator in the PowerShell command,
30
+ * inherited by the akm process it starts.
28
31
  *
29
32
  * Tests inject a fake exec + filesystem.
30
33
  */
@@ -37,143 +40,54 @@ import { ConfigError } from "../../core/errors.js";
37
40
  import { getTaskLogDir } from "../../core/paths.js";
38
41
  import { resolveAkmInvocation } from "../resolve-akm-bin.js";
39
42
  import { parseSchedule, translateToSchtasks } from "../schedule.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";
43
+ import { schedulerBindingNativeId, schedulerLogicalBindingId } from "../scheduler-binding.js";
44
+ import { buildScheduledInvocation, parseScheduledInvocationArgv, scheduledRowEnvironmentEntries, scheduledRowEnvironmentFrom, } from "../scheduler-invocation.js";
42
45
  import { escapeXml, nodeExec, nodeFs, normalizeXmlForUtf16File, runOrThrow, } from "./exec-utils.js";
43
46
  export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
44
47
  const SIGNATURE_PREFIX = "akm:v1:";
45
- const SCHTASKS_SNAPSHOT = Symbol("akm-schtasks-binding-snapshot");
46
48
  export function SCHTASKS_BACKEND(options = {}) {
47
- const exec = options.exec ?? defaultSchtasksExec();
49
+ const exec = options.exec ?? nodeExec();
48
50
  const fsLike = options.fs ?? defaultSchtasksFs();
49
51
  const akmArgv = options.akmArgv ?? resolveAkmInvocation().argv;
50
52
  const logDir = options.logDir ?? getTaskLogDir();
51
53
  const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
52
- const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
53
- const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, process.env.PATH ?? ""));
54
54
  const userSid = options.userSid ?? resolveCurrentUserSid(exec);
55
55
  const taskName = (nativeId) => `${folder}${nativeId}`;
56
+ const xmlFor = (task, opts) => buildSchtasksXml(task, akmArgv, logDir, {
57
+ folderPrefix: folder,
58
+ userSid,
59
+ binding: [...(opts?.binding ?? akmArgv)],
60
+ ...(opts?.environment ? { environment: opts.environment } : {}),
61
+ });
62
+ const queryXml = (nativeId) => runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(nativeId), "/XML"], {
63
+ message: (result) => `schtasks /Query /XML for "${taskName(nativeId)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
64
+ }).stdout;
56
65
  return {
57
66
  name: "schtasks",
58
- install(task, opts, expected) {
59
- if (expected)
60
- assertSchedulerExpectationIdentity(expected, task);
67
+ install(task, opts) {
68
+ const xml = normalizeXmlForUtf16File(xmlFor(task, opts));
61
69
  const nativeId = schedulerBindingNativeId(task);
62
- const xml = normalizeXmlForUtf16File(buildSchtasksXml(task, akmArgv, logDir, {
63
- folderPrefix: folder,
64
- contextPath: opts?.contextPath ?? defaultContextPath,
65
- userSid,
66
- binding: [...(opts?.binding ?? akmArgv)],
67
- ...(opts?.target !== undefined ? { target: opts.target } : {}),
68
- }));
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
- }
82
- let previous;
83
- if (query.status === 0) {
84
- if (!expected) {
85
- assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(query.stdout)?.invocation);
86
- }
87
- const enabled = taskXmlEnabled(query.stdout);
88
- if (enabled === undefined) {
89
- throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(nativeId)}"; refusing to replace it.`, "INVALID_CONFIG_FILE");
90
- }
91
- previous = { xml: normalizeXmlForUtf16File(query.stdout), enabled };
92
- }
93
70
  fsLike.ensureDir(logDir);
94
71
  const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${nativeId}-${Date.now()}.xml`);
95
72
  fsLike.writeFile(tmpFile, xml);
96
73
  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), nativeId, task);
110
- }
111
- try {
112
- // /F forces overwrite if a task with the same name exists.
113
- runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
114
- message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
74
+ // /F forces overwrite if a task with the same name exists.
75
+ runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
76
+ message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
77
+ });
78
+ if (!task.enabled) {
79
+ runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
80
+ message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
115
81
  });
116
- if (!task.enabled) {
117
- runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
118
- message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
119
- });
120
- }
121
- }
122
- catch (err) {
123
- const rollbackErrors = [];
124
- if (previous === undefined) {
125
- try {
126
- const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"]);
127
- if (remove.status !== 0 && !isMissingTaskResult(remove)) {
128
- rollbackErrors.push(new ConfigError(`schtasks /Delete during rollback failed: ${remove.stderr || remove.stdout || "no output"}.`, "INVALID_CONFIG_FILE"));
129
- }
130
- }
131
- catch (rollbackError) {
132
- rollbackErrors.push(rollbackError);
133
- }
134
- }
135
- else {
136
- try {
137
- fsLike.writeFile(tmpFile, previous.xml);
138
- runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
139
- message: (r) => `schtasks /Create during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
140
- });
141
- const stateFlag = previous.enabled ? "/ENABLE" : "/DISABLE";
142
- runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), stateFlag], {
143
- message: (r) => `schtasks /Change ${stateFlag} during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
144
- });
145
- }
146
- catch (rollbackError) {
147
- rollbackErrors.push(rollbackError);
148
- }
149
- }
150
- if (rollbackErrors.length > 0) {
151
- const message = err instanceof Error ? err.message : String(err);
152
- throw new AggregateError([err, ...rollbackErrors], `${message}; rollback for Task Scheduler task "${task.id}" was incomplete.`);
153
- }
154
- throw err;
155
82
  }
156
83
  }
157
84
  finally {
158
85
  fsLike.removeFile(tmpFile);
159
86
  }
160
87
  },
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
- }
88
+ uninstall(nativeId) {
175
89
  runOrThrow(exec, ["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"], {
176
- isOk: (r) => r.status === 0 || /cannot find/i.test(r.stderr ?? ""),
90
+ isOk: (r) => r.status === 0 || isMissingTaskResult(r),
177
91
  message: (r) => `schtasks /Delete failed: ${r.stderr || r.stdout || "no output"}.`,
178
92
  });
179
93
  },
@@ -184,219 +98,39 @@ export function SCHTASKS_BACKEND(options = {}) {
184
98
  });
185
99
  },
186
100
  list() {
187
- return [...inspectSchtasksState(exec, folder, taskName).installed];
188
- },
189
- listForRebind() {
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);
101
+ const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
102
+ message: (result) => `schtasks /Query failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
103
+ });
104
+ const rows = [];
105
+ for (const line of (listing.stdout ?? "").split(/\r?\n/)) {
106
+ const name = line.match(/^"([^"]+)",/)?.[1];
107
+ if (!name?.startsWith(folder))
108
+ continue;
109
+ const nativeId = name.slice(folder.length);
110
+ const xml = queryXml(nativeId);
111
+ const parsed = extractSchtasksInvocation(xml);
112
+ if (!parsed)
113
+ continue;
114
+ rows.push({
115
+ id: schedulerLogicalBindingId(nativeId, parsed.invocation),
116
+ nativeId,
117
+ enabled: taskXmlEnabled(xml),
118
+ signature: taskXmlSignature(xml),
119
+ ...(parsed.target !== undefined ? { target: parsed.target } : {}),
120
+ binding: parsed.binding,
121
+ ...(parsed.contextPath !== undefined ? { contextPath: parsed.contextPath } : {}),
122
+ ...(parsed.environment !== undefined ? { environment: parsed.environment } : {}),
123
+ invocation: parsed.invocation,
124
+ });
125
+ }
126
+ return rows;
203
127
  },
204
128
  expectedSignature(task, opts) {
205
- const signature = taskXmlSignature(buildSchtasksXml(task, akmArgv, logDir, {
206
- folderPrefix: folder,
207
- contextPath: opts?.contextPath ?? defaultContextPath,
208
- userSid,
209
- binding: [...(opts?.binding ?? akmArgv)],
210
- ...(opts?.target !== undefined ? { target: opts.target } : {}),
211
- }));
212
- if (signature === undefined)
213
- throw new Error("Failed to fingerprint generated Task Scheduler XML.");
214
- return signature;
129
+ return taskXmlSignature(xmlFor(task, opts));
215
130
  },
216
131
  };
217
132
  }
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 enabled = taskXmlEnabled(query.stdout);
261
- const ref = {
262
- id: schedulerLogicalBindingId(nativeId, parsed.invocation),
263
- ...(enabled !== undefined ? { enabled } : {}),
264
- ...(artifact.fingerprint !== undefined ? { signature: artifact.fingerprint } : {}),
265
- ...(parsed.target !== undefined ? { target: parsed.target } : {}),
266
- binding: parsed.binding,
267
- contextPath: parsed.contextPath,
268
- };
269
- Object.defineProperty(ref, "nativeId", { value: nativeId });
270
- Object.defineProperty(ref, "invocation", { value: Object.freeze([...parsed.invocation]) });
271
- installed.push(ref);
272
- }
273
- return Object.freeze({ installed: Object.freeze(installed), artifacts: Object.freeze(artifacts) });
274
- }
275
- function schtasksNativeIds(stdout, folder) {
276
- const ids = [];
277
- for (const line of (stdout ?? "").split(/\r?\n/)) {
278
- const match = line.match(/^"([^"]+)",/);
279
- const name = match?.[1];
280
- if (name?.startsWith(folder))
281
- ids.push(name.slice(folder.length));
282
- }
283
- return ids;
284
- }
285
- function schtasksArtifact(nativeId, xml) {
286
- const parsed = extractSchtasksInvocation(xml);
287
- const owner = parsed ? schedulerLogicalBindingOwner(nativeId, parsed.invocation) : undefined;
288
- const artifact = parsed
289
- ? {
290
- nativeId,
291
- ...(owner !== undefined ? { bindingId: owner } : {}),
292
- invocation: Object.freeze([...parsed.invocation]),
293
- }
294
- : { nativeId };
295
- const fingerprint = installedSignature(xml);
296
- if (fingerprint !== undefined)
297
- Object.defineProperty(artifact, "fingerprint", { value: fingerprint });
298
- return artifact;
299
- }
300
- function snapshotSchtasksBindings(ids, exec, folder, taskName) {
301
- const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
302
- message: (result) => `schtasks /Query failed while snapshotting bindings (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
303
- });
304
- const requestedKeys = new Set(ids.map(schedulerNativeArtifactKey));
305
- const enumeratedIds = new Set(ids);
306
- for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
307
- if (requestedKeys.has(schedulerNativeArtifactKey(nativeId)))
308
- enumeratedIds.add(nativeId);
309
- }
310
- const entries = [...enumeratedIds].map((id) => {
311
- const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
312
- isOk: (result) => result.status === 0 || isMissingTaskResult(result),
313
- message: (result) => `schtasks /Query failed while snapshotting "${taskName(id)}": ${result.stderr || result.stdout || "no output"}.`,
314
- });
315
- if (query.status !== 0)
316
- return Object.freeze({ id });
317
- const enabled = taskXmlEnabled(query.stdout);
318
- if (enabled === undefined) {
319
- throw new ConfigError(`schtasks /Query returned an unreadable definition while snapshotting "${taskName(id)}".`, "INVALID_CONFIG_FILE");
320
- }
321
- return Object.freeze({ id, xml: normalizeXmlForUtf16File(query.stdout), enabled });
322
- });
323
- const artifacts = entries.flatMap((entry) => entry.xml === undefined ? [] : [schtasksArtifact(entry.id, entry.xml)]);
324
- return Object.freeze({
325
- kind: SCHTASKS_SNAPSHOT,
326
- nativeIds: Object.freeze([...ids]),
327
- artifacts: Object.freeze(artifacts),
328
- entries: Object.freeze(entries),
329
- });
330
- }
331
- function restoreSchtasksBindings(snapshot, context, expectedCurrent) {
332
- if (!isSchtasksBindingSnapshot(snapshot)) {
333
- throw new ConfigError("Invalid Task Scheduler snapshot.", "INVALID_CONFIG_FILE");
334
- }
335
- const errors = [];
336
- let rollbackInventory;
337
- if (expectedCurrent) {
338
- try {
339
- rollbackInventory = inspectSchtasksState(context.exec, context.folder, context.taskName);
340
- }
341
- catch (error) {
342
- errors.push(error);
343
- }
344
- }
345
- for (const entry of snapshot.entries) {
346
- try {
347
- if (expectedCurrent) {
348
- if (!rollbackInventory)
349
- continue;
350
- const expected = expectedCurrent.find((candidate) => candidate.nativeId === entry.id);
351
- if (!expected) {
352
- throw new ConfigError(`Missing Task Scheduler rollback expectation for ${JSON.stringify(entry.id)}.`, "INVALID_CONFIG_FILE");
353
- }
354
- assertSchedulerRollbackArtifactCardinality(rollbackInventory.artifacts, expected);
355
- }
356
- restoreSchtasksEntry(entry, context);
357
- }
358
- catch (error) {
359
- errors.push(error);
360
- }
361
- }
362
- if (errors.length > 0) {
363
- throw new AggregateError(errors, `Failed to completely restore ${errors.length} Task Scheduler operation(s).`);
364
- }
365
- }
366
- function restoreSchtasksEntry(entry, context) {
367
- if (entry.xml === undefined) {
368
- runOrThrow(context.exec, ["schtasks", "/Delete", "/TN", context.taskName(entry.id), "/F"], {
369
- isOk: (result) => result.status === 0 || isMissingTaskResult(result),
370
- message: (result) => `schtasks /Delete failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
371
- });
372
- return;
373
- }
374
- const tmpFile = path.join(context.fsLike.tmpdir(), `akm-task-restore-${schedulerNativeBindingId(entry.id)}-${Date.now()}.xml`);
375
- context.fsLike.writeFile(tmpFile, entry.xml);
376
- try {
377
- runOrThrow(context.exec, ["schtasks", "/Create", "/TN", context.taskName(entry.id), "/XML", tmpFile, "/F"], {
378
- message: (result) => `schtasks /Create failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
379
- });
380
- const stateFlag = entry.enabled === false ? "/DISABLE" : "/ENABLE";
381
- runOrThrow(context.exec, ["schtasks", "/Change", "/TN", context.taskName(entry.id), stateFlag], {
382
- message: (result) => `schtasks /Change ${stateFlag} failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
383
- });
384
- }
385
- finally {
386
- context.fsLike.removeFile(tmpFile);
387
- }
388
- }
389
- function isSchtasksBindingSnapshot(value) {
390
- return (typeof value === "object" &&
391
- value !== null &&
392
- value.kind === SCHTASKS_SNAPSHOT &&
393
- Array.isArray(value.entries));
394
- }
395
- /**
396
- * Recover the bundle name embedded as a `--bundle <bundle>` pair in the
397
- * PowerShell `<Arguments>` of an installed Task Scheduler definition. Returns
398
- * undefined for the primary/default form.
399
- */
133
+ /** The `--bundle <bundle>` token of an installed Task Scheduler definition; undefined for the primary form. */
400
134
  export function extractSchtasksTarget(xml) {
401
135
  return extractSchtasksInvocation(xml)?.target;
402
136
  }
@@ -408,19 +142,26 @@ export function extractSchtasksInvocation(xml) {
408
142
  const invocationStart = findPowerShellInvocationOperator(commandLine);
409
143
  if (invocationStart === undefined)
410
144
  return undefined;
411
- return parseScheduledBindingArgv(parsePowerShellSingleQuotedArgs(commandLine, invocationStart + 1));
145
+ const parsed = parseScheduledInvocationArgv(parsePowerShellSingleQuotedArgs(commandLine, invocationStart + 1));
146
+ if (!parsed)
147
+ return undefined;
148
+ const variables = {};
149
+ const assignments = commandLine.slice(0, invocationStart);
150
+ for (const assignment of assignments.matchAll(/\$env:([A-Za-z_][A-Za-z0-9_]*)='((?:[^']|'')*)'\s*;/g)) {
151
+ variables[assignment[1]] = assignment[2].replaceAll("''", "'");
152
+ }
153
+ const environment = scheduledRowEnvironmentFrom(variables);
154
+ return environment ? { ...parsed, environment } : parsed;
412
155
  }
413
156
  function findPowerShellInvocationOperator(script) {
414
157
  let inSingleQuote = false;
415
158
  for (let index = 0; index < script.length; index += 1) {
416
159
  const char = script[index];
417
160
  if (char === "'") {
418
- if (inSingleQuote && script[index + 1] === "'") {
161
+ if (inSingleQuote && script[index + 1] === "'")
419
162
  index += 1;
420
- }
421
- else {
163
+ else
422
164
  inSingleQuote = !inSingleQuote;
423
- }
424
165
  continue;
425
166
  }
426
167
  if (!inSingleQuote && char === "&" && /\s/.test(script[index + 1] ?? ""))
@@ -463,70 +204,35 @@ function parsePowerShellSingleQuotedArgs(script, start) {
463
204
  }
464
205
  return argv;
465
206
  }
466
- /**
467
- * Re-verify ownership of the native artifact immediately before `/Create /F`
468
- * overwrites it (the read/prepare-to-/Create ownership race at the native
469
- * boundary).
470
- *
471
- * This used to also compare the fresh query against the first read taken
472
- * earlier in `install` and refuse the whole operation if anything about the
473
- * XML had changed in between -- a freshness re-check on top of the identity
474
- * re-check right below it. That extra comparison never caught anything the
475
- * owner check didn't already cover (a foreign/changed owner still fails
476
- * `assertSchedulerNativeArtifactOwner`), and `task sync` is idempotent, so a
477
- * spurious refusal here only cost the user a rerun of a command they'd
478
- * already asked for. Dropped; the owner re-check (the actual corruption/
479
- * clobber guard -- no fallback exists if a foreign task got silently
480
- * overwritten) stays.
481
- */
482
- function assertSchtasksArtifactUnchanged(exec, nativeTaskName, nativeId, task) {
483
- // This runs outside install's rollback region: no scheduler mutation has
484
- // occurred, so a raced owner must remain untouched rather than be restored
485
- // from the stale first read.
486
- const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", nativeTaskName, "/XML"], {
487
- isOk: (result) => result.status === 0 || isMissingTaskResult(result),
488
- message: (result) => `schtasks /Query failed during final ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
489
- });
490
- if (current.status === 0) {
491
- assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(current.stdout)?.invocation);
492
- }
493
- }
494
207
  export function buildSchtasksXml(task, akmArgv, logDir, options) {
495
208
  const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
496
209
  const now = options.now ? options.now() : new Date();
497
- const definition = buildSchtasksDefinition(task, options.binding ?? akmArgv, logDir, folder, options.contextPath, options.userSid, options.target);
498
- const triggerXml = renderSchtasksTrigger(definition.trigger, now);
499
- const nativeId = schedulerBindingNativeId(task);
500
- return schtasksTemplate
501
- .replaceAll("{{TASK_ID}}", escapeXml(nativeId))
502
- .replaceAll("{{FOLDER}}", escapeXml(folder))
503
- .replace("{{SIGNATURE}}", definition.signature)
504
- .replace("{{TRIGGER_XML}}", triggerXml)
505
- .replace('<Principal id="Author">', `<Principal id="Author">\n <UserId>${escapeXml(options.userSid)}</UserId>`)
506
- .replace("{{ENABLED}}", task.enabled ? "true" : "false")
507
- .replace("{{COMMAND}}", escapeXml(definition.command))
508
- .replace("{{ARGS}}", escapeXml(definition.args))
509
- .replace("{{LOG_PATH}}", escapeXml(definition.logPath));
510
- }
511
- function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid, _target) {
512
- const spec = parseSchedule(task.cron, "schtasks");
513
- const trigger = translateToSchtasks(spec);
514
- const invocation = buildScheduledBindingInvocation(akmArgv, contextPath, task.invocation);
515
- const invoke = `& ${invocation.argv.map((arg) => quotePowerShell(arg)).join(" ")}`;
516
- const executionEvidence = task.executionEvidenceDigest === undefined
517
- ? ""
518
- : `$null=${quotePowerShell(`akm-workflow-evidence:${assertSchedulerExecutionEvidenceDigest(task.executionEvidenceDigest)}`)}; `;
519
- const script = `${executionEvidence}${invoke}; exit $LASTEXITCODE`;
210
+ const trigger = translateToSchtasks(parseSchedule(task.cron, "schtasks"));
211
+ const argv = buildScheduledInvocation(options.binding ?? akmArgv, task.invocation);
212
+ const assignments = scheduledRowEnvironmentEntries(options.environment)
213
+ .map(([name, value]) => `$env:${name}=${quotePowerShell(value)}; `)
214
+ .join("");
215
+ const script = `${assignments}& ${argv.map((arg) => quotePowerShell(arg)).join(" ")}; exit $LASTEXITCODE`;
520
216
  const command = "powershell.exe";
521
217
  const args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script].map(quoteArg).join(" ");
522
218
  const nativeId = schedulerBindingNativeId(task);
523
219
  const logPath = path.join(logDir, `${nativeId}.log`);
524
- // The boundary changes on reinstall, and enabled state can change via /Change.
525
- // Keep both outside the stored definition fingerprint so no-op sync stays stable.
220
+ // The boundary changes on reinstall, and enabled state can change via
221
+ // /Change. Keep both outside the stored definition fingerprint so no-op
222
+ // sync stays stable.
526
223
  const fingerprint = createHash("sha256")
527
- .update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid }))
224
+ .update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid: options.userSid }))
528
225
  .digest("hex");
529
- return { trigger, command, args, logPath, signature: `${SIGNATURE_PREFIX}${fingerprint}` };
226
+ return schtasksTemplate
227
+ .replaceAll("{{TASK_ID}}", escapeXml(nativeId))
228
+ .replaceAll("{{FOLDER}}", escapeXml(folder))
229
+ .replace("{{SIGNATURE}}", `${SIGNATURE_PREFIX}${fingerprint}`)
230
+ .replace("{{TRIGGER_XML}}", renderSchtasksTrigger(trigger, now))
231
+ .replace('<Principal id="Author">', `<Principal id="Author">\n <UserId>${escapeXml(options.userSid)}</UserId>`)
232
+ .replace("{{ENABLED}}", task.enabled ? "true" : "false")
233
+ .replace("{{COMMAND}}", escapeXml(command))
234
+ .replace("{{ARGS}}", () => escapeXml(args))
235
+ .replace("{{LOG_PATH}}", escapeXml(logPath));
530
236
  }
531
237
  function renderSchtasksTrigger(trigger, now) {
532
238
  return expandNativeTriggers(trigger)
@@ -540,14 +246,7 @@ function expandNativeTriggers(trigger) {
540
246
  case "minuteValues":
541
247
  return trigger.minutes.map((atMinute) => ({ kind: "daily", atHour: 0, atMinute, repeatEveryMinutes: 60 }));
542
248
  case "hour":
543
- return [
544
- {
545
- kind: "daily",
546
- atHour: 0,
547
- atMinute: trigger.atMinute,
548
- repeatEveryMinutes: trigger.everyHours * 60,
549
- },
550
- ];
249
+ return [{ kind: "daily", atHour: 0, atMinute: trigger.atMinute, repeatEveryMinutes: trigger.everyHours * 60 }];
551
250
  case "hourValues":
552
251
  return trigger.hours.map((atHour) => ({ kind: "daily", atHour, atMinute: trigger.atMinute }));
553
252
  case "daily":
@@ -563,8 +262,8 @@ function renderNativeTrigger(trigger, startBoundary) {
563
262
  const repetition = trigger.repeatEveryMinutes === undefined
564
263
  ? ""
565
264
  : ` <Repetition>
566
- <Interval>${formatRepetitionInterval(trigger.repeatEveryMinutes)}</Interval>
567
- <Duration>${formatRepetitionDuration(trigger.repeatEveryMinutes)}</Duration>
265
+ <Interval>${formatMinuteDuration(trigger.repeatEveryMinutes)}</Interval>
266
+ <Duration>${formatMinuteDuration(24 * 60 - trigger.repeatEveryMinutes)}</Duration>
568
267
  <StopAtDurationEnd>false</StopAtDurationEnd>
569
268
  </Repetition>
570
269
  `;
@@ -617,18 +316,10 @@ ${months}
617
316
  </ScheduleByMonth>
618
317
  </CalendarTrigger>`;
619
318
  }
620
- function formatRepetitionInterval(minutes) {
621
- return formatMinuteDuration(minutes);
622
- }
623
- function formatRepetitionDuration(intervalMinutes) {
624
- return formatMinuteDuration(24 * 60 - intervalMinutes);
625
- }
626
319
  function formatMinuteDuration(minutes) {
627
320
  const hours = Math.floor(minutes / 60);
628
321
  const remainingMinutes = minutes % 60;
629
- const hourPart = hours > 0 ? `${hours}H` : "";
630
- const minutePart = remainingMinutes > 0 ? `${remainingMinutes}M` : "";
631
- return `PT${hourPart}${minutePart}`;
322
+ return `PT${hours > 0 ? `${hours}H` : ""}${remainingMinutes > 0 ? `${remainingMinutes}M` : ""}`;
632
323
  }
633
324
  function nextStartBoundary(trigger, now) {
634
325
  const boundary = new Date(now.getTime());
@@ -669,16 +360,11 @@ function nextStartBoundary(trigger, now) {
669
360
  }
670
361
  }
671
362
  function formatStartBoundary(d) {
672
- // Local-time ISO-8601 (no zone suffix) — Task Scheduler interprets a
673
- // bare boundary in the registering user's timezone, which matches what
674
- // a user typing "0 9 * * *" intuitively means ("9am local").
675
- const yyyy = d.getFullYear();
676
- const mm = String(d.getMonth() + 1).padStart(2, "0");
677
- const dd = String(d.getDate()).padStart(2, "0");
678
- const hh = String(d.getHours()).padStart(2, "0");
679
- const mi = String(d.getMinutes()).padStart(2, "0");
680
- const ss = String(d.getSeconds()).padStart(2, "0");
681
- return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}`;
363
+ // Local-time ISO-8601 (no zone suffix) — Task Scheduler interprets a bare
364
+ // boundary in the registering user's timezone, which matches what a user
365
+ // typing "0 9 * * *" means ("9am local").
366
+ const pad = (value) => String(value).padStart(2, "0");
367
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
682
368
  }
683
369
  function quoteArg(s) {
684
370
  if (/^[A-Za-z0-9_\-./@:%=+,\\]+$/.test(s))
@@ -690,110 +376,24 @@ function quoteArg(s) {
690
376
  function quotePowerShell(value) {
691
377
  return `'${value.replaceAll("'", "''")}'`;
692
378
  }
693
- function installedSignature(xml) {
694
- return taskXmlSignature(xml);
695
- }
696
379
  function isMissingTaskResult(result) {
697
380
  return /cannot find|not found/i.test(`${result.stderr ?? ""}\n${result.stdout ?? ""}`);
698
381
  }
382
+ // ── Installed-definition signature ──────────────────────────────────────────
383
+ //
384
+ // Task Scheduler rewrites a registered definition (schema defaults filled in,
385
+ // StartBoundary moved on), so a row is compared by the fingerprint akm stamps
386
+ // into `<Source>` — everything it renders except the enabled state — plus the
387
+ // enabled state `/Change` toggles. A task edited in Task Scheduler that keeps
388
+ // that fingerprint is left as it is.
699
389
  function taskXmlSignature(xml) {
700
- try {
701
- const document = parseXml(xml);
702
- const task = findChild(document, "Task");
703
- const triggers = findChild(task, "Triggers");
704
- const principals = findChild(task, "Principals");
705
- const settings = findChild(task, "Settings");
706
- const actions = findChild(task, "Actions");
707
- if (!triggers || !principals || !settings || !actions)
708
- return undefined;
709
- normalizeTriggerBoundaries(triggers);
710
- normalizeNativeDefaults(triggers, principals, settings);
711
- const enabledElement = findChild(settings, "Enabled");
712
- const enabledValue = enabledElement ? elementText(enabledElement).toLowerCase() : undefined;
713
- const enabled = enabledValue === undefined || enabledValue === "true" || enabledValue === "1";
714
- // Enabled is represented explicitly in the signature suffix. Removing it
715
- // here also treats an omitted Enabled element as its schema default, true.
716
- settings.children = settings.children.filter((child) => typeof child === "string" || child.name.toLowerCase() !== "enabled");
717
- const canonical = [triggers, principals, settings, actions].map(canonicalXmlElement).join("\n");
718
- const fingerprint = createHash("sha256").update(canonical).digest("hex");
719
- return signatureWithEnabled(`${SIGNATURE_PREFIX}${fingerprint}`, enabled);
720
- }
721
- catch {
722
- return undefined;
723
- }
390
+ const source = xml.match(/<(?:[\w.-]+:)?Source>([^<]*)<\//i)?.[1]?.trim() ?? "";
391
+ return `${decodeXml(source)}|enabled=${taskXmlEnabled(xml)}`;
724
392
  }
725
393
  function taskXmlEnabled(xml) {
726
- try {
727
- const document = parseXml(xml);
728
- const settings = findChild(findChild(document, "Task"), "Settings");
729
- if (!settings)
730
- return undefined;
731
- const enabledElement = findChild(settings, "Enabled");
732
- if (!enabledElement)
733
- return true;
734
- const enabled = elementText(enabledElement).toLowerCase();
735
- if (enabled === "true" || enabled === "1")
736
- return true;
737
- if (enabled === "false" || enabled === "0")
738
- return false;
739
- return undefined;
740
- }
741
- catch {
742
- return undefined;
743
- }
744
- }
745
- function parseXml(xml) {
746
- const document = { name: "#document", attributes: {}, children: [] };
747
- const stack = [document];
748
- const tokens = xml.match(/<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<[^>]+>|[^<]+/g) ?? [];
749
- for (const token of tokens) {
750
- if (token.startsWith("<!--") || token.startsWith("<?"))
751
- continue;
752
- if (token.startsWith("</")) {
753
- if (stack.length === 1)
754
- throw new Error("Unexpected XML closing tag.");
755
- const closingName = localXmlName(token.slice(2, -1).trim());
756
- const current = stack.pop();
757
- if (current?.name !== closingName)
758
- throw new Error("Mismatched XML closing tag.");
759
- continue;
760
- }
761
- if (token.startsWith("<")) {
762
- const selfClosing = /\/\s*>$/.test(token);
763
- const match = token.match(/^<\s*([^\s/>]+)([\s\S]*?)\/?\s*>$/);
764
- if (!match)
765
- throw new Error("Invalid XML opening tag.");
766
- const element = {
767
- name: localXmlName(match[1]),
768
- attributes: parseXmlAttributes(match[2]),
769
- children: [],
770
- };
771
- stack[stack.length - 1].children.push(element);
772
- if (!selfClosing)
773
- stack.push(element);
774
- continue;
775
- }
776
- const text = decodeXml(token.trim());
777
- if (text.length > 0)
778
- stack[stack.length - 1].children.push(text);
779
- }
780
- if (stack.length !== 1)
781
- throw new Error("Unclosed XML tag.");
782
- return document;
783
- }
784
- function parseXmlAttributes(raw) {
785
- const attributes = {};
786
- const pattern = /([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
787
- for (const match of raw.matchAll(pattern)) {
788
- const name = localXmlName(match[1]);
789
- if (match[1] === "xmlns" || match[1].startsWith("xmlns:"))
790
- continue;
791
- attributes[name] = decodeXml(match[2] ?? match[3] ?? "");
792
- }
793
- return attributes;
794
- }
795
- function localXmlName(name) {
796
- return name.slice(name.lastIndexOf(":") + 1);
394
+ const settings = xml.match(/<(?:[\w.-]+:)?Settings>([\s\S]*?)<\/(?:[\w.-]+:)?Settings>/i)?.[1] ?? "";
395
+ const value = settings.match(/<(?:[\w.-]+:)?Enabled>\s*([^<]*?)\s*<\//i)?.[1]?.toLowerCase();
396
+ return value !== "false" && value !== "0";
797
397
  }
798
398
  function decodeXml(value) {
799
399
  return value.replace(/&(?:#(\d+)|#x([\da-f]+)|amp|lt|gt|quot|apos);/gi, (entity, decimal, hex) => {
@@ -817,124 +417,20 @@ function decodeXml(value) {
817
417
  }
818
418
  });
819
419
  }
820
- function findChild(parent, name) {
821
- return parent?.children.find((child) => typeof child !== "string" && child.name.toLowerCase() === name.toLowerCase());
822
- }
823
- function elementText(element) {
824
- return element.children.filter((child) => typeof child === "string").join("");
825
- }
826
- function normalizeTriggerBoundaries(triggers) {
827
- for (const trigger of triggers.children) {
828
- if (typeof trigger === "string")
829
- continue;
830
- const boundary = findChild(trigger, "StartBoundary");
831
- if (!boundary)
832
- continue;
833
- const time = elementText(boundary).match(/T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?/);
834
- if (!time)
835
- continue;
836
- const secondsSinceMidnight = Number(time[1]) * 3600 + Number(time[2]) * 60 + Number(time[3]);
837
- const interval = findChild(findChild(trigger, "Repetition"), "Interval");
838
- const intervalSeconds = interval ? parseIsoDurationSeconds(elementText(interval)) : undefined;
839
- boundary.children = [
840
- intervalSeconds === undefined
841
- ? `dynamic-date;time=${time[1]}:${time[2]}:${time[3]}`
842
- : `dynamic-cycle;phase-seconds=${secondsSinceMidnight % intervalSeconds}`,
843
- ];
844
- }
845
- }
846
- const MATERIALIZED_SETTING_DEFAULTS = {
847
- allowstartondemand: "true",
848
- allowhardterminate: "true",
849
- startwhenavailable: "false",
850
- runonlyifnetworkavailable: "false",
851
- hidden: "false",
852
- runonlyifidle: "false",
853
- waketorun: "false",
854
- executiontimelimit: "PT72H",
855
- priority: "7",
856
- compatibility: "Vista",
857
- useunifiedschedulingengine: "true",
858
- disallowstartonremoteappsession: "false",
859
- volatile: "false",
860
- };
861
- const MATERIALIZED_IDLE_DEFAULTS = {
862
- duration: "PT10M",
863
- waittimeout: "PT1H",
864
- stoponidleend: "true",
865
- restartonidle: "false",
866
- };
867
- function normalizeNativeDefaults(triggers, principals, settings) {
868
- for (const principal of elementChildren(principals)) {
869
- principal.children = principal.children.filter((child) => {
870
- if (typeof child === "string")
871
- return true;
872
- return child.name.toLowerCase() !== "runlevel" || elementText(child).toLowerCase() !== "leastprivilege";
873
- });
874
- }
875
- settings.children = settings.children.filter((child) => {
876
- if (typeof child === "string")
877
- return true;
878
- const name = child.name.toLowerCase();
879
- if (name === "idlesettings")
880
- return !containsOnlyDefaults(child, MATERIALIZED_IDLE_DEFAULTS);
881
- const defaultValue = MATERIALIZED_SETTING_DEFAULTS[name];
882
- return defaultValue === undefined || elementText(child) !== defaultValue;
883
- });
884
- for (const trigger of elementChildren(triggers)) {
885
- trigger.children = trigger.children.filter((child) => {
886
- if (typeof child === "string")
887
- return true;
888
- const name = child.name.toLowerCase();
889
- if (name === "enabled")
890
- return elementText(child).toLowerCase() !== "true";
891
- return name !== "executiontimelimit" || elementText(child) !== "PT72H";
892
- });
893
- }
894
- }
895
420
  function resolveCurrentUserSid(exec) {
896
421
  const result = runOrThrow(exec, ["whoami", "/user", "/fo", "csv", "/nh"], {
897
422
  message: (r) => `whoami /user failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
898
423
  });
899
424
  const match = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.match(/\bS-\d+(?:-\d+){2,}\b/i);
900
- if (!match) {
425
+ if (!match)
901
426
  throw new ConfigError("whoami /user returned no Windows user SID.", "INVALID_CONFIG_FILE");
902
- }
903
427
  return `S${match[0].slice(1)}`;
904
428
  }
905
- function elementChildren(element) {
906
- return element.children.filter((child) => typeof child !== "string");
907
- }
908
- function containsOnlyDefaults(element, defaults) {
909
- const children = elementChildren(element);
910
- return (children.length > 0 &&
911
- children.length === element.children.length &&
912
- children.every((child) => defaults[child.name.toLowerCase()] === elementText(child)));
913
- }
914
- function parseIsoDurationSeconds(value) {
915
- const match = value.match(/^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i);
916
- if (!match)
917
- return undefined;
918
- const seconds = Number(match[1] ?? 0) * 3600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0);
919
- return seconds > 0 ? seconds : undefined;
920
- }
921
- function canonicalXmlElement(element) {
922
- const attributes = Object.entries(element.attributes).sort(([a], [b]) => a.localeCompare(b));
923
- const children = element.children.map((child) => typeof child === "string" ? ["text", child] : ["element", canonicalXmlElement(child)]);
924
- children.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
925
- return JSON.stringify([element.name, attributes, children]);
926
- }
927
- function signatureWithEnabled(signature, enabled) {
928
- return `${signature}|enabled=${enabled ? "true" : "false"}`;
929
- }
930
- function defaultSchtasksExec() {
931
- return nodeExec();
932
- }
933
429
  function defaultSchtasksFs() {
934
430
  return {
935
431
  ...nodeFs(),
936
432
  writeFile(file, content) {
937
- fs.writeFileSync(file, `\uFEFF${content}`, { encoding: "utf16le" });
433
+ fs.writeFileSync(file, `${content}`, { encoding: "utf16le" });
938
434
  },
939
435
  removeFile(file) {
940
436
  try {