akm-cli 0.9.17-alpha.2 → 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 +756 -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 +363 -679
  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 -427
  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
@@ -1,150 +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
- * Improve-pipeline advisories for `akm health`: projects the computed
6
- * {@link ImproveHealthMetrics} plus a few direct event reads into the
7
- * ordered advisory list.
8
- */
9
- import { readEvents } from "../../core/events.js";
10
- import { getLatestCycleMetrics } from "../../storage/repositories/canaries-repository.js";
11
- import { ENRICHMENT_MINTED_FAIL_SHARE, ENRICHMENT_MINTED_WARN_SHARE, } from "./types.js";
12
- /**
13
- * Build the improve-pipeline advisories for the health window from the already
14
- * computed {@link ImproveHealthMetrics} plus a few direct event reads. Pure
15
- * projection of state → advisories; emission order is preserved so the health
16
- * report is byte-identical to the previous inline construction.
17
- */
18
- export function collectImproveAdvisories(db, stateDbPath, since, improveSummary) {
19
- const advisories = [];
20
- // WS-2 proxy-adequacy tripwire: surface any outcome_proxy_inverted events
21
- // in the health window as an advisory so operators know when the 0.10+
22
- // rich in-session signal is no longer deferrable.
23
- const proxyInvertedEvents = readEvents({ since, type: "outcome_proxy_inverted" }, { dbPath: stateDbPath }).events;
24
- if (proxyInvertedEvents.length > 0) {
25
- const lastEvent = proxyInvertedEvents[proxyInvertedEvents.length - 1];
26
- const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
27
- advisories.push({
28
- name: "outcome-proxy-adequacy",
29
- status: "warn",
30
- kind: "deterministic",
31
- confidence: "high",
32
- message: `WS-2 outcome proxy inverted (${proxyInvertedEvents.length} event(s) in window). ` +
33
- `corr(outcome_score, accepted_change_rate) = ${correlation} < −0.3. ` +
34
- "Popular assets are also the most-needing-improvement assets — " +
35
- "the retrieval-based proxy is inverted. " +
36
- "The 0.10+ rich in-session outcome signal is no longer deferrable. See plan §WS-2.",
37
- });
38
- }
39
- // Two-tailed companion: a proxy that decays to noise (|corr| < 0.1 at scale)
40
- // is as much a failure as an inverted one — it just fails silently.
41
- const proxyDeadEvents = readEvents({ since, type: "outcome_proxy_dead" }, { dbPath: stateDbPath, db }).events;
42
- if (proxyDeadEvents.length > 0) {
43
- const lastEvent = proxyDeadEvents[proxyDeadEvents.length - 1];
44
- const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
45
- advisories.push({
46
- name: "outcome-proxy-dead",
47
- status: "warn",
48
- kind: "deterministic",
49
- confidence: "high",
50
- message: `WS-2 outcome proxy is DEAD (${proxyDeadEvents.length} event(s) in window). ` +
51
- `|corr(outcome_score, accepted_change_rate)| = ${correlation} < 0.1 at n ≥ 500. ` +
52
- "outcome_score is statistically unrelated to improvement outcomes — " +
53
- "treat outcome-derived rank contributions as noise until a real usage/outcome signal lands.",
54
- });
55
- }
56
- // Salience-distribution collapse across all assets with retrieval evidence.
57
- if (improveSummary.degradation?.salienceUniformityFlagged) {
58
- const sampleSize = improveSummary.degradation.retrievalSalienceSampleSize;
59
- advisories.push({
60
- name: "salience-uniformity-collapse",
61
- status: "warn",
62
- kind: "deterministic",
63
- confidence: "high",
64
- message: `Salience distribution collapsed toward uniform: retrieval_salience Gini = ` +
65
- `${improveSummary.degradation.corpusCentroidDistance} < 0.08 across ${sampleSize} ` +
66
- "observed, resolvable assets. Ranking carries little discrimination among assets with retrieval evidence.",
67
- });
68
- }
69
- // Enrichment-vs-minting policy: enrichment lanes edit existing assets;
70
- // a rising minted share means a lane is generating new content instead.
71
- const minting = improveSummary.enrichmentMinting;
72
- if (minting && Number.isFinite(minting.share) && minting.share > ENRICHMENT_MINTED_WARN_SHARE) {
73
- advisories.push({
74
- name: "enrichment-lane-minting",
75
- status: minting.share > ENRICHMENT_MINTED_FAIL_SHARE ? "fail" : "warn",
76
- kind: "deterministic",
77
- confidence: "high",
78
- message: `Enrichment lanes minted ${minting.minted} NEW asset(s) vs ${minting.updated} update(s) ` +
79
- `(${Math.round(minting.share * 100)}% minted, threshold ${Math.round(ENRICHMENT_MINTED_WARN_SHARE * 100)}%). ` +
80
- "Enrichment-classed lanes (proactive/high-salience/signal-delta) are ratified to edit " +
81
- "existing assets only — new-asset generation belongs to the signal-gated minting lanes.",
82
- });
83
- }
84
- // Churn: accepted proposals far exceeding distinct touched refs means the
85
- // loop is repeatedly rewriting the same assets, not covering the corpus.
86
- if (Number.isFinite(improveSummary.coverage.churnRatio) && improveSummary.coverage.churnRatio > 1.5) {
87
- advisories.push({
88
- name: "improve-churn-ratio",
89
- status: "warn",
90
- kind: "deterministic",
91
- confidence: "high",
92
- message: `Improve churn ratio ${improveSummary.coverage.churnRatio} > 1.5: ` +
93
- `${improveSummary.coverage.acceptedProposals} accepted proposals touched only ` +
94
- `${improveSummary.coverage.distinctRefs} distinct assets in the window — ` +
95
- "repeated rewrites of the same refs count as churn, not coverage.",
96
- });
97
- }
98
- // R5 collapse/churn detector: surface any collapse_detector_alert events
99
- // in the health window, plus the latest cycle row's headline numbers so
100
- // the operator can act without opening the DB. `unknown` when the detector
101
- // has never produced a cycle row (no consolidate work yet).
102
- try {
103
- // Reuse the already-open state.db handle (readEvents supports a
104
- // borrowed connection) — no extra open/migrate/close per health call.
105
- const collapseAlertEvents = readEvents({ since, type: "collapse_detector_alert" }, { dbPath: stateDbPath, db }).events;
106
- const latestCycle = getLatestCycleMetrics(db);
107
- const cycleSummary = latestCycle
108
- ? `Latest cycle (${latestCycle.ts}, ${latestCycle.pass}): mean canary recall ${latestCycle.mean_recall.toFixed(3)}, ` +
109
- `distinct-content ratio ${latestCycle.distinct_content_ratio.toFixed(3)}.`
110
- : "";
111
- if (collapseAlertEvents.length > 0) {
112
- const kinds = [...new Set(collapseAlertEvents.map((e) => String(e.metadata?.kind ?? "unknown")))];
113
- const collapseKinds = kinds.filter((k) => k.startsWith("collapse"));
114
- advisories.push({
115
- name: "collapse-churn-detector",
116
- status: "warn",
117
- kind: "deterministic",
118
- // Collapse kinds are measured, not inferred; the merge-floor
119
- // volume threshold is still being tuned (design doc §7).
120
- confidence: collapseKinds.length > 0 ? "high" : "medium",
121
- message: `R5 detector fired ${collapseAlertEvents.length} alert(s) in window (kinds: ${kinds.join(", ")}). ` +
122
- `${cycleSummary} See docs/architecture/specs/improve-collapse-churn-detector-design.md §6.3 runbook queries.`,
123
- });
124
- }
125
- else if (latestCycle) {
126
- advisories.push({
127
- name: "collapse-churn-detector",
128
- status: "pass",
129
- kind: "deterministic",
130
- confidence: "high",
131
- message: `No collapse/churn alerts in window. ${cycleSummary}`,
132
- });
133
- }
134
- else {
135
- advisories.push({
136
- name: "collapse-churn-detector",
137
- status: "unknown",
138
- kind: "deterministic",
139
- confidence: "high",
140
- message: "No detector cycle rows yet — the collapse/churn detector runs only on improve cycles " +
141
- "where consolidate did work.",
142
- });
143
- }
144
- }
145
- catch {
146
- // Table may predate migration 016 in odd mixed-version setups — advisory
147
- // is best-effort and must never fail the health command.
148
- }
149
- return advisories;
150
- }
@@ -1,329 +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
- * State.db-backed health metrics for `akm health`: the round-trip probe,
6
- * denominator-fixed coverage, the enrichment-vs-minting rollup, and the WS-5
7
- * per-run degradation metrics.
8
- */
9
- import { rethrowIfTestIsolationError } from "../../core/errors.js";
10
- import { decodeImproveResult } from "../../core/improve-result.js";
11
- import { withStateDb } from "../../core/state-db.js";
12
- import { insertEvent } from "../../storage/repositories/events-repository.js";
13
- import { queryImproveRuns } from "../../storage/repositories/improve-runs-repository.js";
14
- import { listStateProposals } from "../../storage/repositories/proposals-repository.js";
15
- import { getObservedRetrievalSalience } from "../../storage/repositories/salience-repository.js";
16
- import { roundRate, toFiniteNumber } from "./improve-metrics.js";
17
- import { ENRICHMENT_LANES, } from "./types.js";
18
- /** Event type appended + read back by the state.db round-trip probe. */
19
- const HEALTH_PROBE_EVENT = "health_probe";
20
- /**
21
- * Retrieval-salience Gini guardrails.
22
- *
23
- * These are distribution-shape thresholds, not quantiles tied to a corpus
24
- * size. Synthetic anchors pinned in monitor-liveness.test.ts are ~0.01 for a
25
- * near-uniform two-band distribution, 0.25 for a balanced 0.25/0.75 spread,
26
- * and ~0.82 for one dominant value among nine 0.01 values. Full-observation
27
- * production snapshots also sit stably in the neutral band: 0.2613 at n=1,209
28
- * (2026-07-12) and 0.2506 at n=1,454 (2026-08-17, missing rows excluded).
29
- */
30
- const SALIENCE_UNIFORMITY_GINI_THRESHOLD = 0.08;
31
- const SALIENCE_ENTRENCHMENT_GINI_THRESHOLD = 0.35;
32
- /** Synthetic sentinel ref (ref-grammar decision D-R3): a colon-free
33
- * `<subsystem>/_<marker>` label. `health` has no asset stash-subdir, so
34
- * `health/_probe` names the subsystem. */
35
- const HEALTH_PROBE_REF = "health/_probe";
36
- /**
37
- * Verify state.db can accept a write and read it back — WITHOUT leaving any
38
- * permanent trace (R-030). Earlier versions appended a `health_probe` event
39
- * on every `akm health` invocation and never removed it: a read-only health
40
- * check ran on a cron would grow state.db without bound (the only purge is
41
- * `improve`'s retention pass, which a health-only user never runs). The probe
42
- * row inserted here is deleted again inside the SAME connection once the
43
- * round trip is confirmed, so the net effect on the `events` table is always
44
- * zero rows — the round trip still genuinely exercises append + read against
45
- * the real table, it just doesn't accumulate.
46
- */
47
- export function probeStateDbRoundTrip(stateDbPath) {
48
- const started = Date.now();
49
- try {
50
- return withStateDb((db) => {
51
- const ts = new Date().toISOString();
52
- const insertedId = insertEvent(db, {
53
- eventType: HEALTH_PROBE_EVENT,
54
- ts,
55
- ref: HEALTH_PROBE_REF,
56
- metadata: { source: "akm health" },
57
- });
58
- const durationMs = Date.now() - started;
59
- if (insertedId === undefined) {
60
- return { ok: false, durationMs, error: "probe event insert did not return a row id" };
61
- }
62
- // The round-trip matches on the exact (id, eventType, ref) triple
63
- // written above, then removes the row regardless of outcome — a
64
- // failed round trip must not leak a row any more than a successful
65
- // one should.
66
- let roundTripOk = false;
67
- try {
68
- const row = db
69
- .prepare("SELECT 1 AS present FROM events WHERE id = ? AND event_type = ? AND ref = ?")
70
- .get(insertedId, HEALTH_PROBE_EVENT, HEALTH_PROBE_REF);
71
- roundTripOk = row !== undefined;
72
- }
73
- finally {
74
- db.prepare("DELETE FROM events WHERE id = ?").run(insertedId);
75
- }
76
- if (!roundTripOk) {
77
- return { ok: false, durationMs, error: "probe event was not readable after append" };
78
- }
79
- return { ok: true, durationMs };
80
- }, { path: stateDbPath });
81
- }
82
- catch (err) {
83
- rethrowIfTestIsolationError(err);
84
- return { ok: false, durationMs: Date.now() - started, error: err instanceof Error ? err.message : String(err) };
85
- }
86
- }
87
- // ── WS-5 Observability helpers ───────────────────────────────────────────────
88
- /**
89
- * Compute WS-5 denominator-fixed coverage metrics.
90
- *
91
- * `coverage = accepted_proposals / total_assets` (Part V §3).
92
- * The denominator is the TOTAL stash size (not the moving eligible set) so
93
- * more-inclusive WS-1 ranking cannot spuriously inflate coverage.
94
- * `eligibleFraction = eligible_assets / total_assets` is reported separately.
95
- *
96
- * Proposals are counted only when their `updatedAt` falls within `[since, until)`
97
- * so the rate is genuinely window-scoped (matching the JSDoc on the type).
98
- *
99
- * @param db - Open state.db connection.
100
- * @param totalAssets - Total stash asset count (eligible + derived) from the
101
- * most recent run's memorySummary. 0 = denominator unknown, returns NaN rates.
102
- * @param eligibleAssets - Eligible (non-derived) asset count from the most recent run.
103
- * @param since - Window start (ISO-8601). Proposals accepted before this are excluded.
104
- * @param until - Window end (ISO-8601, exclusive). Absent = open-ended (up to now).
105
- * @param stashDir - Optional: scope accepted proposals to one stash. Absent = all stashes.
106
- */
107
- export function computeDenominatorFixedCoverage(db, totalAssets, eligibleAssets, since, until, stashDir) {
108
- let acceptedProposals = 0;
109
- let distinctRefs = 0;
110
- try {
111
- const proposals = listStateProposals(db, {
112
- status: "accepted",
113
- ...(stashDir ? { stashDir } : {}),
114
- }).filter((p) => {
115
- const updatedAt = p.updatedAt ?? "";
116
- if (updatedAt < since)
117
- return false;
118
- if (until !== undefined && updatedAt >= until)
119
- return false;
120
- return true;
121
- });
122
- acceptedProposals = proposals.length;
123
- // Coverage counts DISTINCT refs: N accepted rewrites of one asset are
124
- // churn, not coverage. The raw proposal count is kept alongside so the
125
- // churn ratio (proposals ÷ distinct refs) stays visible.
126
- distinctRefs = new Set(proposals.map((p) => p.ref)).size;
127
- }
128
- catch {
129
- // Fail open: table may not exist on older installs.
130
- }
131
- const churnRatio = distinctRefs > 0 ? roundRate(acceptedProposals / distinctRefs) : Number.NaN;
132
- if (totalAssets === 0) {
133
- return {
134
- rate: Number.NaN,
135
- eligibleFraction: Number.NaN,
136
- acceptedProposals,
137
- distinctRefs,
138
- churnRatio,
139
- totalAssets: 0,
140
- };
141
- }
142
- return {
143
- rate: roundRate(distinctRefs / totalAssets),
144
- eligibleFraction: roundRate(eligibleAssets / totalAssets),
145
- acceptedProposals,
146
- distinctRefs,
147
- churnRatio,
148
- totalAssets,
149
- };
150
- }
151
- /**
152
- * Compute the enrichment-vs-minting rollup over the window's accepted,
153
- * lane-attributed proposals (reporting-only; see {@link EnrichmentMintingRollup}).
154
- *
155
- * SQL-side `json_extract` keeps the (potentially large) `backupContent` blobs
156
- * out of process memory. Pre-Phase-6C rows without an `eligibilitySource`
157
- * cannot be lane-classified and are excluded. Fails open (undefined) when the
158
- * proposals table is absent.
159
- */
160
- export function computeEnrichmentMintingRollup(db, since, until) {
161
- try {
162
- const rows = db
163
- .prepare(`SELECT
164
- json_extract(metadata_json, '$.eligibilitySource') AS lane,
165
- CASE WHEN json_extract(metadata_json, '$.backupContent') IS NULL THEN 1 ELSE 0 END AS is_minted,
166
- COUNT(*) AS cnt
167
- FROM proposals
168
- WHERE status = 'accepted'
169
- AND updated_at >= ?
170
- AND (? IS NULL OR updated_at < ?)
171
- AND json_extract(metadata_json, '$.eligibilitySource') IS NOT NULL
172
- AND json_extract(metadata_json, '$.eligibilitySource') != ''
173
- GROUP BY lane, is_minted`)
174
- .all(since, until ?? null, until ?? null);
175
- if (rows.length === 0)
176
- return undefined;
177
- const byLane = {};
178
- for (const row of rows) {
179
- byLane[row.lane] ??= { minted: 0, updated: 0 };
180
- const entry = byLane[row.lane];
181
- if (row.is_minted === 1)
182
- entry.minted += row.cnt;
183
- else
184
- entry.updated += row.cnt;
185
- }
186
- let minted = 0;
187
- let updated = 0;
188
- for (const lane of ENRICHMENT_LANES) {
189
- const entry = byLane[lane];
190
- if (!entry)
191
- continue;
192
- minted += entry.minted;
193
- updated += entry.updated;
194
- }
195
- const decided = minted + updated;
196
- return {
197
- minted,
198
- updated,
199
- share: decided > 0 ? roundRate(minted / decided) : Number.NaN,
200
- byLane,
201
- };
202
- }
203
- catch {
204
- // Fail open: proposals table may not exist on older installs.
205
- return undefined;
206
- }
207
- }
208
- /**
209
- * Compute WS-5 per-run degradation metrics (Part V §4).
210
- *
211
- * Health VIEWS only — reads from state.db tables populated by prior improve
212
- * runs. Gracefully returns partial data when tables are absent (pre-WS-1/2).
213
- *
214
- * @param db - Open state.db connection.
215
- * @param since - Window start (ISO-8601).
216
- * @param until - Window end (ISO-8601).
217
- */
218
- export function computeDegradationMetrics(db, since, until) {
219
- // (a) Corpus diversity — distribution of every observed retrieval-salience
220
- // value for a currently resolvable asset. Zero is the no-observation floor
221
- // and is excluded: the diagnostic measures discrimination among assets that
222
- // have a retrieval signal, not corpus coverage.
223
- //
224
- // Do not preselect by rank_score here. The old top-100 sample truncated the
225
- // distribution before measuring it, producing a low Gini even when the full
226
- // observed corpus had a healthy spread.
227
- let corpusCentroidDistance = Number.NaN;
228
- let retrievalSalienceSampleSize = 0;
229
- let entrenchmentFlagged;
230
- let salienceUniformityFlagged;
231
- try {
232
- // Fail-open: the asset_salience table may not exist yet (pre-WS-1 install).
233
- const rows = getObservedRetrievalSalience(db);
234
- retrievalSalienceSampleSize = rows.length;
235
- if (rows.length >= 5) {
236
- // The repository returns ascending values. Keep a defensive sort so the
237
- // O(n log n) closed-form Gini remains correct if that contract changes.
238
- const vals = rows.map((r) => r.retrieval_salience).sort((a, b) => a - b);
239
- const n = vals.length;
240
- const sum = vals.reduce((acc, value) => acc + value, 0);
241
- const weightedSum = vals.reduce((acc, value, index) => acc + (index + 1) * value, 0);
242
- // Closed-form Gini for sorted non-negative values. This is O(n log n)
243
- // including the defensive sort; the previous pairwise implementation
244
- // was O(n²) and only safe because the sample was capped at 100.
245
- const gini = sum > 0 ? (2 * weightedSum) / (n * sum) - (n + 1) / n : 0;
246
- // Re-express as a diversity proxy in [0,1]: high gini = low diversity.
247
- // corpusCentroidDistance approximation: gini is "distance from uniform".
248
- // Two-tailed: high concentration flags entrenchment; very low spread
249
- // flags near-uniformity. Calibration provenance is documented with the
250
- // constants above and pinned by synthetic distribution tests.
251
- corpusCentroidDistance = roundRate(gini);
252
- entrenchmentFlagged = gini > SALIENCE_ENTRENCHMENT_GINI_THRESHOLD;
253
- salienceUniformityFlagged = gini < SALIENCE_UNIFORMITY_GINI_THRESHOLD;
254
- }
255
- }
256
- catch {
257
- // Table not present (pre-WS-1 install) — leave NaN.
258
- }
259
- // (b) Merge fidelity — fraction of consolidate accepted proposals in the window
260
- // whose ref also has a consolidate skip-reason of "contradict_target_missing"
261
- // or an event indicating contradiction. Uses the improve_runs result_json
262
- // consolidation.contradicted count as a proxy.
263
- // Simple implementation: contradictionRate = total_contradicted / max(1, total_processed)
264
- // sourced from the window's consolidation envelope.
265
- // (The full "merge proposal → later contradiction" correlation requires cross-run
266
- // history; this is the available proxy.)
267
- let mergeFidelityContradictionRate = 0;
268
- try {
269
- const runs = queryImproveRuns(db, since, until);
270
- let totalContradicted = 0;
271
- let totalProcessed = 0;
272
- for (const row of runs) {
273
- try {
274
- const result = decodeImproveResult(row.result_json).envelope;
275
- const cons = result.consolidation;
276
- if (cons) {
277
- totalContradicted += toFiniteNumber(cons.contradicted);
278
- totalProcessed += toFiniteNumber(cons.processed);
279
- }
280
- }
281
- catch {
282
- // Skip malformed rows.
283
- }
284
- }
285
- if (totalProcessed > 0) {
286
- mergeFidelityContradictionRate = roundRate(totalContradicted / totalProcessed);
287
- }
288
- }
289
- catch {
290
- // Fail open.
291
- }
292
- // (c) highGenerationFraction was DELETED (meta-review 05 DRIFT-3): it
293
- // approximated "LLM-merge generations" from consecutive_no_ops — which counts
294
- // the opposite condition (cycles where nothing was changed) — and its own
295
- // in-code TODO admitted the proxy. Display-only, never actionable; removed
296
- // rather than instrumented.
297
- // (d) Oracle spot-check — up to 5 recently accepted proposals in the window.
298
- const oracleSpotCheck = [];
299
- try {
300
- const accepted = listStateProposals(db, { status: "accepted" }).filter((p) => {
301
- const updatedAt = p.updatedAt ?? "";
302
- return updatedAt >= since && updatedAt < until;
303
- });
304
- // Sample up to 5: pick evenly spaced (not just the first 5).
305
- const step = Math.max(1, Math.floor(accepted.length / 5));
306
- for (let i = 0; i < accepted.length && oracleSpotCheck.length < 5; i += step) {
307
- const p = accepted[i];
308
- if (p) {
309
- oracleSpotCheck.push({
310
- proposalId: p.id,
311
- ref: p.ref,
312
- source: p.source ?? "unknown",
313
- acceptedAt: p.updatedAt ?? p.createdAt ?? "",
314
- });
315
- }
316
- }
317
- }
318
- catch {
319
- // Fail open.
320
- }
321
- return {
322
- corpusCentroidDistance,
323
- retrievalSalienceSampleSize,
324
- entrenchmentFlagged,
325
- salienceUniformityFlagged,
326
- mergeFidelityContradictionRate,
327
- oracleSpotCheck,
328
- };
329
- }
@@ -1,102 +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 remaining `surfaces` advisory group for `akm health` (meta-review 08).
6
- * `stash-git-exposure` (08-F1) shipped first in ./stash-exposure.ts; this
7
- * module adds the other two read-only checks the adjudication approved:
8
- *
9
- * - `binary-config-skew` — config.json written by a NEWER akm than this binary (F3)
10
- * - `egress-endpoints` — the remote-destination list, for eyeball diff (surfaces 3/9)
11
- *
12
- * Every collector is a pure projection over injected paths/config (no
13
- * process.env reads) and is silent when there is nothing to report, matching
14
- * the stash-exposure pattern. `egress-endpoints` is the one informational
15
- * (pass-status) entry: it emits whenever any remote endpoint is configured.
16
- */
17
- import { readTextFile } from "../../core/common.js";
18
- import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
19
- import { compareConfigVersion } from "../../core/config/config-version.js";
20
- import { formatRegistryUrl } from "../../core/registry-url.js";
21
- /**
22
- * `binary-config-skew` (08-F3): warn when config.json carries a configVersion
23
- * NEWER than (or unorderable against) this binary's CURRENT_CONFIG_VERSION —
24
- * i.e. a newer/foreign akm wrote the shared config and this install is stale.
25
- * That is the proven multi-install incident class. Silent for current or older
26
- * versions and unreadable configs (config loading surfaces its own errors).
27
- */
28
- export function collectConfigSkewAdvisory(configPath) {
29
- let raw;
30
- try {
31
- raw = JSON.parse(readTextFile(configPath, "Config file"));
32
- }
33
- catch {
34
- return undefined;
35
- }
36
- const onDisk = raw.configVersion;
37
- const order = compareConfigVersion(onDisk, CURRENT_CONFIG_VERSION);
38
- const skewed = order === 1 || (onDisk !== undefined && order === undefined);
39
- if (!skewed)
40
- return undefined;
41
- return {
42
- name: "binary-config-skew",
43
- kind: "deterministic",
44
- status: "warn",
45
- confidence: "high",
46
- message: `config.json has configVersion ${JSON.stringify(onDisk)} but this binary knows ${CURRENT_CONFIG_VERSION} — ` +
47
- "a newer akm wrote the shared config, so this install is stale. Upgrade this install; do not keep a stale " +
48
- "binary against the shared config/DBs.",
49
- evidence: { onDiskConfigVersion: onDisk, binaryConfigVersion: CURRENT_CONFIG_VERSION },
50
- };
51
- }
52
- /**
53
- * `egress-endpoints` (08 surfaces 3/9): the full list of remote destinations
54
- * akm can talk to under the effective config — registries, remote sources,
55
- * LLM endpoints, embedding endpoint — as one pass-status informational entry
56
- * for eyeball diff against expectations. Silent only when nothing remote is
57
- * configured at all.
58
- */
59
- export function collectEgressAdvisory(config) {
60
- if (!config)
61
- return undefined;
62
- const endpoints = [];
63
- for (const reg of config.registries ?? []) {
64
- if (reg.enabled === false || !reg.url)
65
- continue;
66
- endpoints.push(`registry ${reg.name ?? "(unnamed)"}: ${formatRegistryUrl(reg.url)}`);
67
- }
68
- for (const [key, bundle] of Object.entries(config.bundles ?? {})) {
69
- if (!bundle)
70
- continue;
71
- const url = bundle.git ?? bundle.website?.url;
72
- if (!url)
73
- continue;
74
- endpoints.push(`source ${key} (${bundle.git ? "git" : "website"}): ${url}`);
75
- }
76
- for (const [name, engine] of Object.entries(config.engines ?? {})) {
77
- if (engine?.kind !== "llm" || !engine.endpoint)
78
- continue;
79
- endpoints.push(`llm ${name}: ${engine.endpoint}`);
80
- }
81
- if (config.embedding?.endpoint)
82
- endpoints.push(`embedding: ${config.embedding.endpoint}`);
83
- if (endpoints.length === 0)
84
- return undefined;
85
- return {
86
- name: "egress-endpoints",
87
- kind: "deterministic",
88
- status: "pass",
89
- confidence: "high",
90
- message: `${endpoints.length} remote endpoint(s) in the effective config (registries/sources/LLM/embedding) — ` +
91
- "review the evidence list for unexpected destinations.",
92
- evidence: { endpoints },
93
- };
94
- }
95
- /**
96
- * Aggregate the two collectors into the advisories array shape `akmHealth`
97
- * consumes. Order is fixed: skew → egress.
98
- */
99
- export function collectSurfacesAdvisories(input) {
100
- const results = [collectConfigSkewAdvisory(input.configPath), collectEgressAdvisory(input.config)];
101
- return results.filter((r) => r !== undefined);
102
- }
@@ -1,83 +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
- * WS-3b Step 8 — Anti-collapse merge guards.
6
- *
7
- * (a) Generation counter: merged.generation = max(sources)+1; merges cite
8
- * sources. `over_generation_count` (collapse-detector.ts) tracks assets
9
- * above the generation threshold as an advisory metric only — there is
10
- * no merge-refusal path wired in.
11
- * (b) Lexical-diversity check: low n-gram diversity ⇒ raise merge threshold.
12
- * (d) Occasional random non-similar cluster in the pool.
13
- *
14
- * @module anti-collapse
15
- */
16
- /** Default max generation depth before merge is refused. */
17
- export const DEFAULT_MAX_GENERATION = 2;
18
- /** Default fraction of pool to fill with random (non-similar) clusters. */
19
- export const DEFAULT_RANDOM_CLUSTER_FRACTION = 0.05;
20
- /**
21
- * Read the `generation` field from an asset's frontmatter.
22
- * Returns 0 when absent (no generation metadata = original asset).
23
- */
24
- export function readAssetGeneration(frontmatterData) {
25
- const gen = frontmatterData.generation;
26
- if (typeof gen === "number" && Number.isFinite(gen) && gen >= 0) {
27
- return Math.floor(gen);
28
- }
29
- return 0;
30
- }
31
- /**
32
- * Compute the new generation for a merged asset.
33
- * Rule: `merged.generation = max(source generations) + 1`.
34
- */
35
- export function computeMergedGeneration(sourceGenerations) {
36
- if (sourceGenerations.length === 0)
37
- return 1;
38
- return Math.max(...sourceGenerations) + 1;
39
- }
40
- /**
41
- * Compute the bigram n-gram diversity of a text string.
42
- * Returns a value in [0, 1] where 0 = all identical bigrams, 1 = all unique.
43
- * Used by the lexical-diversity check to detect correlated-extraction artifacts.
44
- */
45
- export function computeBigramDiversity(text) {
46
- const words = text
47
- .toLowerCase()
48
- .split(/\s+/)
49
- .filter((w) => w.length > 0);
50
- if (words.length < 2)
51
- return 1; // too short to have bigrams; treat as diverse
52
- const total = words.length - 1;
53
- const unique = new Set();
54
- for (let i = 0; i < total; i++) {
55
- unique.add(`${words[i]}\t${words[i + 1]}`);
56
- }
57
- return unique.size / total;
58
- }
59
- /**
60
- * Check whether a cluster of memories exhibits suspiciously low lexical diversity.
61
- * When true, the cluster is likely a correlated-extraction artifact; the merge
62
- * threshold should be raised.
63
- *
64
- * @param bodies - The stripped body texts of the cluster members.
65
- * @param config - Anti-collapse config.
66
- * @returns `{ lowDiversity: true, diversity }` when the cluster diversity is
67
- * below the 0.3 threshold; `{ lowDiversity: false }` otherwise.
68
- */
69
- export function checkLexicalDiversity(bodies, config) {
70
- // R5: default ON — only an explicit opt-out disables the check.
71
- if (config.enabled === false || config.lexicalDiversityCheck === false) {
72
- return { lowDiversity: false };
73
- }
74
- if (bodies.length === 0)
75
- return { lowDiversity: false };
76
- // Average bigram diversity across all bodies in the cluster.
77
- const avg = bodies.reduce((sum, b) => sum + computeBigramDiversity(b), 0) / bodies.length;
78
- const DIVERSITY_FLOOR = 0.3;
79
- if (avg < DIVERSITY_FLOOR) {
80
- return { lowDiversity: true, diversity: avg };
81
- }
82
- return { lowDiversity: false };
83
- }