akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +731 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +361 -751
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -441
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -5,18 +5,18 @@ import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { isDeepStrictEqual } from "node:util";
8
- import { isBundleSlug } from "../asset/asset-ref.js";
9
- import { isRecord } from "../common.js";
8
+ import { isBundleSlug, parseBundleRef } from "../asset/asset-ref.js";
9
+ import { deriveBundleId } from "../bundle-id.js";
10
+ import { isRecord, resolveStashDir } from "../common.js";
10
11
  import { ConfigError } from "../errors.js";
11
12
  import { liftLegacyEngineExtraParams } from "../extra-params.js";
12
13
  import { formatRegistryLabel, hasRegistryUrlCredentials } from "../registry-url.js";
13
14
  import { acquireConfigLock, backupExistingConfig, parseConfigText, readConfigText, withConfigLock, writeConfigAtomic, } from "./config-io.js";
14
- import { AkmConfigSchema, CURRENT_CONFIG_VERSION, listTopLevelConfigKeys } from "./config-schema.js";
15
- import { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundlesToSourceEntries } from "./config-sources.js";
16
- import { upgradeConfigVersion } from "./config-version-shim.js";
15
+ import { AkmConfigSchema, CURRENT_CONFIG_VERSION } from "./config-schema.js";
16
+ import { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundleSourceId, bundlesToSourceEntries, filesystemBundleSourceId, isBundleEnabled, } from "./config-sources.js";
17
+ import { resolveSchemaAt } from "./config-walker.js";
17
18
  import { deepMergeConfig, isPlainObject } from "./deep-merge.js";
18
19
  import { migrateLegacySourceShape } from "./legacy-source-shape-shim.js";
19
- import { stripRetiredExperimentalKeys } from "./retired-experimental-keys-shim.js";
20
20
  import { isApiKeyReference, SECRET_STORE_REFERENCE_PATTERN } from "./schema/primitives.js";
21
21
  export { stripJsonComments } from "./config-io.js";
22
22
  import { getConfigPath } from "../paths.js";
@@ -125,40 +125,38 @@ export function loadUserConfig() {
125
125
  };
126
126
  return finalConfig;
127
127
  }
128
- /**
129
- * Acquire the existing config-write sentinel and read a fresh validated
130
- * generation while keeping the sentinel held. Source update uses this to
131
- * fence an audited bundle descriptor through publication: a cooperating
132
- * config writer can commit either before this snapshot or after the update,
133
- * never between the final generation check and index commit.
134
- */
135
- export function acquireConfigReadFence() {
136
- const release = acquireConfigLock();
137
- try {
138
- cachedConfig = undefined;
139
- return { config: loadUserConfig(), release };
140
- }
141
- catch (error) {
142
- release();
143
- throw error;
144
- }
145
- }
146
128
  /**
147
129
  * Run the per-file config pipeline every raw config object goes through
148
130
  * before it is either validated (the local/top-level file) or merged in as
149
- * an `extends` base: JSONC parse already done by the caller, then version
150
- * shim, then legacy `stashDir`/`sources[]`/`installed[]` shim, then the
151
- * legacy `extraParams` lift (#852), then the retired `experimental.*` key
152
- * shim. Shared by {@link parseAndValidateConfigText}
153
- * (the local file) and {@link resolveExtendsChain} (each base in the chain) so
154
- * a fleet-shared base config can carry its own old `configVersion` / legacy
155
- * shape independently of the file that extends it.
131
+ * an `extends` base: JSONC parse already done by the caller, then the
132
+ * `configVersion` read ({@link readConfigVersion}), then the legacy
133
+ * `stashDir`/`sources[]`/`installed[]` shim, then the legacy `extraParams`
134
+ * lift (#852). Unknown keys are not an error: the schema drops them in
135
+ * memory. Shared by {@link parseAndValidateConfigText} (the local file) and
136
+ * {@link resolveExtendsChain} (each base in the chain) so a fleet-shared base
137
+ * config can carry its own `configVersion` / legacy shape independently of
138
+ * the file that extends it.
156
139
  */
157
140
  function runConfigFilePipeline(text, sourcePath) {
158
- const versioned = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
141
+ const versioned = readConfigVersion(parseConfigText(text, sourcePath), sourcePath);
159
142
  const parsedRaw = migrateLegacySourceShape(versioned, sourcePath);
160
- const liftedRaw = liftExtraParamsOrThrow(parsedRaw, sourcePath);
161
- return stripRetiredExperimentalKeys(liftedRaw, sourcePath);
143
+ return liftExtraParamsOrThrow(parsedRaw, sourcePath);
144
+ }
145
+ /**
146
+ * `configVersion` is read, never gated on. `"0.9.0"` is the only value akm
147
+ * has ever shipped, and a document without the field is that same document.
148
+ * Any other value is named once and the file is read as the current shape
149
+ * anyway; every ordinary config write and `akm migrate apply`'s `configFile`
150
+ * step then persist `"0.9.0"`.
151
+ */
152
+ function readConfigVersion(raw, sourcePath) {
153
+ const version = raw.configVersion;
154
+ if (version === CURRENT_CONFIG_VERSION)
155
+ return raw;
156
+ if (version !== undefined) {
157
+ warnOnce(`config:config-version${sourcePath ? `:${sourcePath}` : ""}`, `${sourcePath ?? "config.json"} declares configVersion ${JSON.stringify(version)}; this release reads it as ${CURRENT_CONFIG_VERSION}.`);
158
+ }
159
+ return { ...raw, configVersion: CURRENT_CONFIG_VERSION };
162
160
  }
163
161
  /**
164
162
  * #852 (following #815): a config still using legacy `extraParams` keys —
@@ -195,49 +193,62 @@ function liftExtraParamsOrThrow(parsedRaw, sourcePath) {
195
193
  * keeps around, instead of only getting the final merged `AkmConfig` back.
196
194
  */
197
195
  function buildEffectiveConfig(liftedLocalRaw, sourcePath) {
198
- warnUnknownTopLevelConfigKeys(liftedLocalRaw, sourcePath);
199
196
  const withExtends = resolveExtendsChain(liftedLocalRaw, sourcePath);
200
197
  const where = sourcePath ? ` at ${sourcePath}` : "";
198
+ warnUnknownConfigKeys(liftedLocalRaw, sourcePath);
201
199
  const parsed = AkmConfigSchema.safeParse(withExtends);
202
200
  if (!parsed.success) {
203
201
  const lines = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
204
- const needsSchedulerMigration = parsed.error.issues.some((issue) => issue.path[0] === "scheduler" && issue.path.at(-1) === "sourceId");
205
- throw new ConfigError(`Invalid config${where}:\n${lines}`, "INVALID_CONFIG_FILE", needsSchedulerMigration
206
- ? "Run `akm migrate apply` to bind existing scheduler grants to their source."
207
- : undefined);
202
+ throw new ConfigError(`Invalid config${where}:\n${lines}`, "INVALID_CONFIG_FILE");
208
203
  }
209
204
  const merged = deepMergeConfig(DEFAULT_CONFIG, parsed.data);
210
205
  const finalResult = AkmConfigSchema.safeParse(merged);
211
206
  if (!finalResult.success) {
212
207
  const lines = finalResult.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
213
- const needsSchedulerMigration = finalResult.error.issues.some((issue) => issue.path[0] === "scheduler" && issue.path.at(-1) === "sourceId");
214
- throw new ConfigError(`Invalid merged config${sourcePath ? ` at ${sourcePath}` : ""}:\n${lines}`, "INVALID_CONFIG_FILE", needsSchedulerMigration
215
- ? "Run `akm migrate apply` to bind existing scheduler grants to their source."
216
- : undefined);
208
+ throw new ConfigError(`Invalid merged config${sourcePath ? ` at ${sourcePath}` : ""}:\n${lines}`, "INVALID_CONFIG_FILE");
217
209
  }
218
210
  assertUniquePhysicalBundleRoots(finalResult.data, sourcePath);
219
211
  return finalResult.data;
220
212
  }
221
- const RETIRED_TOP_LEVEL_CONFIG_KEYS = new Set([
222
- "agent",
223
- "bindings",
224
- "features",
225
- "installed",
226
- "llm",
227
- "modelAliases",
228
- "profiles",
229
- "sources",
230
- "stashDir",
231
- "stashes",
232
- "writable",
233
- ]);
234
- function warnUnknownTopLevelConfigKeys(raw, sourcePath) {
235
- const known = new Set(listTopLevelConfigKeys());
236
- for (const key of Object.keys(raw).sort()) {
237
- if (known.has(key) || RETIRED_TOP_LEVEL_CONFIG_KEYS.has(key))
213
+ /**
214
+ * Every dotted key in `raw` the schema does not know, at any depth (arrays
215
+ * are not descended). Unknown keys are never an error: they are a typo, or a
216
+ * key another release used. Reads keep them (they round-trip through
217
+ * ordinary writes, so a newer release's settings survive a downgrade);
218
+ * `akm migrate apply` drops them.
219
+ */
220
+ export function unknownConfigKeyPaths(root, node = root, prefix = []) {
221
+ const found = [];
222
+ for (const key of Object.keys(node).sort()) {
223
+ const keyPath = [...prefix, key];
224
+ if (resolveSchemaAt(keyPath, root) === undefined) {
225
+ found.push(keyPath);
238
226
  continue;
239
- warnOnce(`config:unknown-key:${sourcePath ?? "inline"}:${key}`, `Unknown config key ${JSON.stringify(key)}${sourcePath ? ` at ${sourcePath}` : ""} has no defined akm behavior. Check the spelling or remove it.`);
227
+ }
228
+ const value = node[key];
229
+ if (isPlainConfigObject(value))
230
+ found.push(...unknownConfigKeyPaths(root, value, keyPath));
231
+ }
232
+ return found;
233
+ }
234
+ function warnUnknownConfigKeys(raw, sourcePath) {
235
+ for (const keyPath of unknownConfigKeyPaths(raw)) {
236
+ const dotted = keyPath.join(".");
237
+ warnOnce(`config:unknown-key:${sourcePath ?? "inline"}:${dotted}`, `Unknown config key ${JSON.stringify(dotted)}${sourcePath ? ` at ${sourcePath}` : ""} has no defined akm behavior and is ignored. Check the spelling; a key retired by this release or added by a newer one is dropped by \`akm migrate apply\`.`);
238
+ }
239
+ }
240
+ function deleteConfigPath(node, keyPath) {
241
+ let cursor = node;
242
+ for (const segment of keyPath.slice(0, -1)) {
243
+ if (!isPlainConfigObject(cursor))
244
+ return;
245
+ cursor = cursor[segment];
240
246
  }
247
+ if (isPlainConfigObject(cursor))
248
+ delete cursor[keyPath[keyPath.length - 1]];
249
+ }
250
+ function isPlainConfigObject(value) {
251
+ return typeof value === "object" && value !== null && !Array.isArray(value);
241
252
  }
242
253
  function assertUniquePhysicalBundleRoots(config, sourcePath) {
243
254
  const owners = new Map();
@@ -253,11 +264,10 @@ function assertUniquePhysicalBundleRoots(config, sourcePath) {
253
264
  * Parse raw config text and validate via Zod.
254
265
  * ({@link AkmConfigSchema}). Returns the merged-with-defaults AkmConfig.
255
266
  *
256
- * The schema accepts only the current config version. A known older version
257
- * is auto-upgraded in memory first (see `./config-version-shim`); anything
258
- * else — including anything newer — is rejected before the canonical shape
259
- * is validated. When the config sets `extends` (#945), its resolved chain is
260
- * deep-merged underneath before validation — see {@link resolveExtendsChain}.
267
+ * `configVersion` is read as the current version whatever it says (see
268
+ * {@link readConfigVersion}). When the config sets `extends` (#945), its
269
+ * resolved chain is deep-merged underneath before validation — see
270
+ * {@link resolveExtendsChain}.
261
271
  */
262
272
  export function parseAndValidateConfigText(text, sourcePath) {
263
273
  const liftedConfig = runConfigFilePipeline(text, sourcePath);
@@ -290,7 +300,7 @@ function collectExtendsLayers(localRaw, configPath) {
290
300
  }
291
301
  visited.add(resolvedPath);
292
302
  const baseRaw = runConfigFilePipeline(text, resolvedPath);
293
- warnUnknownTopLevelConfigKeys(baseRaw, resolvedPath);
303
+ warnUnknownConfigKeys(baseRaw, resolvedPath);
294
304
  layers.push({ ref, raw: baseRaw });
295
305
  current = baseRaw;
296
306
  currentPath = resolvedPath;
@@ -666,6 +676,36 @@ function configWriteBody(localRaw, current, next, persistTopLevelKeys = []) {
666
676
  }
667
677
  return ordered;
668
678
  }
679
+ /**
680
+ * The migrator's one config step. The reader already tolerates every shape
681
+ * akm has written (`configVersion` read as current, legacy source layout,
682
+ * `extraParams` lift, unknown keys dropped); this writes that current shape back to
683
+ * `config.json` — the same body `mutateConfig` writes — so the tolerance
684
+ * becomes durable. Reports without writing unless `apply` is set.
685
+ */
686
+ export function normalizeConfigFile(configPath, options) {
687
+ return withConfigLock(() => {
688
+ const text = readConfigText(configPath);
689
+ if (text === undefined)
690
+ return { keys: [], changed: false, applied: false };
691
+ const raw = parseConfigText(text, configPath);
692
+ const localRaw = runConfigFilePipeline(text, configPath);
693
+ const current = buildEffectiveConfig(localRaw, configPath);
694
+ const next = validateCompleteConfig({ ...current, configVersion: CURRENT_CONFIG_VERSION });
695
+ const body = withSchedulerOnDisk(configWriteBody(localRaw, current, next), next);
696
+ for (const keyPath of unknownConfigKeyPaths(body))
697
+ deleteConfigPath(body, keyPath);
698
+ const keys = [...new Set([...Object.keys(raw), ...Object.keys(body)])]
699
+ .filter((key) => JSON.stringify(raw[key]) !== JSON.stringify(body[key]))
700
+ .sort();
701
+ if (keys.length === 0 || !options.apply)
702
+ return { keys, changed: keys.length > 0, applied: false };
703
+ const backup = backupExistingConfig(configPath);
704
+ writeConfigAtomic(configPath, body);
705
+ cachedConfig = undefined;
706
+ return { keys, changed: true, applied: true, ...(backup ? { backupPath: backup.timestamped } : {}) };
707
+ });
708
+ }
669
709
  /**
670
710
  * Mutate config under one fail-closed lock spanning read, merge, validation,
671
711
  * ordinary backup, and atomic write.
@@ -719,6 +759,61 @@ export async function mutateConfigWithPrecommit(mutate, precommit, options) {
719
759
  release();
720
760
  }
721
761
  }
762
+ const UNBOUND_SCHEDULER_SOURCE_ID = `sha256:${"0".repeat(64)}`;
763
+ /**
764
+ * The source identity 0.9.16 bound a scheduler grant to: the configured
765
+ * bundle's source id, or the implicit `AKM_BUNDLE_DIR` stash's. `undefined`
766
+ * when the bundle is not active on this host.
767
+ */
768
+ export function schedulerSourceIdFor(config, bundleId) {
769
+ if (isBundleEnabled(config, bundleId))
770
+ return bundleSourceId(config, bundleId);
771
+ if (config.bundles?.[bundleId] !== undefined || !process.env.AKM_BUNDLE_DIR?.trim())
772
+ return undefined;
773
+ try {
774
+ const root = resolveStashDir();
775
+ const implicitId = deriveBundleId(undefined, root, new Set(Object.keys(config.bundles ?? {})));
776
+ return implicitId === bundleId ? filesystemBundleSourceId(root) : undefined;
777
+ }
778
+ catch {
779
+ return undefined;
780
+ }
781
+ }
782
+ /**
783
+ * `scheduler.enabled` is a list of refs in memory but is written in the
784
+ * `{kind, ref, sourceId}` shape 0.9.16 reads, so that release still runs
785
+ * against a config this one wrote (the upgrade rehearsal's read-back). This
786
+ * release reads either shape; the object form can go once no supported
787
+ * release is strict about it.
788
+ */
789
+ function schedulerEnabledOnDisk(config) {
790
+ const enabled = config.scheduler?.enabled;
791
+ if (enabled === undefined)
792
+ return undefined;
793
+ return enabled.map((ref) => {
794
+ let bundle;
795
+ let conceptId = "";
796
+ try {
797
+ const parsed = parseBundleRef(ref);
798
+ bundle = parsed.bundle;
799
+ conceptId = parsed.conceptId;
800
+ }
801
+ catch {
802
+ return ref;
803
+ }
804
+ return {
805
+ kind: conceptId.startsWith("workflows/") ? "workflow" : "task",
806
+ ref,
807
+ sourceId: (bundle !== undefined ? schedulerSourceIdFor(config, bundle) : undefined) ?? UNBOUND_SCHEDULER_SOURCE_ID,
808
+ };
809
+ });
810
+ }
811
+ function withSchedulerOnDisk(body, config) {
812
+ const onDisk = schedulerEnabledOnDisk(config);
813
+ if (onDisk === undefined || !isPlainConfigObject(body.scheduler))
814
+ return body;
815
+ return { ...body, scheduler: { ...body.scheduler, enabled: onDisk } };
816
+ }
722
817
  /**
723
818
  * Strip literal apiKey fields before writing config to disk.
724
819
  * API keys are expected to come from environment variables
@@ -783,7 +878,7 @@ export function sanitizeConfigForWrite(config) {
783
878
  warn(`Config sanitizer dropped registry entr${droppedRegistries.length === 1 ? "y" : "ies"} with URL credentials before writing to disk:\n - ${droppedRegistries.join("\n - ")}\n\nRegistry URLs must be credential-free; configure a credential-free HTTPS endpoint.`);
784
879
  }
785
880
  }
786
- return sanitized;
881
+ return withSchedulerOnDisk(sanitized, config);
787
882
  }
788
883
  export function updateConfig(partial) {
789
884
  return mutateConfig((current) => deepMergeConfig(current, partial)).config;
@@ -55,20 +55,29 @@ function filesystemLocator(bundle) {
55
55
  return undefined;
56
56
  return path.resolve(bundle.path);
57
57
  }
58
- export function migrateLegacySourceShape(raw, sourcePath) {
59
- const hasStashDir = typeof raw.stashDir === "string" && raw.stashDir.trim().length > 0;
60
- const hasSources = Array.isArray(raw.sources) && raw.sources.length > 0;
58
+ /**
59
+ * Pure legacy `stashDir`/`sources[]`/`installed` -> `bundles`/`defaultBundle`
60
+ * conversion. Triggers on key *presence* (not on the value being usable), so
61
+ * an unusable value — `stashDir: ""`, `sources: []` — is still removed along
62
+ * with the rest of the legacy shape; only a usable `stashDir` or `sources[]`
63
+ * entry is actually folded into `bundles`. Never warns — see
64
+ * {@link migrateLegacySourceShape} for the warn-once wrapper callers use.
65
+ */
66
+ export function convertLegacySourceShape(raw) {
67
+ const hasStashDir = "stashDir" in raw;
68
+ const hasSources = "sources" in raw;
61
69
  const hasInstalled = "installed" in raw && raw.installed !== undefined;
62
70
  if (!hasStashDir && !hasSources && !hasInstalled)
63
- return raw;
71
+ return { config: raw, converted: [] };
64
72
  const { stashDir: _stashDir, sources: _sources, installed: _installed, ...rest } = raw;
65
73
  const bundles = isPlainRecord(rest.bundles) ? { ...rest.bundles } : {};
66
74
  let defaultBundle = typeof rest.defaultBundle === "string" ? rest.defaultBundle : undefined;
67
- if (hasStashDir) {
75
+ const usableStashDir = typeof raw.stashDir === "string" && raw.stashDir.trim().length > 0;
76
+ if (usableStashDir) {
68
77
  bundles.stash = { path: raw.stashDir, writable: true };
69
78
  defaultBundle ??= "stash";
70
79
  }
71
- if (hasSources) {
80
+ if (Array.isArray(raw.sources)) {
72
81
  raw.sources.forEach((entry, index) => {
73
82
  const converted = bundleFromLegacySource(entry, index);
74
83
  if (!converted)
@@ -81,8 +90,28 @@ export function migrateLegacySourceShape(raw, sourcePath) {
81
90
  defaultBundle ??= key;
82
91
  });
83
92
  }
84
- const droppedKeys = [hasStashDir && "stashDir", hasSources && "sources", hasInstalled && "installed"].filter(Boolean);
93
+ const converted = [hasStashDir && "stashDir", hasSources && "sources", hasInstalled && "installed"].filter((key) => typeof key === "string");
94
+ // Only add a `bundles` key when there is something to say: either a
95
+ // legacy entry actually converted into one, or the config already had a
96
+ // `bundles` map (preserved as-is). An unusable `stashDir: ""` / `sources: []`
97
+ // with no pre-existing `bundles` should just disappear, not leave behind
98
+ // an empty `bundles: {}`.
99
+ const hasBundles = Object.keys(bundles).length > 0 || isPlainRecord(rest.bundles);
100
+ return {
101
+ config: { ...rest, ...(hasBundles ? { bundles } : {}), ...(defaultBundle !== undefined ? { defaultBundle } : {}) },
102
+ converted,
103
+ };
104
+ }
105
+ /**
106
+ * Warn-once wrapper around {@link convertLegacySourceShape} for the
107
+ * in-memory read path: converts the legacy shape and, when it removed
108
+ * anything, warns once that `akm migrate apply` will persist the rewrite.
109
+ */
110
+ export function migrateLegacySourceShape(raw, sourcePath) {
111
+ const { config, converted } = convertLegacySourceShape(raw);
112
+ if (converted.length === 0)
113
+ return config;
85
114
  const where = sourcePath ? ` at ${sourcePath}` : "";
86
- warnOnce(`legacy-source-shape${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired ${droppedKeys.join("/")} shape — auto-migrated in memory to \`bundles\`/\`defaultBundle\`. Run \`akm migrate apply\` to rewrite the config file and silence this warning.`);
87
- return { ...rest, bundles, ...(defaultBundle !== undefined ? { defaultBundle } : {}) };
115
+ warnOnce(`legacy-source-shape${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired ${converted.join("/")} shape — auto-migrated in memory to \`bundles\`/\`defaultBundle\`. Run \`akm migrate apply\` to rewrite the config file and silence this warning.`);
116
+ return config;
88
117
  }
@@ -27,11 +27,7 @@ export const EmbeddingConnectionConfigSchema = z
27
27
  endpoint: z.string().optional(),
28
28
  model: z.string().optional(),
29
29
  apiKey: symbolicOrWarnApiKey("embedding.apiKey").optional(),
30
- // Bounded to the index schema's own vec-table guard (1–4096,
31
- // storage/repositories/index-schema.ts) so an out-of-range dimension
32
- // fails at config validation with a clear message instead of crashing
33
- // `akm index` when ensureSchema rejects it (§24.2 "Semantic" gate).
34
- dimension: positiveInt.max(4096).optional(),
30
+ dimension: positiveInt.optional(),
35
31
  localModel: z.string().min(1).optional(),
36
32
  /**
37
33
  * Per-document token cap applied BEFORE batching (default 512,
@@ -68,6 +64,25 @@ export const EmbeddingConnectionConfigSchema = z
68
64
  * no retry — every batch that hit it was silently dropped (#954).
69
65
  */
70
66
  timeoutMs: positiveInt.optional(),
67
+ /**
68
+ * How long search waits for the query embedding (default 3000,
69
+ * `DEFAULT_QUERY_EMBED_TIMEOUT_MS` in `src/indexer/search/db-search.ts`).
70
+ * On expiry the search is served by keyword ranking alone, with a warning.
71
+ */
72
+ queryTimeoutMs: positiveInt.optional(),
73
+ /**
74
+ * Text sent to the model around a search query, `{text}` marking the
75
+ * query (a template without it is a prefix). Overrides the preset matched
76
+ * on the model name (`src/llm/embedders/profile.ts`); `""` sends the query
77
+ * as is.
78
+ */
79
+ queryTemplate: z.string().optional(),
80
+ /**
81
+ * Text sent to the model around each document at index time, as
82
+ * `queryTemplate`. Part of the embedding fingerprint: changing it
83
+ * re-embeds the index.
84
+ */
85
+ documentTemplate: z.string().optional(),
71
86
  /**
72
87
  * Overrides the fixed in-flight request window (#954, added after field
73
88
  * evidence from multi-slot local servers). Bounded 1-16. Unset keeps
@@ -77,6 +77,11 @@ const LlmEngineSchema = z
77
77
  maxTokens: positiveInt.optional(),
78
78
  timeoutMs: timeoutMsField,
79
79
  concurrency: positiveInt.optional(),
80
+ // Same user-settable override LlmConnectionConfigSchema declares above; a
81
+ // named `kind: "llm"` engine is validated by THIS object, so without the
82
+ // field here the unknown-key walk reported a live setting as unknown and
83
+ // `akm migrate apply` dropped it from config.json.
84
+ supportsJsonSchema: z.boolean().optional(),
80
85
  extraParams: ExtraParamsSchema.optional(),
81
86
  contextLength: positiveInt.optional(),
82
87
  enableThinking: z.boolean().optional(),
@@ -20,4 +20,4 @@ export const ExecutionPolicyConfigSchema = z
20
20
  }
21
21
  }),
22
22
  })
23
- .strict();
23
+ .passthrough();
@@ -27,4 +27,4 @@ export const ExperimentalConfigSchema = z
27
27
  */
28
28
  improveAutonomy: z.boolean().optional(),
29
29
  })
30
- .strict();
30
+ .passthrough();
@@ -64,8 +64,6 @@ const processLimitField = positiveInt.optional();
64
64
  * `reflect` process (proposal-side quality gate; see reflect.ts).
65
65
  */
66
66
  const qualityGateField = z.object({ enabled: z.boolean().optional() }).passthrough().optional();
67
- /** Consolidate process: gate for the M-1 (#367) contradiction-detection pass. */
68
- const contradictionDetectionField = z.object({ enabled: z.boolean().optional() }).passthrough().optional();
69
67
  /**
70
68
  * WS-3b: CLS (Complementary Learning System) interleaving (step 9).
71
69
  * distill/memoryInference prompts include embedding-retrieved existing adjacent
@@ -109,19 +107,10 @@ const extractTriageGateField = z
109
107
  })
110
108
  .passthrough()
111
109
  .optional();
112
- const triageJudgmentErrorMap = (issue, ctx) => {
113
- if (issue.code === z.ZodIssueCode.unrecognized_keys) {
114
- const retired = issue.keys.find((key) => key === "mode" || key === "profile");
115
- if (retired)
116
- return { message: `${retired} is retired; use engine` };
117
- }
118
- return { message: ctx.defaultError };
119
- };
120
- // Judgment is an explicit opt-in surface, so invocation typos must fail closed.
121
- // Keep the shared override schema lenient for ordinary cross-version config
122
- // compatibility, while making this nested surface strict. `extraParams`
123
- // remains the intentional arbitrary provider-parameter escape hatch.
124
- const triageJudgmentLlmOverridesField = LlmInvocationOverridesSchema.strict();
110
+ // Unknown keys pass through here like every other nested surface (the loader
111
+ // names them once); `extraParams` remains the arbitrary provider-parameter
112
+ // escape hatch.
113
+ const triageJudgmentLlmOverridesField = LlmInvocationOverridesSchema.passthrough();
125
114
  const triageJudgmentObjectField = z
126
115
  .object({
127
116
  enabled: z.boolean().optional(),
@@ -129,41 +118,26 @@ const triageJudgmentObjectField = z
129
118
  model: nonEmptyString.optional(),
130
119
  timeoutMs: z.union([positiveInt, z.null()]).optional(),
131
120
  llm: triageJudgmentLlmOverridesField.optional(),
132
- }, { errorMap: triageJudgmentErrorMap })
133
- .strict();
121
+ })
122
+ .passthrough();
134
123
  /** Triage process: explicit LLM-as-judge enablement and execution overrides. */
135
124
  const triageJudgmentField = z
136
125
  .union([z.boolean(), triageJudgmentObjectField])
137
126
  .transform((value) => typeof value === "boolean" ? { enabled: value } : { ...value, enabled: value.enabled ?? true })
138
127
  .optional();
139
128
  /**
140
- * WS-3b: Anti-collapse guards (step 8). Prevents the consolidation pipeline
141
- * from collapsing too aggressively and losing diversity. Consolidate process
142
- * only. Default ON since R5 (opt out via enabled: false).
143
- * - maxGeneration: refuse to merge two assets both above this generation (default 2).
144
- * - lexicalDiversityCheck: low n-gram diversity ⇒ raise merge threshold.
145
- * - randomClusterFraction: occasional random (non-similar) cluster in pool (default 0.05).
146
- * - mergeInformationFloor: LIVE gate (anti-collapse.ts:143) — NOT a
147
- * decorative/inert knob. `false` skips the merge-information-floor
148
- * measurement entirely (no counting, no warning) for every merge;
149
- * true/absent (default) measures it on every merge. The MEASUREMENT's
150
- * outcome is advisory in v1: a failing merge is counted
151
- * (`merge_floor_violations`) and warned but never refused (promotion path:
152
- * docs/architecture/specs/improve-collapse-churn-detector-design.md §7). In short:
153
- * this field gates whether the check runs at all (a real code path), not
154
- * whether a merge is allowed.
155
- * - minSpecificityRetention: distinct-token retention floor for merges (default 0.6).
156
- * (WS-3b step 0a `homeostaticDemotion` was removed — R4. Continuous decay is
157
- * now part of the always-applied salience recency term.)
129
+ * WS-3b: Anti-collapse guard (step 8), consolidate process only: a small
130
+ * random (non-similarity-driven) fraction of the pool is mixed into the
131
+ * clustered order so consolidation is not purely rich-get-richer. Default ON
132
+ * (opt out via `enabled: false`); `randomClusterFraction` defaults to 0.05.
133
+ * The retired merge guards (`maxGeneration`, `lexicalDiversityCheck`,
134
+ * `mergeInformationFloor`, `minSpecificityRetention`) never refused a merge
135
+ * and are tolerated as unknown keys.
158
136
  */
159
137
  const antiCollapseField = z
160
138
  .object({
161
139
  enabled: z.boolean().optional(),
162
- maxGeneration: z.number().int().min(1).optional(),
163
- lexicalDiversityCheck: z.boolean().optional(),
164
140
  randomClusterFraction: z.number().min(0).max(1).optional(),
165
- mergeInformationFloor: z.boolean().optional(),
166
- minSpecificityRetention: z.number().min(0).max(1).optional(),
167
141
  })
168
142
  .passthrough()
169
143
  .optional();
@@ -202,7 +176,6 @@ const CONSOLIDATE_PROCESS_FIELDS = {
202
176
  // accumulates; this value is only used on the very first run. Default 30s.
203
177
  p90ChunkSecondsDefault: z.number().finite().positive().optional(),
204
178
  antiCollapse: antiCollapseField,
205
- contradictionDetection: contradictionDetectionField,
206
179
  };
207
180
  const MEMORY_INFERENCE_PROCESS_FIELDS = {
208
181
  // Minimum pending memory count to run the pass.
@@ -261,10 +234,7 @@ const EXTRACT_PROCESS_FIELDS = {
261
234
  };
262
235
  const TRIAGE_PROCESS_FIELDS = {
263
236
  applyMode: z.enum(["queue", "promote"]).optional(),
264
- policy: z.string().min(1).optional(),
265
237
  maxAcceptsPerRun: positiveInt.optional(),
266
- maxDiffLines: positiveInt.optional(),
267
- rejectEmpty: z.boolean().optional(),
268
238
  judgment: triageJudgmentField,
269
239
  };
270
240
  /** proactiveMaintenance process fields (Layer 2). */
@@ -276,38 +246,6 @@ const PROACTIVE_MAINTENANCE_PROCESS_FIELDS = {
276
246
  maxPerRun: positiveInt.optional(),
277
247
  limit: processLimitField,
278
248
  };
279
- /**
280
- * Shared cross-process superRefine: rejects the retired `mode`/`profile`
281
- * knobs (top-level and inside a `judgment` sub-object) in favour of `engine`.
282
- * Applied identically to every per-process schema and to the wide
283
- * ImproveProcessConfigSchema.
284
- */
285
- function checkRetiredProcessKeys(value, ctx) {
286
- for (const key of ["mode", "profile"]) {
287
- if (key in value) {
288
- ctx.addIssue({ code: z.ZodIssueCode.custom, path: [key], message: `${key} is retired; use engine` });
289
- }
290
- }
291
- if ("judgement" in value) {
292
- ctx.addIssue({
293
- code: z.ZodIssueCode.custom,
294
- path: ["judgement"],
295
- message: "judgement is not a valid key; use judgment",
296
- });
297
- }
298
- const judgment = value.judgment;
299
- if (judgment) {
300
- for (const key of ["mode", "profile"]) {
301
- if (key in judgment) {
302
- ctx.addIssue({
303
- code: z.ZodIssueCode.custom,
304
- path: ["judgment", key],
305
- message: `${key} is retired; use engine`,
306
- });
307
- }
308
- }
309
- }
310
- }
311
249
  /** distill/consolidate are memory-only and never read `excludeRefPrefixes` (reflect only, R12). */
312
250
  function rejectExcludeRefPrefixesOutsideReflect(value, ctx) {
313
251
  if ("excludeRefPrefixes" in value) {
@@ -330,55 +268,43 @@ export const ImproveProcessConfigSchema = z
330
268
  ...TRIAGE_PROCESS_FIELDS,
331
269
  ...PROACTIVE_MAINTENANCE_PROCESS_FIELDS,
332
270
  })
333
- .passthrough()
334
- .superRefine(checkRetiredProcessKeys);
271
+ .passthrough();
335
272
  /** `processes.reflect` — narrow per-process schema (WI-9.6). */
336
273
  export const ReflectProcessConfigSchema = z
337
274
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...REFLECT_PROCESS_FIELDS })
338
- .passthrough()
339
- .superRefine(checkRetiredProcessKeys);
275
+ .passthrough();
340
276
  /** `processes.distill` — narrow per-process schema (WI-9.6). */
341
277
  export const DistillProcessConfigSchema = z
342
278
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...DISTILL_PROCESS_FIELDS })
343
279
  .passthrough()
344
- .superRefine(checkRetiredProcessKeys)
345
280
  .superRefine(rejectExcludeRefPrefixesOutsideReflect);
346
281
  /** `processes.consolidate` — narrow per-process schema (WI-9.6). */
347
282
  export const ConsolidateProcessConfigSchema = z
348
283
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...CONSOLIDATE_PROCESS_FIELDS })
349
284
  .passthrough()
350
- .superRefine(checkRetiredProcessKeys)
351
285
  .superRefine(rejectExcludeRefPrefixesOutsideReflect);
352
286
  /** `processes.memoryInference` — narrow per-process schema (WI-9.6). */
353
287
  export const MemoryInferenceProcessConfigSchema = z
354
288
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...MEMORY_INFERENCE_PROCESS_FIELDS })
355
- .passthrough()
356
- .superRefine(checkRetiredProcessKeys);
289
+ .passthrough();
357
290
  /** `processes.graphExtraction` — narrow per-process schema (WI-9.6). */
358
291
  export const GraphExtractionProcessConfigSchema = z
359
292
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...GRAPH_EXTRACTION_PROCESS_FIELDS })
360
- .passthrough()
361
- .superRefine(checkRetiredProcessKeys);
293
+ .passthrough();
362
294
  /** `processes.extract` — narrow per-process schema (WI-9.6). */
363
295
  export const ExtractProcessConfigSchema = z
364
296
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...EXTRACT_PROCESS_FIELDS })
365
- .passthrough()
366
- .superRefine(checkRetiredProcessKeys);
297
+ .passthrough();
367
298
  /** `processes.validation` — narrow per-process schema (WI-9.6); no extra fields beyond the shared base. */
368
- export const ValidationProcessConfigSchema = z
369
- .object({ ...IMPROVE_PROCESS_BASE_FIELDS })
370
- .passthrough()
371
- .superRefine(checkRetiredProcessKeys);
299
+ export const ValidationProcessConfigSchema = z.object({ ...IMPROVE_PROCESS_BASE_FIELDS }).passthrough();
372
300
  /** `processes.triage` — narrow per-process schema (WI-9.6). */
373
301
  export const TriageProcessConfigSchema = z
374
302
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...TRIAGE_PROCESS_FIELDS })
375
- .passthrough()
376
- .superRefine(checkRetiredProcessKeys);
303
+ .passthrough();
377
304
  /** `processes.proactiveMaintenance` — narrow per-process schema (WI-9.6). */
378
305
  export const ProactiveMaintenanceProcessConfigSchema = z
379
306
  .object({ ...IMPROVE_PROCESS_BASE_FIELDS, ...PROACTIVE_MAINTENANCE_PROCESS_FIELDS })
380
- .passthrough()
381
- .superRefine(checkRetiredProcessKeys);
307
+ .passthrough();
382
308
  const ImproveProfileProcessesSchema = z
383
309
  .object({
384
310
  reflect: ReflectProcessConfigSchema.optional(),