pi-mega-compact 0.16.3 → 0.17.1

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 (207) hide show
  1. package/dist/config/vector-cortex.js +91 -0
  2. package/dist/dedup/digest.js +30 -0
  3. package/dist/dedup/l1-lsh-v2.js +60 -0
  4. package/dist/dedup/l1-lsh.js +52 -0
  5. package/dist/dedup/l1-minhash-v2.js +193 -0
  6. package/dist/dedup/l1-minhash.js +91 -0
  7. package/dist/dedup/l1-verify.js +54 -0
  8. package/dist/dedup/mmr.js +45 -0
  9. package/dist/dedup/normalize.js +39 -0
  10. package/dist/dedup/raptor/buildHistory.js +164 -0
  11. package/dist/dedup/raptor/guardrails.js +83 -0
  12. package/dist/dedup/raptor/incremental.js +304 -0
  13. package/dist/dedup/raptor/index.js +190 -0
  14. package/dist/dedup/raptor/kmeans.js +152 -0
  15. package/dist/dedup/raptor/multilevel.js +184 -0
  16. package/dist/dedup/raptor/retrieval.js +93 -0
  17. package/dist/dedup/raptor/summarizer.js +86 -0
  18. package/dist/dedup/raptor/tree.js +202 -0
  19. package/dist/dedup/topk.js +60 -0
  20. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +30 -0
  21. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +4 -14
  22. package/dist/extensions/dashboard-server/api-contracts/vector-cortex.js +12 -0
  23. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +11 -0
  24. package/dist/extensions/dashboard-server/routes-vector-cortex.js +292 -0
  25. package/dist/extensions/dashboard-server/routes.js +1 -0
  26. package/dist/extensions/dashboard-server/server.js +9 -1
  27. package/dist/extensions/mega-events/context-handler.js +11 -0
  28. package/dist/extensions/mega-runtime/vector-cortex-ledger.js +131 -0
  29. package/dist/extensions/mega-runtime/vector-cortex-safety.js +100 -0
  30. package/dist/src/config/vector-cortex.js +91 -0
  31. package/dist/src/config.js +6 -0
  32. package/dist/src/dedup/l1-lsh-v2.js +60 -0
  33. package/dist/src/dedup/l1-minhash-v2.js +193 -0
  34. package/dist/src/dedup/normalize.js +1 -1
  35. package/dist/src/vector-cortex/conformance/emit.js +46 -0
  36. package/dist/src/vector-cortex/conformance/manifest.js +235 -0
  37. package/dist/src/vector-cortex/conformance/runner.js +111 -0
  38. package/dist/src/vector-cortex/conformance/triadB-reader.js +95 -0
  39. package/dist/src/vector-cortex/eval/annotations.js +41 -0
  40. package/dist/src/vector-cortex/eval/metrics.js +86 -0
  41. package/dist/src/vector-cortex/eval/observer.js +61 -0
  42. package/dist/src/vector-cortex/eval/persist.js +67 -0
  43. package/dist/src/vector-cortex/eval/reader.js +29 -0
  44. package/dist/src/vector-cortex/eval/types.js +33 -0
  45. package/dist/src/vector-cortex/ledger/adapter.js +55 -0
  46. package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
  47. package/dist/src/vector-cortex/ledger/emit.js +53 -0
  48. package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
  49. package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
  50. package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
  51. package/dist/src/vector-cortex/ledger/store.js +184 -0
  52. package/dist/src/vector-cortex/ledger/types.js +52 -0
  53. package/dist/src/vector-cortex/ledger/validator.js +117 -0
  54. package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
  55. package/dist/src/vector-cortex/migrations/minhash-v2.js +209 -0
  56. package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
  57. package/dist/src/vector-cortex/replay/cut.js +123 -0
  58. package/dist/src/vector-cortex/replay/emit.js +58 -0
  59. package/dist/src/vector-cortex/replay/replay.js +260 -0
  60. package/dist/src/vector-cortex/replay/replayB.js +73 -0
  61. package/dist/src/vector-cortex/replay/types.js +52 -0
  62. package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
  63. package/dist/src/vector-cortex/resilience/breaker.js +12 -0
  64. package/dist/src/vector-cortex/resilience/emit.js +56 -0
  65. package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
  66. package/dist/src/vector-cortex/resilience/spool.js +12 -0
  67. package/dist/src/vector-cortex/resilience/types.js +32 -0
  68. package/dist/vector-cortex/conformance/emit.js +46 -0
  69. package/dist/vector-cortex/conformance/manifest.js +235 -0
  70. package/dist/vector-cortex/conformance/runner.js +111 -0
  71. package/dist/vector-cortex/conformance/triadB-reader.js +95 -0
  72. package/dist/vector-cortex/eval/annotations.js +41 -0
  73. package/dist/vector-cortex/eval/metrics.js +86 -0
  74. package/dist/vector-cortex/eval/observer.js +61 -0
  75. package/dist/vector-cortex/eval/persist.js +67 -0
  76. package/dist/vector-cortex/eval/reader.js +29 -0
  77. package/dist/vector-cortex/eval/types.js +33 -0
  78. package/dist/vector-cortex/ledger/adapter.js +55 -0
  79. package/dist/vector-cortex/ledger/compat-journal.js +206 -0
  80. package/dist/vector-cortex/ledger/emit.js +53 -0
  81. package/dist/vector-cortex/ledger/event-codec.js +72 -0
  82. package/dist/vector-cortex/ledger/event-codecB.js +76 -0
  83. package/dist/vector-cortex/ledger/sqlite.js +200 -0
  84. package/dist/vector-cortex/ledger/store.js +184 -0
  85. package/dist/vector-cortex/ledger/types.js +52 -0
  86. package/dist/vector-cortex/ledger/validator.js +117 -0
  87. package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
  88. package/dist/vector-cortex/migrations/minhash-v2.js +209 -0
  89. package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
  90. package/dist/vector-cortex/replay/cut.js +123 -0
  91. package/dist/vector-cortex/replay/emit.js +58 -0
  92. package/dist/vector-cortex/replay/replay.js +260 -0
  93. package/dist/vector-cortex/replay/replayB.js +73 -0
  94. package/dist/vector-cortex/replay/types.js +52 -0
  95. package/dist/vector-cortex/resilience/breaker-core.js +359 -0
  96. package/dist/vector-cortex/resilience/breaker.js +12 -0
  97. package/dist/vector-cortex/resilience/emit.js +56 -0
  98. package/dist/vector-cortex/resilience/spool-core.js +347 -0
  99. package/dist/vector-cortex/resilience/spool.js +12 -0
  100. package/dist/vector-cortex/resilience/types.js +32 -0
  101. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-BQXFF7UI.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-BQXFF7UI.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-BVtoz9Eg.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-BVtoz9Eg.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-trI2TIGy.js} +2 -2
  106. package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-trI2TIGy.js.map} +1 -1
  107. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-CNUbk9__.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-CNUbk9__.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-K-bd_CM-.js} +2 -2
  110. package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-K-bd_CM-.js.map} +1 -1
  111. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-CoEioKzx.js} +2 -2
  112. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-CoEioKzx.js.map} +1 -1
  113. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-CJo24ulv.js} +2 -2
  114. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-CJo24ulv.js.map} +1 -1
  115. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DCNlQWWP.js} +2 -2
  116. package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DCNlQWWP.js.map} +1 -1
  117. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-CQQlyIQ6.js} +2 -2
  118. package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-CQQlyIQ6.js.map} +1 -1
  119. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-Cqm82_yn.js} +2 -2
  120. package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-Cqm82_yn.js.map} +1 -1
  121. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-A8xTAdET.js} +2 -2
  122. package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-A8xTAdET.js.map} +1 -1
  123. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-CqaSgaZl.js} +2 -2
  124. package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-CqaSgaZl.js.map} +1 -1
  125. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C_f3q8GR.js} +2 -2
  126. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C_f3q8GR.js.map} +1 -1
  127. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-BEsafACo.js} +2 -2
  128. package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-BEsafACo.js.map} +1 -1
  129. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js +2 -0
  130. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js.map +1 -0
  131. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Bf01M2_Z.js} +2 -2
  132. package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Bf01M2_Z.js.map} +1 -1
  133. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-BEJPTk_E.js} +2 -2
  134. package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-BEJPTk_E.js.map} +1 -1
  135. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DK1V382s.js} +2 -2
  136. package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DK1V382s.js.map} +1 -1
  137. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CRtaPphL.js} +2 -2
  138. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CRtaPphL.js.map} +1 -1
  139. package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-CB6BslE4.js} +34 -29
  140. package/extensions/dashboard-client/dist/assets/index-CB6BslE4.js.map +1 -0
  141. package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
  142. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-C4FV6B9k.js} +2 -2
  143. package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-C4FV6B9k.js.map} +1 -1
  144. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-GBmeENMd.js} +2 -2
  145. package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-GBmeENMd.js.map} +1 -1
  146. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-BOKZGSn1.js} +2 -2
  147. package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-BOKZGSn1.js.map} +1 -1
  148. package/extensions/dashboard-client/dist/index.html +2 -2
  149. package/extensions/dashboard-client/src/App.tsx +2 -0
  150. package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
  151. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
  152. package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
  153. package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
  154. package/extensions/dashboard-client/tsconfig.json +1 -1
  155. package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
  156. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
  157. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  158. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
  159. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +41 -0
  160. package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
  161. package/extensions/dashboard-server/routes.ts +6 -0
  162. package/extensions/dashboard-server/server.ts +8 -0
  163. package/extensions/mega-events/context-handler.ts +16 -0
  164. package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
  165. package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
  166. package/package.json +2 -1
  167. package/src/config/vector-cortex.ts +98 -0
  168. package/src/config.ts +28 -0
  169. package/src/dedup/l1-lsh-v2.ts +78 -0
  170. package/src/dedup/l1-minhash-v2.ts +221 -0
  171. package/src/dedup/normalize.ts +1 -1
  172. package/src/store/sqlite/game-scores.ts +2 -1
  173. package/src/vector-cortex/conformance/emit.ts +56 -0
  174. package/src/vector-cortex/conformance/manifest.ts +298 -0
  175. package/src/vector-cortex/conformance/runner.ts +165 -0
  176. package/src/vector-cortex/conformance/triadB-reader.ts +110 -0
  177. package/src/vector-cortex/eval/annotations.ts +70 -0
  178. package/src/vector-cortex/eval/metrics.ts +113 -0
  179. package/src/vector-cortex/eval/observer.ts +86 -0
  180. package/src/vector-cortex/eval/persist.ts +73 -0
  181. package/src/vector-cortex/eval/reader.ts +45 -0
  182. package/src/vector-cortex/eval/types.ts +89 -0
  183. package/src/vector-cortex/ledger/adapter.ts +71 -0
  184. package/src/vector-cortex/ledger/compat-journal.ts +234 -0
  185. package/src/vector-cortex/ledger/emit.ts +86 -0
  186. package/src/vector-cortex/ledger/event-codec.ts +79 -0
  187. package/src/vector-cortex/ledger/event-codecB.ts +87 -0
  188. package/src/vector-cortex/ledger/sqlite.ts +265 -0
  189. package/src/vector-cortex/ledger/store.ts +380 -0
  190. package/src/vector-cortex/ledger/types.ts +141 -0
  191. package/src/vector-cortex/ledger/validator.ts +124 -0
  192. package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
  193. package/src/vector-cortex/migrations/minhash-v2.ts +263 -0
  194. package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
  195. package/src/vector-cortex/replay/cut.ts +169 -0
  196. package/src/vector-cortex/replay/emit.ts +89 -0
  197. package/src/vector-cortex/replay/replay.ts +308 -0
  198. package/src/vector-cortex/replay/replayB.ts +97 -0
  199. package/src/vector-cortex/replay/types.ts +145 -0
  200. package/src/vector-cortex/resilience/breaker-core.ts +444 -0
  201. package/src/vector-cortex/resilience/breaker.ts +22 -0
  202. package/src/vector-cortex/resilience/emit.ts +99 -0
  203. package/src/vector-cortex/resilience/spool-core.ts +411 -0
  204. package/src/vector-cortex/resilience/spool.ts +22 -0
  205. package/src/vector-cortex/resilience/types.ts +158 -0
  206. package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
  207. package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +0 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * buildHistory.ts — S42D RAPTOR build history + freshness.
3
+ *
4
+ * - Stores one structured row per tree build in `raptor_build_history`.
5
+ * - `computeCoherenceScore`: avg intra-cluster cosine similarity (0–1) for a
6
+ * built tree — a health/diagnostics metric, not a gating signal.
7
+ * - `isRaptorTreeFresh`: lets the build call site skip a rebuild when the last
8
+ * build is recent (within `freshnessHours`) and the checkpoint count has not
9
+ * drifted by more than 20%. Returns false when no history exists (forces a
10
+ * build) — the same default as a missing tree.
11
+ *
12
+ * No network. Pure SQLite + local cosine.
13
+ */
14
+ import { randomUUID } from "node:crypto";
15
+ import { openStore, withTx } from "../../store/sqlite/utils.js";
16
+ import { getStateDir } from "../../store.js";
17
+ import { normalizeSessionId } from "../../store.js";
18
+ import { cosineSimilarity } from "../../embedder.js";
19
+ /** Insert one build-history row. Idempotent on build_id (PK). */
20
+ export function insertBuildHistory(input, stateDir = getStateDir()) {
21
+ const db = openStore(stateDir);
22
+ const buildId = randomUUID();
23
+ const sid = normalizeSessionId(input.sessionId);
24
+ withTx(db, () => {
25
+ db.prepare(`INSERT INTO raptor_build_history
26
+ (build_id, session_id, state_dir, started_at, completed_at,
27
+ node_count, leaf_count, depth, config_json, coherence_score, timed_out)
28
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
+ ON CONFLICT(build_id) DO NOTHING`).run(buildId, sid, input.stateDir, input.startedAt, input.completedAt, input.nodeCount, input.leafCount, input.depth, input.configJson, input.coherenceScore, input.timedOut ? 1 : 0);
30
+ });
31
+ return buildId;
32
+ }
33
+ /** The most recent build row for a session (by completed_at), or null. */
34
+ export function getLatestBuild(sessionId, stateDir = getStateDir()) {
35
+ const db = openStore(stateDir);
36
+ const sid = normalizeSessionId(sessionId);
37
+ const row = db
38
+ .prepare(`SELECT build_id, session_id, state_dir, started_at, completed_at,
39
+ node_count, leaf_count, depth, config_json, coherence_score, timed_out
40
+ FROM raptor_build_history
41
+ WHERE session_id = ?
42
+ ORDER BY completed_at DESC
43
+ LIMIT 1`)
44
+ .get(sid);
45
+ if (!row)
46
+ return null;
47
+ return {
48
+ buildId: row.build_id,
49
+ sessionId: row.session_id,
50
+ stateDir: row.state_dir,
51
+ startedAt: row.started_at,
52
+ completedAt: row.completed_at,
53
+ nodeCount: row.node_count,
54
+ leafCount: row.leaf_count,
55
+ depth: row.depth,
56
+ configJson: row.config_json,
57
+ coherenceScore: row.coherence_score,
58
+ timedOut: row.timed_out === 1,
59
+ };
60
+ }
61
+ /** All build rows for a session, newest first. */
62
+ export function listBuildHistory(sessionId, stateDir = getStateDir()) {
63
+ const db = openStore(stateDir);
64
+ const sid = normalizeSessionId(sessionId);
65
+ const rows = db
66
+ .prepare(`SELECT build_id, session_id, state_dir, started_at, completed_at,
67
+ node_count, leaf_count, depth, config_json, coherence_score, timed_out
68
+ FROM raptor_build_history
69
+ WHERE session_id = ?
70
+ ORDER BY completed_at DESC`)
71
+ .all(sid);
72
+ return rows.map((row) => ({
73
+ buildId: row.build_id,
74
+ sessionId: row.session_id,
75
+ stateDir: row.state_dir,
76
+ startedAt: row.started_at,
77
+ completedAt: row.completed_at,
78
+ nodeCount: row.node_count,
79
+ leafCount: row.leaf_count,
80
+ depth: row.depth,
81
+ configJson: row.config_json,
82
+ coherenceScore: row.coherence_score,
83
+ timedOut: row.timed_out === 1,
84
+ }));
85
+ }
86
+ /**
87
+ * Compute the average intra-cluster cosine similarity for a RAPTOR tree.
88
+ * For each internal node, compute the mean pairwise cosine similarity of its
89
+ * children's embeddings. Average across all internal nodes. Returns 0–1
90
+ * (higher = more coherent clusters). A tree with no internal nodes returns 0.
91
+ *
92
+ * Child embeddings: a child that is itself an internal node contributes its own
93
+ * centroid. A child id not in `tree.nodes` is a raw leaf — leaves are not stored
94
+ * on RaptorNodes, so `leafEmbeddings` (id → embedding, from the build-time Leaf[]
95
+ * set) supplies the true leaf embedding; if absent, we fall back to the parent
96
+ * node's centroid. Passing `leafEmbeddings` is strongly recommended — without it
97
+ * every leaf collapses to its parent's centroid and the score inflates to ~1.0.
98
+ */
99
+ export function computeCoherenceScore(tree, leafEmbeddings) {
100
+ let sum = 0;
101
+ let count = 0;
102
+ for (const node of tree.nodes.values()) {
103
+ if (node.embedding.length === 0)
104
+ continue;
105
+ const childEmbeddings = [];
106
+ for (const cid of node.children) {
107
+ const child = tree.nodes.get(cid);
108
+ if (child && child.embedding.length > 0) {
109
+ childEmbeddings.push(child.embedding);
110
+ }
111
+ else {
112
+ // Raw leaf: use its true embedding if known, else parent centroid.
113
+ const leafEmb = leafEmbeddings?.get(cid);
114
+ childEmbeddings.push(leafEmb && leafEmb.length > 0 ? leafEmb : node.embedding);
115
+ }
116
+ }
117
+ if (childEmbeddings.length < 2)
118
+ continue; // need >=2 to measure spread
119
+ let pairSum = 0;
120
+ let pairCount = 0;
121
+ for (let i = 0; i < childEmbeddings.length; i++) {
122
+ for (let j = i + 1; j < childEmbeddings.length; j++) {
123
+ pairSum += cosineSimilarity(childEmbeddings[i], childEmbeddings[j]);
124
+ pairCount++;
125
+ }
126
+ }
127
+ if (pairCount > 0) {
128
+ sum += pairSum / pairCount;
129
+ count++;
130
+ }
131
+ }
132
+ return count > 0 ? sum / count : 0;
133
+ }
134
+ /**
135
+ * Check if the RAPTOR tree is fresh enough to skip a rebuild.
136
+ * Returns true iff:
137
+ * - a build-history row exists, AND
138
+ * - it completed within `freshnessHours`, AND
139
+ * - the checkpoint count has not changed by more than 20% since the build.
140
+ * A `freshnessHours` of 0 always returns false (rebuild every time) — the spec's
141
+ * "0 disables" escape hatch, symmetric to the S38 retry caps.
142
+ */
143
+ export function isRaptorTreeFresh(sessionId, stateDir, freshnessHours, currentCheckpointCount) {
144
+ if (freshnessHours <= 0)
145
+ return false;
146
+ const latest = getLatestBuild(sessionId, stateDir);
147
+ if (!latest)
148
+ return false;
149
+ const ageHours = (Date.now() - latest.completedAt) / 3_600_000;
150
+ if (ageHours > freshnessHours)
151
+ return false;
152
+ if (latest.leafCount <= 0)
153
+ return false;
154
+ const changeRatio = Math.abs(currentCheckpointCount - latest.leafCount) / latest.leafCount;
155
+ return changeRatio <= 0.2;
156
+ }
157
+ /** Clear build history for a session (used by tests + DR). */
158
+ export function clearBuildHistory(sessionId, stateDir = getStateDir()) {
159
+ const db = openStore(stateDir);
160
+ const sid = normalizeSessionId(sessionId);
161
+ withTx(db, () => {
162
+ db.prepare("DELETE FROM raptor_build_history WHERE session_id = ?").run(sid);
163
+ });
164
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * guardrails.ts — hallucination defense for RAPTOR summary nodes (Sprint 13, QA #16).
3
+ *
4
+ * Four layers gate a candidate summary before it may be marked high-quality:
5
+ * 1. Claim grounding — every claim in the summary maps to source text
6
+ * (no entity/claim appears that isn't supported by a source chunk).
7
+ * 2. Entity coverage — fraction of summary entities that are present in source.
8
+ * 3. Consistency — cosine(reEmbed(summary), cluster centroid) ≥ threshold.
9
+ * This is the HARD gate: a low score means the summary drifted from the
10
+ * source cluster, so we fall back to extractive (never serve a low-quality
11
+ * LLM summary).
12
+ * 4. Quality markers — 'high' | 'low' | 'extractive_fallback' assigned from the
13
+ * above.
14
+ *
15
+ * Pure functions, no network, no model. The consistency check uses the caller's
16
+ * embedder (the same local Embedder used everywhere else).
17
+ */
18
+ import { cosineSimilarity } from "../../embedder.js";
19
+ const ENTITY_RE = /\b([A-Z][a-zA-Z0-9_]{2,}|[a-z_]+_[a-z_]+|\d{2,})\b/g;
20
+ /** Extract candidate "entities"/tokens from a summary for grounding checks. */
21
+ export function extractEntities(text) {
22
+ const out = new Set();
23
+ for (const m of text.matchAll(ENTITY_RE))
24
+ out.add(m[1].toLowerCase());
25
+ return [...out];
26
+ }
27
+ /** Lowercase word set from a body of source text (for grounding lookups). */
28
+ export function sourceTokenSet(sources) {
29
+ const set = new Set();
30
+ for (const s of sources)
31
+ for (const w of s.toLowerCase().split(/\W+/))
32
+ if (w)
33
+ set.add(w);
34
+ return set;
35
+ }
36
+ /**
37
+ * Verify a summary against its sources + centroid.
38
+ *
39
+ * Faithfulness (QA #16): consistency is the hard gate. If the summary embedding
40
+ * is insufficiently similar to the cluster centroid, the summary is NOT faithful
41
+ * to the source — mark it 'extractive_fallback' so callers fall back to the
42
+ * deterministic extractive summary instead of serving a drifted LLM summary.
43
+ *
44
+ * grounding: every summary entity must appear in the source token set. A single
45
+ * un-grounded entity fails grounding (caught hallucination).
46
+ */
47
+ export function applyHallucinationGuardrails(input) {
48
+ const threshold = input.consistencyThreshold ?? 0.6;
49
+ const sourceTokens = input.sourceTokens;
50
+ // Layer 1 + 2: entity grounding & coverage.
51
+ const entities = extractEntities(input.summary);
52
+ let groundedCount = 0;
53
+ for (const e of entities) {
54
+ if (sourceTokens.has(e))
55
+ groundedCount++;
56
+ }
57
+ const entityCoverage = entities.length === 0 ? 1 : groundedCount / entities.length;
58
+ const grounded = entities.length === 0 || groundedCount === entities.length;
59
+ // Layer 3: consistency re-embed.
60
+ const summEmbed = input.embedder.embed(input.summary);
61
+ const consistency = cosineSimilarity(summEmbed, input.centroid);
62
+ // Layer 4: quality marker decision.
63
+ if (!grounded || consistency < threshold) {
64
+ return {
65
+ marker: "extractive_fallback",
66
+ entityCoverage,
67
+ consistency,
68
+ grounded,
69
+ reason: !grounded
70
+ ? "ungrounded entity in summary"
71
+ : `consistency ${consistency.toFixed(2)} < ${threshold} (drift from source)`,
72
+ };
73
+ }
74
+ const marker = entityCoverage >= 0.7 ? "high" : "low";
75
+ return { marker, entityCoverage, consistency, grounded, reason: "ok" };
76
+ }
77
+ /**
78
+ * Convenience: build a fixture summary that is deliberately un-grounded (used by
79
+ * tests to prove the guardrail CATCHES a hallucination). Not used in production.
80
+ */
81
+ export function makeUngroundedSummary(realSource, fakeEntity) {
82
+ return `${realSource.slice(0, 60)} The quarterly revenue doubled to ${fakeEntity}.`;
83
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * incremental.ts — incremental RAPTOR tree update (Sprint 26, #7).
3
+ *
4
+ * Instead of rebuilding the full tree (~2s per compaction), insert only the new
5
+ * checkpoints since the last build and recompute only the affected cluster
6
+ * assignments up the tree. Falls back to a full rebuild when the tree is
7
+ * missing, corrupted, or >50% of nodes are new.
8
+ *
9
+ * Non-fatal: every failure logs and returns null (caller falls back to full
10
+ * rebuild). PREVENT-PI-004: zero network; all operations are local SQLite + CPU.
11
+ */
12
+ import { cosineSimilarity } from "../../embedder.js";
13
+ import { defaultEmbedder as getDefaultEmbedder } from "../../embedder.js";
14
+ import { buildRaptorTree } from "./tree.js";
15
+ import { applyHallucinationGuardrails, sourceTokenSet } from "./guardrails.js";
16
+ import { meanVector } from "./kmeans.js";
17
+ import { summarizeCluster, extractiveClusterSummary } from "./summarizer.js";
18
+ import { saveRaptorTree, listRaptorNodes, } from "../../store/sqlite.js";
19
+ /** When >FRESH_THRESHOLD of existing leaves are new, a full rebuild is cheaper. */
20
+ const FRESH_THRESHOLD = 0.5;
21
+ /**
22
+ * Summarize + apply hallucination guardrails (mirrors tree.ts:summarizeInto).
23
+ * If the guardrail marks the summary as extractive_fallback (drift/hallucination),
24
+ * re-summarize with the deterministic extractive path so we never serve a
25
+ * drifted LLM summary from an incremental update.
26
+ */
27
+ function summarizeGuarded(messages, centroid, embedder, consistencyThreshold) {
28
+ let cs = summarizeCluster(messages);
29
+ const sources = messages.map((m) => m.text);
30
+ const guard = applyHallucinationGuardrails({
31
+ summary: cs.summary,
32
+ sources,
33
+ centroid,
34
+ embedder,
35
+ sourceTokens: sourceTokenSet(sources),
36
+ consistencyThreshold,
37
+ });
38
+ if (guard.marker === "extractive_fallback") {
39
+ cs = extractiveClusterSummary(messages);
40
+ }
41
+ return {
42
+ summary: cs.summary,
43
+ tokenEstimate: cs.tokenEstimate,
44
+ qualityMarker: guard.marker === "extractive_fallback" ? "low" : guard.marker,
45
+ };
46
+ }
47
+ /**
48
+ * Incrementally update a persisted RAPTOR tree with new leaves.
49
+ *
50
+ * 1. Reads the existing tree from the store.
51
+ * 2. Filters `newLeaves` to those NOT already covered by the tree.
52
+ * 3. If no new leaves → early-return (existing tree).
53
+ * 4. If no existing tree or >50% new → full rebuild.
54
+ * 5. For each new leaf, finds the best-matching Level-0 cluster node,
55
+ * reassigns (inserts into its children), updates that node's summary
56
+ * and centroid embedding.
57
+ * 6. Propagates changes up through parent levels to the root.
58
+ * 7. Persists the updated tree (full delete + reinsert in a tx).
59
+ *
60
+ * Never throws. Returns the updated tree, or null on failure/fallback.
61
+ */
62
+ export function incrementRaptorTree(existingLeaves, newLeaves, opts) {
63
+ const logger = opts.logger;
64
+ const builtAt = opts.builtAt ?? Date.now();
65
+ try {
66
+ // 1. Read existing tree.
67
+ const storedNodes = listRaptorNodes(opts.sessionId, opts.stateDir);
68
+ if (storedNodes.length === 0) {
69
+ logger?.info("raptor_incremental_no_tree", { sessionId: opts.sessionId });
70
+ return null; // no existing tree — caller falls back to full buildRaptorTree
71
+ }
72
+ // 2. Find new leaf ids not already in the tree.
73
+ const coveredLeafIds = new Set();
74
+ for (const n of storedNodes) {
75
+ for (const cid of n.children)
76
+ coveredLeafIds.add(cid);
77
+ }
78
+ const reallyNewLeaves = newLeaves.filter((l) => !coveredLeafIds.has(l.id));
79
+ const totalCovered = coveredLeafIds.size;
80
+ if (reallyNewLeaves.length === 0) {
81
+ logger?.info("raptor_incremental_no_new", { sessionId: opts.sessionId });
82
+ return rehydrateFromStored(storedNodes);
83
+ }
84
+ // 4. Fallback guard: >50% new → full rebuild.
85
+ if (totalCovered > 0 &&
86
+ reallyNewLeaves.length / totalCovered > FRESH_THRESHOLD) {
87
+ logger?.info("raptor_incremental_fallback_ratio", {
88
+ sessionId: opts.sessionId,
89
+ coveredNodes: totalCovered,
90
+ newLeaves: reallyNewLeaves.length,
91
+ threshold: FRESH_THRESHOLD,
92
+ });
93
+ return fullRebuild(existingLeaves, reallyNewLeaves, opts, logger);
94
+ }
95
+ // 5. Build the in-memory tree from stored nodes.
96
+ const tree = rehydrateFromStored(storedNodes);
97
+ if (!tree || tree.nodes.size === 0) {
98
+ logger?.info("raptor_incremental_rehydrate_failed", {
99
+ sessionId: opts.sessionId,
100
+ });
101
+ return fullRebuild(existingLeaves, reallyNewLeaves, opts, logger);
102
+ }
103
+ // Group stored nodes by level.
104
+ const levelGroups = new Map();
105
+ for (const n of storedNodes) {
106
+ const g = levelGroups.get(n.level);
107
+ if (g)
108
+ g.push(n);
109
+ else
110
+ levelGroups.set(n.level, [n]);
111
+ }
112
+ // Level-0 covers leaf ids directly.
113
+ const level0Nodes = levelGroups.get(0) ??
114
+ storedNodes.filter((n) => {
115
+ // A node is level 0 if its level is the minimum.
116
+ const minLevel = Math.min(...storedNodes.map((x) => x.level));
117
+ return n.level === minLevel;
118
+ });
119
+ if (level0Nodes.length === 0) {
120
+ // Single-root tree with no hierarchy: cannot increment, fall back.
121
+ return fullRebuild(existingLeaves, reallyNewLeaves, opts, logger);
122
+ }
123
+ // For each new leaf, assign to the nearest Level-0 cluster node.
124
+ const leafEmbeddings = new Map();
125
+ for (const l of reallyNewLeaves)
126
+ leafEmbeddings.set(l.id, l.embedding);
127
+ // Track which Level-0 nodes changed (by their stored id).
128
+ const affectedNodeIds = new Set();
129
+ for (const leaf of reallyNewLeaves) {
130
+ const emb = leafEmbeddings.get(leaf.id);
131
+ if (!emb || emb.length === 0)
132
+ continue;
133
+ // Find best matching Level-0 node.
134
+ let bestNode = null;
135
+ let bestSim = -Infinity;
136
+ for (const cn of level0Nodes) {
137
+ const sim = cosineSimilarity(emb, cn.embedding);
138
+ if (sim > bestSim) {
139
+ bestSim = sim;
140
+ bestNode = cn;
141
+ }
142
+ }
143
+ if (!bestNode)
144
+ continue;
145
+ // Add the leaf id to this node's children.
146
+ const updated = tree.nodes.get(bestNode.id);
147
+ if (updated) {
148
+ if (!updated.children.includes(leaf.id)) {
149
+ updated.children.push(leaf.id);
150
+ }
151
+ affectedNodeIds.add(bestNode.id);
152
+ }
153
+ }
154
+ if (affectedNodeIds.size === 0) {
155
+ // No matches found — fall back to full rebuild.
156
+ logger?.info("raptor_incremental_no_matches", {
157
+ sessionId: opts.sessionId,
158
+ });
159
+ return fullRebuild(existingLeaves, reallyNewLeaves, opts, logger);
160
+ }
161
+ // 6. Recompute embeddings and summaries for affected nodes, bottom-up.
162
+ // Build a map: leafId → leaf info for summarization.
163
+ const leafMap = new Map();
164
+ for (const l of existingLeaves)
165
+ leafMap.set(l.id, l);
166
+ for (const l of reallyNewLeaves)
167
+ leafMap.set(l.id, l);
168
+ // Recompute affected Level-0 nodes.
169
+ const embedder = opts.embedder ?? getDefaultEmbedder();
170
+ for (const nid of affectedNodeIds) {
171
+ const node = tree.nodes.get(nid);
172
+ if (!node)
173
+ continue;
174
+ const coveredLeaves = node.children
175
+ .map((cid) => leafMap.get(cid))
176
+ .filter((l) => !!l);
177
+ if (coveredLeaves.length > 0) {
178
+ node.embedding = meanVector(coveredLeaves.map((l) => l.embedding));
179
+ // Summarize: flatten messages from all covered leaves.
180
+ const messages = coveredLeaves.flatMap((l) => l.messages);
181
+ if (messages.length > 0) {
182
+ const cs = summarizeGuarded(messages, node.embedding, embedder, opts.consistencyThreshold);
183
+ node.summary = cs.summary;
184
+ node.tokenEstimate = cs.tokenEstimate;
185
+ }
186
+ }
187
+ }
188
+ // Propagate up: for each higher level, recompute nodes whose leaf set
189
+ // includes any newly inserted leaf. Since `children` stores flattened leaf
190
+ // ids (not node ids), we check intersection with the new leaf id set.
191
+ const newLeafIds = new Set(reallyNewLeaves.map((l) => l.id));
192
+ const sortedLevels = [...levelGroups.keys()].sort((a, b) => a - b);
193
+ for (const level of sortedLevels.slice(1)) {
194
+ const levelNodes = levelGroups.get(level) ?? [];
195
+ for (const stored of levelNodes) {
196
+ const node = tree.nodes.get(stored.id);
197
+ if (!node)
198
+ continue;
199
+ if (node.children.some((cid) => newLeafIds.has(cid))) {
200
+ // Recompute embedding as centroid of covered leaves.
201
+ const covered = node.children
202
+ .map((cid) => leafMap.get(cid))
203
+ .filter((l) => !!l);
204
+ if (covered.length > 0) {
205
+ node.embedding = meanVector(covered.map((l) => l.embedding));
206
+ const messages = covered.flatMap((l) => l.messages);
207
+ if (messages.length > 0) {
208
+ const cs = summarizeGuarded(messages, node.embedding, embedder, opts.consistencyThreshold);
209
+ node.summary = cs.summary;
210
+ node.tokenEstimate = cs.tokenEstimate;
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ // 7. Persist the full updated tree (delete + reinsert in a tx).
217
+ tree.builtAt = builtAt;
218
+ saveRaptorTree(opts.sessionId, tree, builtAt, opts.stateDir);
219
+ logger?.info("raptor_incremental_success", {
220
+ sessionId: opts.sessionId,
221
+ nodeCount: tree.nodes.size,
222
+ newLeaves: reallyNewLeaves.length,
223
+ affectedNodes: affectedNodeIds.size,
224
+ });
225
+ return tree;
226
+ }
227
+ catch (e) {
228
+ logger?.error("raptor_incremental_failed", {
229
+ sessionId: opts.sessionId,
230
+ error: String(e instanceof Error ? e.message : e),
231
+ });
232
+ return null;
233
+ }
234
+ }
235
+ // ---------------------------------------------------------------------------
236
+ // Helpers
237
+ // ---------------------------------------------------------------------------
238
+ /**
239
+ * Full rebuild fallback: merges existingLeaves + newLeaves, builds from scratch.
240
+ */
241
+ function fullRebuild(existingLeaves, newLeaves, opts, logger) {
242
+ logger?.info("raptor_incremental_full_rebuild", {
243
+ sessionId: opts.sessionId,
244
+ });
245
+ try {
246
+ // Merge: deduplicate by leaf id.
247
+ const seen = new Set();
248
+ const allLeaves = [];
249
+ for (const l of existingLeaves) {
250
+ if (!seen.has(l.id)) {
251
+ seen.add(l.id);
252
+ allLeaves.push(l);
253
+ }
254
+ }
255
+ for (const l of newLeaves) {
256
+ if (!seen.has(l.id)) {
257
+ seen.add(l.id);
258
+ allLeaves.push(l);
259
+ }
260
+ }
261
+ const tree = buildRaptorTree(allLeaves, {
262
+ embedder: opts.embedder ?? getDefaultEmbedder(),
263
+ budgetMs: opts.budgetMs,
264
+ clustersPerLevel: opts.clustersPerLevel,
265
+ consistencyThreshold: opts.consistencyThreshold,
266
+ });
267
+ const builtAt = opts.builtAt ?? Date.now();
268
+ saveRaptorTree(opts.sessionId, tree, builtAt, opts.stateDir);
269
+ return tree;
270
+ }
271
+ catch (e2) {
272
+ logger?.error("raptor_incremental_full_rebuild_failed", {
273
+ sessionId: opts.sessionId,
274
+ error: String(e2 instanceof Error ? e2.message : e2),
275
+ });
276
+ return null;
277
+ }
278
+ }
279
+ /** Rebuild the in-memory RaptorTree from stored nodes. */
280
+ function rehydrateFromStored(nodes) {
281
+ if (nodes.length === 0)
282
+ return null;
283
+ const root = nodes.reduce((best, n) => (!best || n.level > (best?.level ?? -1) ? n : best), null);
284
+ const tree = {
285
+ nodes: new Map(nodes.map((n) => [
286
+ n.id,
287
+ {
288
+ id: n.id,
289
+ level: n.level,
290
+ parentId: n.parentId,
291
+ children: n.children,
292
+ summary: n.summary,
293
+ embedding: n.embedding,
294
+ qualityMarker: n.qualityMarker,
295
+ tokenEstimate: n.tokenEstimate,
296
+ },
297
+ ])),
298
+ rootId: root?.id ?? null,
299
+ levels: Math.max(1, ...nodes.map((n) => n.level + 1)),
300
+ timedOut: root != null && root.level >= 99,
301
+ builtAt: nodes.reduce((max, n) => Math.max(max, n.builtAt), 0),
302
+ };
303
+ return tree;
304
+ }