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
@@ -0,0 +1,170 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * `BEGIN IMMEDIATE` transactions and the one SQLite contention classifier.
6
+ *
7
+ * `db.transaction()` is DEFERRED by default on both Bun and better-sqlite3,
8
+ * which means two writers can both perform stale preflight reads and only race
9
+ * when they finally attempt the write. Proposal creation, queue mutation and
10
+ * schema migration need the write lock BEFORE those reads so concurrent
11
+ * processes serialize on the live state rather than clobbering each other.
12
+ *
13
+ * Every open already carries a 30 s `busy_timeout` (`sqlite-pragmas.ts`), so a
14
+ * single blocked statement waits before failing. {@link beginImmediateTransaction}
15
+ * additionally retries `BEGIN IMMEDIATE` itself ({@link WITH_IMMEDIATE_TX_MAX_ATTEMPTS}
16
+ * attempts) for the rarer case of two writers racing the BEGIN statement
17
+ * back-to-back. If every attempt is still contention-shaped
18
+ * ({@link isSqliteContentionError} — SQLITE_BUSY/LOCKED, the matching message
19
+ * text, or the phantom-BEGIN marker), the exhaustion throw is reclassified
20
+ * into `TransientError("STATE_DB_CONTENDED")` (exit 75, #948 addendum) instead
21
+ * of surfacing the raw driver text: another akm process (an unrelated
22
+ * `improve`, `workflow run`, or task run — not necessarily contending for the
23
+ * same row) is writing the database right now. A genuinely unrelated error
24
+ * (real corruption, a body-thrown failure) is never reclassified and rethrows
25
+ * exactly as raised.
26
+ *
27
+ * This is the single place SQLITE_BUSY/LOCKED becomes exit 75 for state.db:
28
+ * `openStateDatabase`'s migration transaction, every repository write and the
29
+ * shared migration runner (`engines/sqlite-migrations.ts`) all go through it.
30
+ * `core/state-db.ts` re-exports these helpers for its callers.
31
+ *
32
+ * @module storage/sqlite-transaction
33
+ */
34
+ import { TransientError } from "../core/errors.js";
35
+ import { sleepSync } from "../runtime.js";
36
+ /**
37
+ * Whether `err` is one of the SQLite conditions a concurrent-writer race can
38
+ * throw that are transient — the statement did NOT corrupt anything, another
39
+ * writer just holds the lock right now — and therefore safe to retry or
40
+ * reclassify as ordinary contention rather than a genuine failure:
41
+ * - `SQLITE_BUSY` / `SQLITE_LOCKED` (either driver's `.code`).
42
+ * - "database is locked" / "database table is locked" message text.
43
+ * - the phantom-BEGIN marker synthesized below when `BEGIN IMMEDIATE`
44
+ * returns without actually opening a transaction.
45
+ *
46
+ * This is the single shared classifier for "is this ordinary contention"
47
+ * (#948): `reclassifyIndexDbContention` (indexer) and the workflow-runs
48
+ * repository's lease-contention classifier (which additionally matches a
49
+ * couple of corruption-shaped texts specific to its own narrower cross-process
50
+ * race and keeps its own live-lease confirmation before reclassifying) both
51
+ * delegate to it. Matching this set alone is never sufficient to declare
52
+ * something DEFINITELY contention when a caller needs corroborating evidence
53
+ * (see the lease path); for `beginImmediateTransaction`'s own exhaustion case
54
+ * there is no such evidence available, so the five 30 s `busy_timeout` waits
55
+ * already spent stand as the evidence instead.
56
+ */
57
+ export function isSqliteContentionError(err) {
58
+ const code = err?.code;
59
+ if (code === "SQLITE_BUSY" || code === "SQLITE_LOCKED")
60
+ return true;
61
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
62
+ return (msg.includes("database is locked") ||
63
+ msg.includes("database table is locked") ||
64
+ // Phantom BEGIN (see below) — synthesized when BEGIN IMMEDIATE returns
65
+ // without opening a transaction. Safe to retry: fn() has not run.
66
+ msg.includes("did not open a transaction"));
67
+ }
68
+ const WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
69
+ /**
70
+ * Reclassify an exhausted-retry BEGIN failure that is still contention-shaped
71
+ * (#948) into a `TransientError("STATE_DB_CONTENDED")`, the same class as a
72
+ * held workflow run lock (`RUN_LEASE_HELD`): the driver text is accurate but unhelpful (`{"ok":false,"error":"database is
73
+ * locked"}`, exit 70/INTERNAL) — this instead reads as a retryable-shortly
74
+ * signal (exit 75, sysexits EX_TEMPFAIL) with the original error preserved as
75
+ * `cause` for `--verbose`/debugging. A genuinely unrelated error (not
76
+ * contention-shaped) is rethrown exactly as raised, never reclassified.
77
+ */
78
+ function throwBeginFailure(err) {
79
+ if (isSqliteContentionError(err)) {
80
+ const contended = new TransientError("akm's state database is busy (another akm process is writing it); retry shortly.", "STATE_DB_CONTENDED");
81
+ contended.cause = err;
82
+ throw contended;
83
+ }
84
+ throw err;
85
+ }
86
+ /**
87
+ * Open, but deliberately do not finish, an immediate transaction.
88
+ *
89
+ * This is the split-phase counterpart to {@link withImmediateTransaction} for
90
+ * the source-update coordinator: index finalization must mutate state.db in a
91
+ * transaction that remains pending until content, lockfile, and index
92
+ * publication have all succeeded. The caller that asked for this split phase
93
+ * owns the matching COMMIT/ROLLBACK.
94
+ *
95
+ * Only a contention-shaped BEGIN failure is retried. "cannot start a
96
+ * transaction within a transaction" is deliberately NOT: it means a
97
+ * transaction is already open on this connection (a re-entrant call — handled
98
+ * by the entry guard in {@link withImmediateTransaction}), and "retrying" it
99
+ * with a ROLLBACK would destroy the caller's transaction (issue #686).
100
+ */
101
+ export function beginImmediateTransaction(db) {
102
+ if (db.inTransaction) {
103
+ throw new Error("beginImmediateTransaction requires a connection with no active transaction");
104
+ }
105
+ let lastBeginErr;
106
+ for (let attempt = 1; attempt <= WITH_IMMEDIATE_TX_MAX_ATTEMPTS; attempt++) {
107
+ try {
108
+ db.exec("BEGIN IMMEDIATE");
109
+ if (!db.inTransaction) {
110
+ throw new Error("BEGIN IMMEDIATE did not open a transaction (phantom contention state)");
111
+ }
112
+ return;
113
+ }
114
+ catch (err) {
115
+ lastBeginErr = err;
116
+ if (isSqliteContentionError(err) && attempt < WITH_IMMEDIATE_TX_MAX_ATTEMPTS) {
117
+ if (db.inTransaction) {
118
+ try {
119
+ db.exec("ROLLBACK");
120
+ }
121
+ catch {
122
+ // Transaction already gone — safe to retry BEGIN.
123
+ }
124
+ }
125
+ sleepSync(2 ** (attempt - 1));
126
+ continue;
127
+ }
128
+ throwBeginFailure(err);
129
+ }
130
+ }
131
+ throwBeginFailure(lastBeginErr);
132
+ }
133
+ /** Run `fn` inside a `BEGIN IMMEDIATE` transaction, joining one that is already open on `db`. */
134
+ export function withImmediateTransaction(db, fn) {
135
+ // Re-entrancy guard (issue #686): if a transaction is already open on this
136
+ // connection (e.g. a nested withImmediateTransaction call inside an outer
137
+ // frame's fn), join it — run fn directly with no BEGIN/COMMIT/ROLLBACK of
138
+ // our own. Without this, the nested BEGIN throws "cannot start a transaction
139
+ // within a transaction", which the old retry path answered with an
140
+ // unconditional ROLLBACK — destroying the OUTER transaction and leaving its
141
+ // COMMIT to fail with "cannot commit - no transaction is active".
142
+ if (db.inTransaction) {
143
+ return fn();
144
+ }
145
+ beginImmediateTransaction(db);
146
+ try {
147
+ const result = fn();
148
+ if (!db.inTransaction) {
149
+ // The transaction we opened vanished while fn() ran (e.g. an
150
+ // auto-rollback or a stray ROLLBACK inside fn). fn's writes may have
151
+ // escaped serialization, so retrying is unsafe — fail loudly instead of
152
+ // letting COMMIT throw the opaque "cannot commit - no transaction is
153
+ // active" SQLiteError.
154
+ throw new Error("withImmediateTransaction invariant violated: transaction opened by BEGIN IMMEDIATE was no longer active after the transaction body ran; refusing to COMMIT (writes may have escaped serialization)");
155
+ }
156
+ db.exec("COMMIT");
157
+ return result;
158
+ }
159
+ catch (err) {
160
+ if (db.inTransaction) {
161
+ try {
162
+ db.exec("ROLLBACK");
163
+ }
164
+ catch {
165
+ // Ignore rollback failures so the original error is preserved.
166
+ }
167
+ }
168
+ throw err;
169
+ }
170
+ }
@@ -0,0 +1,130 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * state.db integrity + reclaimable-space probes (R0), and the VACUUM pass
6
+ * state.db and index.db share.
7
+ *
8
+ * `akm health`'s `state-db-integrity` check (src/commands/health/checks.ts)
9
+ * is a pure projection like every other check, so the actual IO lives here:
10
+ * a read-only `PRAGMA quick_check` and a read-only freelist/page-count read.
11
+ * Both open their own short-lived read-only connection via the plain
12
+ * {@link openDatabase} opener rather than `openStateDatabase`
13
+ * (src/core/state-db.ts), since a corrupt database must not need a clean
14
+ * migration-ledger read, let alone a migration, just to report itself as
15
+ * corrupt.
16
+ *
17
+ * {@link vacuumIfReclaimable} is the VACUUM step: given an already-open
18
+ * read-write connection (VACUUM cannot run inside a transaction, and a
19
+ * read-only handle cannot run it at all) and a freelist reading, it VACUUMs
20
+ * when asked to or when the freelist ratio crosses
21
+ * {@link STATE_DB_FREELIST_WARN_RATIO}, and never throws — a locked/busy
22
+ * database is reported, not raised. improve's post-purge pass runs it on
23
+ * state.db, and `akm index` on index.db.
24
+ *
25
+ * @module storage/state-db-integrity
26
+ */
27
+ import { appendEvent } from "../core/events.js";
28
+ import { openDatabase } from "./database.js";
29
+ import { applyReadonlyPragmas } from "./sqlite-pragmas.js";
30
+ /** How many corruption errors `PRAGMA quick_check` collects before it stops scanning and returns. */
31
+ const QUICK_CHECK_ERROR_LIMIT = 10;
32
+ /**
33
+ * Above this fraction of free pages, `state-db-integrity` warns, and
34
+ * {@link vacuumIfReclaimable} compacts state.db (after improve's retention
35
+ * purge) or index.db (at the end of `akm index`).
36
+ */
37
+ export const STATE_DB_FREELIST_WARN_RATIO = 0.5;
38
+ /** Event appended by {@link vacuumIfReclaimable} after a successful VACUUM of state.db. */
39
+ export const STATE_DB_VACUUMED_EVENT = "state_db_vacuumed";
40
+ /** Event appended by {@link vacuumIfReclaimable} after a successful VACUUM of index.db. */
41
+ export const INDEX_DB_VACUUMED_EVENT = "index_db_vacuumed";
42
+ function firstColumn(row) {
43
+ return row === undefined ? undefined : Object.values(row)[0];
44
+ }
45
+ function openReadonlyStateDb(dbPath) {
46
+ const db = openDatabase(dbPath, { readonly: true, create: false });
47
+ applyReadonlyPragmas(db);
48
+ return db;
49
+ }
50
+ /**
51
+ * Run `PRAGMA quick_check(N)` against `dbPath` read-only. Sub-second on a
52
+ * healthy multi-hundred-MB file; on a corrupt one, `N` bounds how many errors
53
+ * SQLite collects before it stops scanning, which keeps the check's runtime
54
+ * bounded even against a badly corrupt file.
55
+ */
56
+ export function runStateDbQuickCheck(dbPath) {
57
+ let db;
58
+ try {
59
+ db = openReadonlyStateDb(dbPath);
60
+ const rows = db.prepare(`PRAGMA quick_check(${QUICK_CHECK_ERROR_LIMIT})`).all();
61
+ const lines = rows.map((row) => String(firstColumn(row)));
62
+ const ok = lines.length === 1 && lines[0] === "ok";
63
+ return { ok, lines };
64
+ }
65
+ catch (err) {
66
+ return { ok: false, lines: [], error: err instanceof Error ? err.message : String(err) };
67
+ }
68
+ finally {
69
+ db?.close();
70
+ }
71
+ }
72
+ /**
73
+ * Read `PRAGMA freelist_count` / `PRAGMA page_count` off an already-open
74
+ * connection. Shared by {@link getStateDbFreelistInfo} (which opens its own
75
+ * read-only handle) and the post-purge VACUUM call site, which must read the
76
+ * freelist off the same read-write connection the purge just used rather
77
+ * than open a second one.
78
+ */
79
+ export function readFreelistInfo(db) {
80
+ const freelistCount = Number(firstColumn(db.prepare("PRAGMA freelist_count").get()) ?? 0);
81
+ const pageCount = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
82
+ return { freelistCount, pageCount, ratio: pageCount > 0 ? freelistCount / pageCount : 0 };
83
+ }
84
+ /** Read `PRAGMA freelist_count` / `PRAGMA page_count` — how much of state.db is reclaimable by VACUUM. */
85
+ export function getStateDbFreelistInfo(dbPath) {
86
+ let db;
87
+ try {
88
+ db = openReadonlyStateDb(dbPath);
89
+ return readFreelistInfo(db);
90
+ }
91
+ catch (err) {
92
+ return { freelistCount: 0, pageCount: 0, ratio: 0, error: err instanceof Error ? err.message : String(err) };
93
+ }
94
+ finally {
95
+ db?.close();
96
+ }
97
+ }
98
+ /**
99
+ * VACUUM `db` when `vacuum.force` is set or `freelist.ratio` exceeds
100
+ * {@link STATE_DB_FREELIST_WARN_RATIO}, appending `vacuum.eventType` with
101
+ * the pages before/after. Runs on the caller's read-write connection — for
102
+ * state.db right after the retention purge, for index.db at the end of an
103
+ * index run. Never throws: a locked/busy database (another writer holds the
104
+ * file right now) is reported via `reason: "busy"` rather than raised, since
105
+ * this is opportunistic maintenance and must not fail the pass it follows.
106
+ *
107
+ * The event is appended via `appendEvent` (not a direct `insertEvent` on
108
+ * `db`) so it honors the caller's `EventsContext` — `readOnly` suppresses
109
+ * the write and an injected `now` is used for `ts` — the same as every
110
+ * other event `runRetentionPurgePass` appends in this callback.
111
+ */
112
+ export function vacuumIfReclaimable(db, freelist, vacuum, eventsCtx) {
113
+ if (!vacuum.force && freelist.ratio <= STATE_DB_FREELIST_WARN_RATIO) {
114
+ return { ran: false, reason: "below-threshold", pagesBefore: freelist.pageCount };
115
+ }
116
+ try {
117
+ db.exec("VACUUM");
118
+ }
119
+ catch (err) {
120
+ const message = err instanceof Error ? err.message : String(err);
121
+ const busy = /busy|locked/i.test(message);
122
+ return { ran: false, reason: busy ? "busy" : "error", pagesBefore: freelist.pageCount, error: message };
123
+ }
124
+ const pagesAfter = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
125
+ appendEvent({
126
+ eventType: vacuum.eventType,
127
+ metadata: { pagesBefore: freelist.pageCount, pagesAfter, freelistRatioBefore: freelist.ratio },
128
+ }, eventsCtx);
129
+ return { ran: true, pagesBefore: freelist.pageCount, pagesAfter };
130
+ }
@@ -1,88 +1,160 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
5
- import { deriveBundleId } from "../core/bundle-id.js";
6
- import { resolveStashDir } from "../core/common.js";
7
- import { mutateConfig } from "../core/config/config.js";
8
- import { bundleSourceId, filesystemBundleSourceId, isBundleEnabled } from "../core/config/config-sources.js";
4
+ /**
5
+ * The host's scheduling choice.
6
+ *
7
+ * `scheduler.enabled` in config.json is the list of fully-qualified refs
8
+ * (`bundle//tasks/x`, `bundle//workflows/y`) this host installs in its native
9
+ * scheduler. Installing a bundle never schedules anything: a task only gets a
10
+ * native row once its ref is on this list (`akm task enable`, `akm setup`),
11
+ * and only bundles that are active on this host are eligible.
12
+ *
13
+ * A config that predates the list (`scheduler.enabled` absent) is read as
14
+ * "keep what is installed": `akm task sync` takes the akm-written rows already
15
+ * in the native scheduler as the host's choice and writes the list once. A
16
+ * 0.9.17-alpha `{kind, ref, sourceId}` entry is read as its `ref`.
17
+ */
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+ import { assetPathForName, stashDirFor } from "../core/asset/asset-placement.js";
21
+ import { bundleRefToString, makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
22
+ import { typeNameFromConceptId } from "../core/asset/resolve-ref.js";
23
+ import { mutateConfig, schedulerSourceIdFor } from "../core/config/config.js";
24
+ import { bundleComponentConfig, bundleContentRoots, isBundleEnabled } from "../core/config/config-sources.js";
9
25
  import { UsageError } from "../core/errors.js";
10
- export function canonicalSchedulerActivationRef(ref) {
26
+ import { warnOnce } from "../core/warn.js";
27
+ export function canonicalSchedulerRef(ref) {
11
28
  const parsed = parseBundleRef(ref);
12
29
  if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== ref) {
13
- throw new UsageError(`Scheduler activation requires a canonical fully-qualified ref, got ${JSON.stringify(ref)}.`, "INVALID_FLAG_VALUE");
30
+ throw new UsageError(`Scheduling requires a canonical fully-qualified ref, got ${JSON.stringify(ref)}.`, "INVALID_FLAG_VALUE");
14
31
  }
15
32
  return ref;
16
33
  }
17
- export function schedulerActivations(config) {
18
- return Object.freeze((config.scheduler?.enabled ?? []).map((activation) => Object.freeze({
19
- kind: activation.kind,
20
- ref: canonicalSchedulerActivationRef(activation.ref),
21
- sourceId: activation.sourceId,
22
- })));
23
- }
24
- export function isSchedulerRefEnabled(config, kind, ref) {
25
- const canonicalRef = canonicalSchedulerActivationRef(ref);
26
- const bundle = parseBundleRef(canonicalRef).bundle;
27
- const currentSourceId = schedulerActivationSourceId(config, bundle);
28
- if (!currentSourceId)
29
- return false;
30
- return schedulerActivations(config).some((activation) => activation.kind === kind && activation.ref === canonicalRef && activation.sourceId === currentSourceId);
34
+ /**
35
+ * Refs this host schedules, or `undefined` when the host has never chosen
36
+ * (the config predates the list). An entry that is not a canonical ref is
37
+ * skipped with a warning, never fatal.
38
+ */
39
+ export function schedulerEnabledRefs(config) {
40
+ const enabled = config.scheduler?.enabled;
41
+ if (enabled === undefined)
42
+ return undefined;
43
+ const refs = new Set();
44
+ for (const ref of enabled) {
45
+ try {
46
+ refs.add(canonicalSchedulerRef(ref));
47
+ }
48
+ catch {
49
+ warnOnce(`scheduler:enabled:${ref}`, `Ignoring scheduler.enabled entry ${JSON.stringify(ref)}: not a canonical ref.`);
50
+ }
51
+ }
52
+ return Object.freeze([...refs]);
31
53
  }
32
- /** Grants that still name the same active source installation they approved. */
33
- export function activeSchedulerActivations(config) {
34
- return schedulerActivations(config).filter((activation) => {
35
- const bundle = parseBundleRef(activation.ref).bundle;
36
- return activation.sourceId === schedulerActivationSourceId(config, bundle);
37
- });
54
+ export function isSchedulerRefEnabled(config, ref) {
55
+ return (schedulerEnabledRefs(config) ?? []).includes(canonicalSchedulerRef(ref));
38
56
  }
39
- /** Pure lifecycle helper used when a bundle is removed or replaced. */
40
57
  export function revokeSchedulerActivationsForBundle(config, bundleId) {
41
- const enabled = schedulerActivations(config).filter((activation) => parseBundleRef(activation.ref).bundle !== bundleId);
42
- if (enabled.length === (config.scheduler?.enabled ?? []).length)
58
+ const current = config.scheduler?.enabled;
59
+ if (current === undefined)
60
+ return config;
61
+ const enabled = current.filter((ref) => bundleOf(ref) !== bundleId);
62
+ if (enabled.length === current.length)
43
63
  return config;
44
64
  return { ...config, scheduler: { ...config.scheduler, enabled } };
45
65
  }
46
- export function setSchedulerRefEnabled(kind, ref, enabled) {
47
- let canonicalRef = ref;
66
+ /** Whether refs from this bundle may be scheduled here: configured and enabled, or the implicit `AKM_BUNDLE_DIR` stash. */
67
+ export function isSchedulerBundleActive(config, bundleId) {
68
+ return schedulerSourceIdFor(config, bundleId) !== undefined;
69
+ }
70
+ /**
71
+ * Add or remove one ref on this host's list. Callers must have made the host's
72
+ * choice explicit first (see `initializeSchedulerChoice` in
73
+ * `commands/tasks/tasks.ts`): on a config without the list this writes just
74
+ * the one ref, which is the whole choice from then on.
75
+ */
76
+ export function setSchedulerRefEnabled(ref, enabled) {
77
+ const canonicalRef = canonicalSchedulerRef(ref);
48
78
  const result = mutateConfig((current) => {
49
- canonicalRef = canonicalSchedulerActivationRef(ref);
50
- const existing = schedulerActivations(current);
51
- const bundle = parseBundleRef(canonicalRef).bundle;
52
- const sourceId = enabled ? schedulerActivationSourceId(current, bundle) : undefined;
53
- if (enabled && !sourceId) {
54
- throw new UsageError(`Cannot enable scheduled execution from inactive or unconfigured bundle ${JSON.stringify(bundle)}.`, "INVALID_FLAG_VALUE");
79
+ const existing = current.scheduler?.enabled ?? [];
80
+ if (enabled && !isSchedulerBundleActive(current, bundleOf(canonicalRef) ?? "")) {
81
+ throw new UsageError(`Cannot enable scheduled execution from inactive or unconfigured bundle ${JSON.stringify(bundleOf(canonicalRef))}.`, "INVALID_FLAG_VALUE");
55
82
  }
56
- const present = existing.some((activation) => activation.kind === kind && activation.ref === canonicalRef && (!enabled || activation.sourceId === sourceId));
57
- if (present === enabled)
83
+ if (current.scheduler?.enabled !== undefined && existing.includes(canonicalRef) === enabled)
58
84
  return current;
59
- const next = enabled
60
- ? [
61
- ...existing.filter((activation) => activation.kind !== kind || activation.ref !== canonicalRef),
62
- { kind, ref: canonicalRef, sourceId: sourceId },
63
- ]
64
- : existing.filter((activation) => activation.kind !== kind || activation.ref !== canonicalRef);
65
- next.sort((left, right) => left.ref.localeCompare(right.ref) || left.kind.localeCompare(right.kind));
66
- return {
67
- ...current,
68
- scheduler: {
69
- ...current.scheduler,
70
- enabled: next,
71
- },
72
- };
85
+ const next = existing.filter((entry) => entry !== canonicalRef);
86
+ if (enabled)
87
+ next.push(canonicalRef);
88
+ next.sort((left, right) => left.localeCompare(right));
89
+ return { ...current, scheduler: { ...current.scheduler, enabled: next } };
73
90
  });
74
91
  return { config: result.config, changed: result.written, ref: canonicalRef };
75
92
  }
76
- /** Current active source identity for a configured or environment-only bundle. */
77
- export function schedulerActivationSourceId(config, bundleId) {
78
- if (isBundleEnabled(config, bundleId))
79
- return bundleSourceId(config, bundleId);
80
- if (config.bundles?.[bundleId] !== undefined || !process.env.AKM_BUNDLE_DIR?.trim())
93
+ /**
94
+ * The refs behind the akm-written rows currently installed in the native
95
+ * scheduler: what this host was running before it had a `scheduler.enabled`
96
+ * list. Rows the backend proves disabled, rows for bundles that are not
97
+ * active here, and rows whose task file is gone are left out.
98
+ */
99
+ export function enabledRefsFromInstalled(installed, config) {
100
+ const refs = new Set();
101
+ for (const entry of installed) {
102
+ const ref = installedEntryRef(entry, config);
103
+ if (ref !== undefined)
104
+ refs.add(ref);
105
+ }
106
+ return Object.freeze([...refs].sort((left, right) => left.localeCompare(right)));
107
+ }
108
+ function installedEntryRef(entry, config) {
109
+ if (entry.enabled === false || !entry.invocation)
110
+ return undefined;
111
+ const invocation = entry.invocation;
112
+ try {
113
+ if (invocation[0] === "workflow" && invocation[1] === "run" && invocation.length === 3 && invocation[2]) {
114
+ const ref = canonicalSchedulerRef(invocation[2]);
115
+ const parsed = parseBundleRef(ref);
116
+ if (!parsed.bundle || !isBundleEnabled(config, parsed.bundle))
117
+ return undefined;
118
+ return hasBackingFile(config, parsed.bundle, parsed.conceptId) ? ref : undefined;
119
+ }
120
+ if (invocation[0] !== "task" || invocation[1] !== "run" || !invocation[2])
121
+ return undefined;
122
+ const bundleIndex = invocation.indexOf("--bundle", 3);
123
+ const bundle = bundleIndex === -1 ? config.defaultBundle : invocation[bundleIndex + 1];
124
+ if (!bundle || !isBundleEnabled(config, bundle))
125
+ return undefined;
126
+ const bundleConfig = config.bundles?.[bundle];
127
+ const adapter = bundleConfig ? (bundleComponentConfig(bundleConfig)?.adapter ?? "akm") : "akm";
128
+ const conceptId = adapter === "akm-task" ? invocation[2] : `tasks/${invocation[2]}`;
129
+ if (!hasBackingFile(config, bundle, conceptId))
130
+ return undefined;
131
+ return canonicalSchedulerRef(makeBundleRef(bundle, conceptId));
132
+ }
133
+ catch {
81
134
  return undefined;
135
+ }
136
+ }
137
+ function hasBackingFile(config, bundle, conceptId) {
138
+ const parts = typeNameFromConceptId(conceptId);
139
+ if (!parts)
140
+ return true;
141
+ const typeDir = stashDirFor(parts.type);
142
+ if (!typeDir)
143
+ return true;
144
+ const contentRoot = bundleContentRoots(config).find((entry) => entry.id === bundle)?.contentRoot;
145
+ if (!contentRoot)
146
+ return true;
147
+ try {
148
+ const target = assetPathForName(parts.type, path.join(contentRoot, typeDir), parts.name);
149
+ return fs.existsSync(target) && fs.statSync(target).isFile();
150
+ }
151
+ catch {
152
+ return false;
153
+ }
154
+ }
155
+ function bundleOf(ref) {
82
156
  try {
83
- const root = resolveStashDir();
84
- const implicitId = deriveBundleId(undefined, root, new Set(Object.keys(config.bundles ?? {})));
85
- return implicitId === bundleId ? filesystemBundleSourceId(root) : undefined;
157
+ return parseBundleRef(ref).bundle;
86
158
  }
87
159
  catch {
88
160
  return undefined;