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
@@ -1,405 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /**
5
- * The ONE durable filesystem-transaction engine (plan §2.2 / §4.5, Chunk 6
6
- * WI-6.3). Replaces the three (+1) per-domain journal engines — proposal
7
- * accept/revert, proposal reject, mv, consolidate — with a single journal
8
- * home, journal format, fsync discipline, phase runner, and recovery scanner.
9
- *
10
- * ## Journal home
11
- *
12
- * `getDataDir()/txn/<rootNs24>/<transactionId>/journal.json`, namespaced by
13
- * the sha256 of the resolved root the transaction mutates. Every legacy home
14
- * (`proposal-transactions/`, `proposal-rejections/`, in-stash
15
- * `.akm/mv-transactions/`, in-stash `consolidate-journal.json`) collapses
16
- * onto this one.
17
- *
18
- * ## Phase model
19
- *
20
- * Each transaction KIND declares its ordered phase vocabulary and a commit
21
- * point. A journal found at a phase strictly BEFORE the commit point rolls
22
- * BACK (the batch never happened); at or after it rolls FORWARD (the kind's
23
- * `finalize` resumes idempotently from the recorded phase). Phase writes are
24
- * durable: tmp + fsync + rename + parent-dir fsync — the exact discipline
25
- * every legacy engine used, now in one place.
26
- *
27
- * ## Kind handlers
28
- *
29
- * Domain logic (what the files are, how to roll back, which DB/index/event
30
- * steps finalize) stays with the domain: each kind registers a
31
- * {@link TxnKindHandler}. The engine owns discovery, safety fencing, journal
32
- * I/O, ordering, and cleanup. Recovery entry points call
33
- * {@link recoverTxnsForRoot} (after importing the domain registrar so the
34
- * kinds are registered).
35
- *
36
- * ## Crash-window test seam
37
- *
38
- * `_setTxnMutationHookForTests` replaces the per-engine hooks; domain code
39
- * fires named points through {@link txnMutationHook} exactly where the legacy
40
- * engines fired theirs, so the subprocess crash runners re-key mechanically.
41
- */
42
- import { createHash, randomUUID } from "node:crypto";
43
- import fs from "node:fs";
44
- import path from "node:path";
45
- import { getDataDir } from "./paths.js";
46
- import { warn } from "./warn.js";
47
- // ── Registry ─────────────────────────────────────────────────────────────────
48
- const kinds = new Map();
49
- /**
50
- * Register (or replace) the handler for a transaction kind. Handlers are
51
- * stored payload-erased; beginTxn/recovery re-associate `P` via the kind tag.
52
- */
53
- export function registerTxnKind(kind, handler) {
54
- kinds.set(kind, handler);
55
- }
56
- function requireKind(kind) {
57
- const handler = kinds.get(kind);
58
- if (!handler)
59
- throw new Error(`No transaction handler registered for kind "${kind}".`);
60
- return handler;
61
- }
62
- /** True when `kind` has a registered handler (see {@link recoverTxnsForRoot}). */
63
- function hasKind(kind) {
64
- return kinds.has(kind);
65
- }
66
- // ── Test seam ────────────────────────────────────────────────────────────────
67
- let mutationHookForTests;
68
- /** TEST-ONLY crash-window hook used by subprocess recovery tests. */
69
- export function _setTxnMutationHookForTests(hook) {
70
- mutationHookForTests = hook;
71
- }
72
- /** Fire a named crash-window point (no-op outside tests). */
73
- export function txnMutationHook(point) {
74
- mutationHookForTests?.(point);
75
- }
76
- // ── Durable file I/O primitives (shared fsync discipline) ────────────────────
77
- export function txnHash(content) {
78
- return createHash("sha256").update(content).digest("hex");
79
- }
80
- export function txnFileHash(filePath) {
81
- return txnHash(fs.readFileSync(filePath));
82
- }
83
- export function fsyncTxnFile(filePath) {
84
- // Open for WRITE. Windows implements fsync as FlushFileBuffers, which
85
- // requires write access on the handle — a read-only descriptor fails with
86
- // EACCES/EPERM, so every proposal accept and reject failed on that platform.
87
- // POSIX accepts "r+" here just as readily as "r".
88
- const fd = fs.openSync(filePath, "r+");
89
- try {
90
- fs.fsyncSync(fd);
91
- }
92
- finally {
93
- fs.closeSync(fd);
94
- }
95
- }
96
- export function fsyncTxnDir(dirPath) {
97
- try {
98
- // Read-only, unlike {@link fsyncTxnFile}: a directory cannot be opened for
99
- // write on POSIX (EISDIR), and on Windows this whole operation is
100
- // unsupported anyway and falls into the catch.
101
- const fd = fs.openSync(dirPath, "r");
102
- try {
103
- fs.fsyncSync(fd);
104
- }
105
- finally {
106
- fs.closeSync(fd);
107
- }
108
- }
109
- catch {
110
- // Directory fsync is unavailable on some platforms.
111
- }
112
- }
113
- /** Durably write `content` to `filePath` (tmp + fsync + rename + dir fsync). */
114
- export function writeTxnFileDurably(filePath, content, mode = 0o600) {
115
- const tempPath = `${filePath}.tmp`;
116
- fs.writeFileSync(tempPath, content, { mode });
117
- fsyncTxnFile(tempPath);
118
- fs.renameSync(tempPath, filePath);
119
- fsyncTxnDir(path.dirname(filePath));
120
- }
121
- // ── Journal home / discovery ─────────────────────────────────────────────────
122
- /**
123
- * Canonical spelling of a transaction root: realpath when the root exists
124
- * (so symlinked spellings — e.g. a stash reached through macOS /tmp — hash
125
- * to the SAME namespace and bind-compare equal), resolved otherwise.
126
- */
127
- export function canonicalTxnRoot(root) {
128
- try {
129
- return fs.realpathSync(path.resolve(root));
130
- }
131
- catch {
132
- return path.resolve(root);
133
- }
134
- }
135
- /** Namespace directory for all transactions mutating `root`. */
136
- export function txnNamespaceDir(root) {
137
- const ns = txnHash(canonicalTxnRoot(root)).slice(0, 24);
138
- return path.join(getDataDir(), "txn", ns);
139
- }
140
- /** Mint a transaction id ahead of {@link beginTxn} (see its `transactionId`). */
141
- export function mintTxnId() {
142
- return randomUUID();
143
- }
144
- /** The directory a transaction with `transactionId` on `root` will own. */
145
- export function txnDirFor(root, transactionId) {
146
- return path.join(txnNamespaceDir(root), transactionId);
147
- }
148
- function writeJournal(txn) {
149
- writeTxnFileDurably(txn.journalPath, `${JSON.stringify(txn.journal, null, 2)}\n`);
150
- }
151
- /** Durably record `phase` on the journal, then mirror it in memory. */
152
- export function advanceTxn(txn, phase) {
153
- const handler = requireKind(txn.journal.kind);
154
- if (!handler.phases.includes(phase)) {
155
- throw new Error(`Unknown phase "${phase}" for transaction kind "${txn.journal.kind}".`);
156
- }
157
- const next = { ...txn.journal, phase };
158
- writeTxnFileDurably(txn.journalPath, `${JSON.stringify(next, null, 2)}\n`);
159
- txn.journal.phase = phase;
160
- }
161
- /**
162
- * Open a new transaction: mint the id, create its directory, and durably
163
- * write the journal at the kind's initial phase. The caller stages sidecar
164
- * files under `txn.dir` and then applies/finalizes through the kind handler.
165
- */
166
- export function beginTxn(args) {
167
- const handler = requireKind(args.kind);
168
- const transactionId = args.transactionId ?? randomUUID();
169
- if (!/^[A-Za-z0-9._-]+$/.test(transactionId) || transactionId === "." || transactionId === "..") {
170
- throw new Error(`Invalid transaction id "${transactionId}" — must be a plain path segment.`);
171
- }
172
- const dir = path.join(txnNamespaceDir(args.root), transactionId);
173
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
174
- const journal = {
175
- version: 1,
176
- kind: args.kind,
177
- phase: handler.phases[0],
178
- transactionId,
179
- root: canonicalTxnRoot(args.root),
180
- changes: args.changes,
181
- decidedAt: args.decidedAt ?? new Date().toISOString(),
182
- payload: args.payload,
183
- };
184
- const txn = { journal, journalPath: path.join(dir, "journal.json"), dir };
185
- writeJournal(txn);
186
- return txn;
187
- }
188
- /** Remove a transaction directory (and its namespace dir when empty). */
189
- export function cleanupTxn(dir) {
190
- try {
191
- fs.rmSync(dir, { recursive: true, force: true });
192
- try {
193
- fs.rmdirSync(path.dirname(dir));
194
- }
195
- catch {
196
- // Other transactions may still exist in the namespace.
197
- }
198
- return null;
199
- }
200
- catch (error) {
201
- const message = `transaction committed but cleanup failed at ${dir}: ${error instanceof Error ? error.message : String(error)}`;
202
- warn(`[txn] ${message}`);
203
- return message;
204
- }
205
- }
206
- /**
207
- * Grace period (ms) before a transaction directory/journal with no other
208
- * evidence of activity is treated as stale rather than possibly belonging to
209
- * a still-running operation. Shared by {@link sweepJournallessTxnDir} (racing
210
- * a sibling's mkdir→journal window, and the unknown-kind sweep in
211
- * {@link recoverTxnsForRoot}) and by read-only reporting such as `akm
212
- * health`'s stale-journal advisory (see {@link listTxnJournalsTolerant}).
213
- */
214
- export const TXN_SWEEP_GRACE_MS = 300_000;
215
- /**
216
- * Sweep a transaction directory that cannot be recovered — it has NO journal,
217
- * or (from {@link recoverTxnsForRoot}) a journal whose kind has no registered
218
- * handler — but only when it is demonstrably stale. All kinds share one
219
- * namespace per root, so a scanner may encounter a SIBLING transaction inside
220
- * `beginTxn`'s mkdir→journal window, or one whose registrar this process
221
- * simply has not imported yet; a grace period keeps the sweep from racing
222
- * either. Returns true when the directory was removed.
223
- */
224
- export function sweepJournallessTxnDir(dir, graceMs = TXN_SWEEP_GRACE_MS) {
225
- try {
226
- const age = Date.now() - fs.statSync(dir).mtimeMs;
227
- if (age < graceMs)
228
- return false;
229
- fs.rmSync(dir, { recursive: true, force: true });
230
- return true;
231
- }
232
- catch {
233
- return false;
234
- }
235
- }
236
- /** True when `candidate` is inside `root` (both resolved). */
237
- export function isWithinTxnRoot(candidate, root) {
238
- const rel = path.relative(path.resolve(root), path.resolve(candidate));
239
- return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
240
- }
241
- function readJournal(journalPath) {
242
- let journal;
243
- try {
244
- journal = JSON.parse(fs.readFileSync(journalPath, "utf8"));
245
- }
246
- catch (error) {
247
- throw new Error(`Cannot read transaction journal at ${journalPath}: ${error instanceof Error ? error.message : String(error)}`);
248
- }
249
- if (journal.version !== 1 || typeof journal.kind !== "string" || typeof journal.phase !== "string") {
250
- throw new Error(`Refusing unsafe transaction journal at ${journalPath}.`);
251
- }
252
- return journal;
253
- }
254
- /** Engine-level safety fences shared by every kind. */
255
- function fenceJournal(journal, txnDir, root, journalPath) {
256
- if (canonicalTxnRoot(journal.root) !== canonicalTxnRoot(root)) {
257
- throw new Error(`Refusing transaction journal bound to a different root at ${journalPath}.`);
258
- }
259
- const handler = requireKind(journal.kind);
260
- if (!handler.phases.includes(journal.phase)) {
261
- throw new Error(`Refusing transaction journal with unknown phase "${journal.phase}" at ${journalPath}.`);
262
- }
263
- for (const change of journal.changes) {
264
- if (typeof change.path !== "string" || !isWithinTxnRoot(change.path, root)) {
265
- throw new Error(`Refusing transaction journal touching paths outside its root at ${journalPath}.`);
266
- }
267
- }
268
- handler.validate?.(journal, txnDir, root);
269
- }
270
- /** True when `journal.phase` is at or after the kind's commit point. */
271
- export function isCommittedPhase(journal) {
272
- const handler = requireKind(journal.kind);
273
- return handler.phases.indexOf(journal.phase) >= handler.phases.indexOf(handler.commitPhase);
274
- }
275
- /**
276
- * Recover every interrupted transaction under `root`'s namespace: journals
277
- * before their kind's commit point roll BACK; the rest roll FORWARD through
278
- * the kind's `finalize`. Fully-finalized directories are swept. The domain
279
- * registrar (which registers the kinds) must be imported by the caller.
280
- *
281
- * A journal whose `kind` has NO registered handler is SWEPT (same stale-dir
282
- * grace period as {@link sweepJournallessTxnDir}) rather than thrown on. A
283
- * kind can disappear for good — 0.9.0 deleted `akm mv` and with it the
284
- * `kind:"mv"` handler — and an unrecoverable leftover journal must never
285
- * brick every later recovery scan (index refresh, proposal accept/reject) run
286
- * against the same root. Kinds that ARE registered keep failing LOUDLY on any
287
- * fence violation: those journals may fence an interrupted, irreversible
288
- * mutation. The grace period also covers the transient case where the caller
289
- * has not imported a live kind's registrar yet.
290
- *
291
- * `filter` optionally narrows recovery (e.g. one kind, one proposal id). The
292
- * unknown-kind sweep runs BEFORE the filter: such a journal is garbage no
293
- * matter what the caller asked to recover, and leaving it behind is what
294
- * bricks the next scan.
295
- */
296
- export async function recoverTxnsForRoot(root, filter) {
297
- const nsDir = txnNamespaceDir(root);
298
- const recovered = [];
299
- if (!fs.existsSync(nsDir))
300
- return recovered;
301
- for (const entry of fs.readdirSync(nsDir, { withFileTypes: true })) {
302
- if (!entry.isDirectory())
303
- continue;
304
- const dir = path.join(nsDir, entry.name);
305
- const journalPath = path.join(dir, "journal.json");
306
- if (!fs.existsSync(journalPath)) {
307
- sweepJournallessTxnDir(dir);
308
- continue;
309
- }
310
- const journal = readJournal(journalPath);
311
- if (!hasKind(journal.kind)) {
312
- if (sweepJournallessTxnDir(dir)) {
313
- warn(`[txn] swept unrecoverable journal of unregistered kind "${journal.kind}" at ${journalPath}.`);
314
- }
315
- continue;
316
- }
317
- if (filter && !filter(journal))
318
- continue;
319
- fenceJournal(journal, dir, root, journalPath);
320
- const handler = requireKind(journal.kind);
321
- const txn = { journal, journalPath, dir };
322
- const terminal = handler.phases[handler.phases.length - 1];
323
- if (!isCommittedPhase(journal)) {
324
- await handler.rollback(txn);
325
- }
326
- else if (journal.phase !== terminal) {
327
- await handler.finalize(txn);
328
- }
329
- recovered.push(journal);
330
- cleanupTxn(dir);
331
- }
332
- return recovered;
333
- }
334
- /**
335
- * Enumerate (without recovering) every journal across ALL namespaces that
336
- * matches `predicate`. Used by stash-scoped recovery entry points that don't
337
- * know which roots their interrupted transactions were bound to.
338
- */
339
- export function listTxnJournals(predicate) {
340
- const home = path.join(getDataDir(), "txn");
341
- const matches = [];
342
- if (!fs.existsSync(home))
343
- return matches;
344
- for (const ns of fs.readdirSync(home, { withFileTypes: true })) {
345
- if (!ns.isDirectory())
346
- continue;
347
- const nsDir = path.join(home, ns.name);
348
- for (const entry of fs.readdirSync(nsDir, { withFileTypes: true })) {
349
- if (!entry.isDirectory())
350
- continue;
351
- const journalPath = path.join(nsDir, entry.name, "journal.json");
352
- if (!fs.existsSync(journalPath))
353
- continue;
354
- // Unreadable/invalid journals fail LOUDLY: a caller deciding what to
355
- // recover must never silently overlook a damaged journal (it may fence
356
- // an interrupted, irreversible mutation).
357
- const journal = readJournal(journalPath);
358
- if (predicate(journal))
359
- matches.push(journal);
360
- }
361
- }
362
- return matches;
363
- }
364
- /**
365
- * Read-only, best-effort sibling of {@link listTxnJournals} for reporting
366
- * (e.g. `akm health`'s stale-journal advisory): a corrupt `journal.json` is
367
- * counted rather than thrown, so one damaged journal doesn't abort the whole
368
- * scan. Recovery call sites (which must decide how to roll a journal forward
369
- * or back) keep using {@link listTxnJournals} — it fails loudly on purpose,
370
- * since silently skipping a damaged journal there could leave an interrupted,
371
- * irreversible mutation unrecovered.
372
- */
373
- export function listTxnJournalsTolerant(predicate) {
374
- const home = path.join(getDataDir(), "txn");
375
- const matches = [];
376
- const unreadableMtimes = [];
377
- if (!fs.existsSync(home))
378
- return { matches, unreadableMtimes };
379
- for (const ns of fs.readdirSync(home, { withFileTypes: true })) {
380
- if (!ns.isDirectory())
381
- continue;
382
- const nsDir = path.join(home, ns.name);
383
- for (const entry of fs.readdirSync(nsDir, { withFileTypes: true })) {
384
- if (!entry.isDirectory())
385
- continue;
386
- const journalPath = path.join(nsDir, entry.name, "journal.json");
387
- let mtimeMs;
388
- try {
389
- mtimeMs = fs.statSync(journalPath).mtimeMs;
390
- }
391
- catch {
392
- continue; // no journal.json here, or it vanished mid-scan
393
- }
394
- try {
395
- const journal = readJournal(journalPath);
396
- if (predicate(journal))
397
- matches.push({ journal, mtimeMs });
398
- }
399
- catch {
400
- unreadableMtimes.push(mtimeMs);
401
- }
402
- }
403
- }
404
- return { matches, unreadableMtimes };
405
- }
@@ -1,25 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /** Dependency-safe fixed FTS5 calibration shared by storage and ranking. */
5
- const SCORE_FLOOR = 0.3;
6
- const PARENT_CEILING = 0.8;
7
- // Fragment BM25 is from a distinct FTS population. Its separately calibrated
8
- // ceiling is an explicit evidence policy, not a cross-table comparability claim:
9
- // when a body fragment independently proves the query, retain that actionable
10
- // selector beside the same parent's length-penalized whole-body row. Metadata
11
- // and cross-fragment conjunctions cannot enter this population.
12
- const FRAGMENT_CEILING = 0.8;
13
- const BM25_REFERENCE = 0.000001;
14
- const LOG_SHAPE = 3;
15
- export function stableFtsScore(bm25Score, population = "parent") {
16
- const ceiling = population === "fragment" ? FRAGMENT_CEILING : PARENT_CEILING;
17
- if (bm25Score === Number.NEGATIVE_INFINITY)
18
- return ceiling;
19
- if (!Number.isFinite(bm25Score) || bm25Score >= 0)
20
- return SCORE_FLOOR;
21
- const scaled = Math.log1p(-bm25Score / BM25_REFERENCE);
22
- if (!Number.isFinite(scaled))
23
- return ceiling;
24
- return SCORE_FLOOR + (ceiling - SCORE_FLOOR) * (scaled / (scaled + LOG_SHAPE));
25
- }
@@ -1,167 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { AsyncLocalStorage } from "node:async_hooks";
5
- import { randomUUID } from "node:crypto";
6
- import fs from "node:fs";
7
- import path from "node:path";
8
- import { sleepSync } from "../runtime.js";
9
- import { backoffDelay } from "./common.js";
10
- import { ConfigError, TransientError } from "./errors.js";
11
- import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "./file-lock.js";
12
- import { getMaintenanceBarrierPath } from "./paths.js";
13
- const heldBarrierContext = new AsyncLocalStorage();
14
- /**
15
- * The barrier is meant to be held only for the short critical section that
16
- * registers one lock/lease/activity — a process that still holds it past
17
- * this age is wedged (crashed mid-section, deadlocked, killed without
18
- * cleanup), not doing legitimate long-running work. Without an age bound, a
19
- * probe only reclaims a lock whose holder PID has verifiably died; a wedged
20
- * — but still-alive — holder (or a PID a container/namespace boundary
21
- * reused, making `isProcessAlive` see the wrong process as live) locked
22
- * every other akm invocation out of ANY maintenance registration forever,
23
- * with no recovery but killing the holder by hand. 5 minutes matches the
24
- * stale-lock window already used for the improve extract-session lock
25
- * (`commands/improve/extract.ts`).
26
- */
27
- const MAINTENANCE_BARRIER_STALE_AFTER_MS = 5 * 60 * 1000;
28
- /**
29
- * The barrier normally holds for one lock-file write — sub-millisecond on
30
- * any real filesystem. Two akm processes racing to register a lock in the
31
- * very same instant (e.g. two `akm index` runs a scheduler launched back to
32
- * back) can still collide on it; retrying briefly resolves that ordinary
33
- * case instead of failing a legitimate concurrent invocation outright
34
- * (field follow-up to #956, G1). Five seconds matches the async and
35
- * synchronous-activity registration paths below and tolerates a holder being
36
- * descheduled under heavy process-shard load; the barrier's normal hold time
37
- * remains sub-millisecond. The bound still surfaces a genuinely wedged holder
38
- * promptly and never applies to the rebuild lock itself, which stays
39
- * non-blocking (#872).
40
- */
41
- const MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS = 5_000;
42
- let busyRetryBoundMsForTests;
43
- /** Test-only override for {@link MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS}, so a unit test can exercise the
44
- * exhausted-retry throw without a real ~1.5s wait. Restored via tests/_helpers/seams.ts's resetAllSeams(). */
45
- export function _setMaintenanceBarrierBusyRetryBoundMsForTests(ms) {
46
- busyRetryBoundMsForTests = ms;
47
- }
48
- /**
49
- * Serialize the short critical section that creates each long-lived AKM lock,
50
- * lease, or state activity. The operation keeps its own ownership record; this
51
- * barrier is released immediately after acquisition.
52
- */
53
- export function tryAcquireMaintenanceBarrier() {
54
- const lockPath = getMaintenanceBarrierPath();
55
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
56
- for (let attempt = 0; attempt < 2; attempt += 1) {
57
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: "maintenance-start" }));
58
- if (ownership) {
59
- return () => releaseLock(ownership);
60
- }
61
- const probe = probeLock(lockPath, { staleAfterMs: MAINTENANCE_BARRIER_STALE_AFTER_MS });
62
- if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
63
- return undefined;
64
- }
65
- return undefined;
66
- }
67
- export function acquireMaintenanceBarrier() {
68
- const boundMs = busyRetryBoundMsForTests ?? MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS;
69
- const deadline = Date.now() + boundMs;
70
- for (let attempt = 0;; attempt += 1) {
71
- const release = tryAcquireMaintenanceBarrier();
72
- if (release)
73
- return release;
74
- const remainingMs = deadline - Date.now();
75
- if (remainingMs <= 0)
76
- break;
77
- sleepSync(Math.min(backoffDelay(attempt), remainingMs));
78
- }
79
- throw new TransientError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry shortly. ` +
80
- `A sentinel older than ${MAINTENANCE_BARRIER_STALE_AFTER_MS / 60_000} minute(s) is reclaimed automatically on the next attempt.`, "MAINTENANCE_BARRIER_BUSY");
81
- }
82
- export function withMaintenanceStartBarrier(run) {
83
- if (heldBarrierContext.getStore()?.active)
84
- return run();
85
- const release = acquireMaintenanceBarrier();
86
- const ownership = { active: true };
87
- try {
88
- return heldBarrierContext.run(ownership, run);
89
- }
90
- finally {
91
- ownership.active = false;
92
- release();
93
- }
94
- }
95
- /** Run while holding the start barrier, or return undefined when it is busy. */
96
- export function tryWithMaintenanceStartBarrier(run) {
97
- if (heldBarrierContext.getStore()?.active)
98
- return run();
99
- const release = tryAcquireMaintenanceBarrier();
100
- if (!release)
101
- return undefined;
102
- const ownership = { active: true };
103
- try {
104
- return heldBarrierContext.run(ownership, run);
105
- }
106
- finally {
107
- ownership.active = false;
108
- release();
109
- }
110
- }
111
- async function acquireMaintenanceBarrierAsync() {
112
- const deadline = Date.now() + 5_000;
113
- while (true) {
114
- const release = tryAcquireMaintenanceBarrier();
115
- if (release)
116
- return release;
117
- if (Date.now() >= deadline)
118
- return acquireMaintenanceBarrier();
119
- await new Promise((resolve) => setTimeout(resolve, 5));
120
- }
121
- }
122
- export async function withMaintenanceStartBarrierAsync(run) {
123
- if (heldBarrierContext.getStore()?.active)
124
- return run();
125
- const release = await acquireMaintenanceBarrierAsync();
126
- const ownership = { active: true };
127
- try {
128
- return await heldBarrierContext.run(ownership, run);
129
- }
130
- finally {
131
- ownership.active = false;
132
- release();
133
- }
134
- }
135
- function withMaintenanceStartBarrierSyncWait(run) {
136
- if (heldBarrierContext.getStore()?.active)
137
- return run();
138
- const deadline = Date.now() + 5_000;
139
- let release = tryAcquireMaintenanceBarrier();
140
- while (!release && Date.now() < deadline) {
141
- sleepSync(5);
142
- release = tryAcquireMaintenanceBarrier();
143
- }
144
- if (!release)
145
- release = acquireMaintenanceBarrier();
146
- const ownership = { active: true };
147
- try {
148
- return heldBarrierContext.run(ownership, run);
149
- }
150
- finally {
151
- ownership.active = false;
152
- release();
153
- }
154
- }
155
- /** Synchronous activity registration for synchronous database handle lifetimes. */
156
- export function acquireMaintenanceActivitySync(name) {
157
- return withMaintenanceStartBarrierSyncWait(() => {
158
- const directory = path.join(path.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
159
- fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
160
- const lockPath = path.join(directory, `${name}-${process.pid}-${randomUUID()}.lock`);
161
- const ownership = tryAcquireLockSync(lockPath, createLockPayload({ purpose: name }));
162
- if (!ownership) {
163
- throw new ConfigError(`Could not register AKM maintenance activity at ${lockPath}.`, "INVALID_CONFIG_FILE");
164
- }
165
- return () => releaseLock(ownership);
166
- });
167
- }