@jungjaehoon/mama-core 1.4.2 → 1.6.0

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 (181) hide show
  1. package/README.md +48 -10
  2. package/db/migrations/026-create-canonical-entity-tables.sql +99 -0
  3. package/db/migrations/027-create-entity-review-support.sql +44 -0
  4. package/db/migrations/028-create-entity-audit-tables.sql +45 -0
  5. package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
  6. package/db/migrations/030-case-first-memory-substrate.sql +901 -0
  7. package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
  8. package/db/migrations/032-add-memory-provenance-columns.sql +21 -0
  9. package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
  10. package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
  11. package/db/migrations/035-create-twin-edges.sql +63 -0
  12. package/db/migrations/036-create-agent-situation-packets.sql +61 -0
  13. package/dist/agent-graph/alias-write.d.ts +3 -0
  14. package/dist/agent-graph/alias-write.js +231 -0
  15. package/dist/agent-graph/entity-resolve.d.ts +3 -0
  16. package/dist/agent-graph/entity-resolve.js +275 -0
  17. package/dist/agent-graph/errors.d.ts +4 -0
  18. package/dist/agent-graph/errors.js +11 -0
  19. package/dist/agent-graph/graph-query.d.ts +5 -0
  20. package/dist/agent-graph/graph-query.js +359 -0
  21. package/dist/agent-graph/index.d.ts +6 -0
  22. package/dist/agent-graph/index.js +22 -0
  23. package/dist/agent-graph/types.d.ts +162 -0
  24. package/dist/agent-graph/types.js +3 -0
  25. package/dist/agent-situation/builder.d.ts +8 -0
  26. package/dist/agent-situation/builder.js +239 -0
  27. package/dist/agent-situation/cache-key.d.ts +4 -0
  28. package/dist/agent-situation/cache-key.js +64 -0
  29. package/dist/agent-situation/index.d.ts +7 -0
  30. package/dist/agent-situation/index.js +28 -0
  31. package/dist/agent-situation/packet-store.d.ts +27 -0
  32. package/dist/agent-situation/packet-store.js +228 -0
  33. package/dist/agent-situation/ranking-policy.d.ts +18 -0
  34. package/dist/agent-situation/ranking-policy.js +75 -0
  35. package/dist/agent-situation/source-readers.d.ts +52 -0
  36. package/dist/agent-situation/source-readers.js +300 -0
  37. package/dist/agent-situation/types.d.ts +195 -0
  38. package/dist/agent-situation/types.js +3 -0
  39. package/dist/canonicalize.d.ts +38 -0
  40. package/dist/canonicalize.js +106 -0
  41. package/dist/cases/case-links.d.ts +116 -0
  42. package/dist/cases/case-links.js +794 -0
  43. package/dist/cases/composition-overrides.d.ts +90 -0
  44. package/dist/cases/composition-overrides.js +724 -0
  45. package/dist/cases/corrections.d.ts +156 -0
  46. package/dist/cases/corrections.js +1045 -0
  47. package/dist/cases/freshness.d.ts +57 -0
  48. package/dist/cases/freshness.js +283 -0
  49. package/dist/cases/live-state.d.ts +48 -0
  50. package/dist/cases/live-state.js +256 -0
  51. package/dist/cases/membership-explain.d.ts +56 -0
  52. package/dist/cases/membership-explain.js +140 -0
  53. package/dist/cases/membership-matcher.d.ts +23 -0
  54. package/dist/cases/membership-matcher.js +193 -0
  55. package/dist/cases/merge-split.d.ts +61 -0
  56. package/dist/cases/merge-split.js +368 -0
  57. package/dist/cases/role-inference.d.ts +10 -0
  58. package/dist/cases/role-inference.js +85 -0
  59. package/dist/cases/search-rollup.d.ts +27 -0
  60. package/dist/cases/search-rollup.js +309 -0
  61. package/dist/cases/sqlite-transaction.d.ts +10 -0
  62. package/dist/cases/sqlite-transaction.js +62 -0
  63. package/dist/cases/store.d.ts +74 -0
  64. package/dist/cases/store.js +816 -0
  65. package/dist/cases/target-ref.d.ts +25 -0
  66. package/dist/cases/target-ref.js +22 -0
  67. package/dist/cases/timeline-range.d.ts +6 -0
  68. package/dist/cases/timeline-range.js +322 -0
  69. package/dist/cases/tombstone-sweeper.d.ts +13 -0
  70. package/dist/cases/tombstone-sweeper.js +128 -0
  71. package/dist/cases/types.d.ts +244 -0
  72. package/dist/cases/types.js +47 -0
  73. package/dist/cases/wiki-page-index.d.ts +50 -0
  74. package/dist/cases/wiki-page-index.js +350 -0
  75. package/dist/connectors/event-index.d.ts +45 -0
  76. package/dist/connectors/event-index.js +381 -0
  77. package/dist/connectors/raw-query.d.ts +18 -0
  78. package/dist/connectors/raw-query.js +305 -0
  79. package/dist/connectors/types.d.ts +109 -0
  80. package/dist/connectors/types.js +3 -0
  81. package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
  82. package/dist/db-adapter/node-sqlite-adapter.js +131 -0
  83. package/dist/db-manager.d.ts +12 -1
  84. package/dist/db-manager.js +60 -5
  85. package/dist/decision-formatter.d.ts +2 -0
  86. package/dist/decision-formatter.js +11 -8
  87. package/dist/edges/ref-validation.d.ts +8 -0
  88. package/dist/edges/ref-validation.js +262 -0
  89. package/dist/edges/store.d.ts +10 -0
  90. package/dist/edges/store.js +343 -0
  91. package/dist/edges/types.d.ts +86 -0
  92. package/dist/edges/types.js +18 -0
  93. package/dist/embedding-server/mobile/daemon.d.ts +1 -0
  94. package/dist/embedding-server/mobile/daemon.js +11 -1
  95. package/dist/embeddings.js +13 -0
  96. package/dist/entities/audit-metrics.d.ts +73 -0
  97. package/dist/entities/audit-metrics.js +128 -0
  98. package/dist/entities/candidate-generator.d.ts +9 -0
  99. package/dist/entities/candidate-generator.js +436 -0
  100. package/dist/entities/entity-impact.d.ts +41 -0
  101. package/dist/entities/entity-impact.js +140 -0
  102. package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
  103. package/dist/entities/entity-linked-decision-counts.js +29 -0
  104. package/dist/entities/entity-list.d.ts +23 -0
  105. package/dist/entities/entity-list.js +99 -0
  106. package/dist/entities/entity-orphan-list.d.ts +26 -0
  107. package/dist/entities/entity-orphan-list.js +91 -0
  108. package/dist/entities/entity-search.d.ts +21 -0
  109. package/dist/entities/entity-search.js +150 -0
  110. package/dist/entities/errors.d.ts +65 -0
  111. package/dist/entities/errors.js +124 -0
  112. package/dist/entities/exact-merge-backfill.d.ts +11 -0
  113. package/dist/entities/exact-merge-backfill.js +161 -0
  114. package/dist/entities/lineage-backfill.d.ts +12 -0
  115. package/dist/entities/lineage-backfill.js +101 -0
  116. package/dist/entities/lineage-store.d.ts +51 -0
  117. package/dist/entities/lineage-store.js +308 -0
  118. package/dist/entities/normalization.d.ts +14 -0
  119. package/dist/entities/normalization.js +63 -0
  120. package/dist/entities/policy-store.d.ts +17 -0
  121. package/dist/entities/policy-store.js +263 -0
  122. package/dist/entities/policy-types.d.ts +72 -0
  123. package/dist/entities/policy-types.js +59 -0
  124. package/dist/entities/projection.d.ts +8 -0
  125. package/dist/entities/projection.js +82 -0
  126. package/dist/entities/provenance-query.d.ts +33 -0
  127. package/dist/entities/provenance-query.js +167 -0
  128. package/dist/entities/read-identity.d.ts +31 -0
  129. package/dist/entities/read-identity.js +264 -0
  130. package/dist/entities/recall-bridge.d.ts +5 -0
  131. package/dist/entities/recall-bridge.js +171 -0
  132. package/dist/entities/resolution-engine.d.ts +15 -0
  133. package/dist/entities/resolution-engine.js +69 -0
  134. package/dist/entities/rollback-preview.d.ts +39 -0
  135. package/dist/entities/rollback-preview.js +235 -0
  136. package/dist/entities/source-locator.d.ts +3 -0
  137. package/dist/entities/source-locator.js +32 -0
  138. package/dist/entities/store.d.ts +75 -0
  139. package/dist/entities/store.js +465 -0
  140. package/dist/entities/types.d.ts +162 -0
  141. package/dist/entities/types.js +30 -0
  142. package/dist/index.d.ts +62 -3
  143. package/dist/index.js +116 -26
  144. package/dist/mama-api.d.ts +53 -35
  145. package/dist/mama-api.js +437 -78
  146. package/dist/memory/api.d.ts +26 -38
  147. package/dist/memory/api.js +772 -68
  148. package/dist/memory/event-store.d.ts +3 -0
  149. package/dist/memory/event-store.js +21 -2
  150. package/dist/memory/provenance-audit.d.ts +22 -0
  151. package/dist/memory/provenance-audit.js +112 -0
  152. package/dist/memory/provenance-query.d.ts +11 -0
  153. package/dist/memory/provenance-query.js +193 -0
  154. package/dist/memory/provenance.d.ts +29 -0
  155. package/dist/memory/provenance.js +136 -0
  156. package/dist/memory/scope-backfill.d.ts +17 -0
  157. package/dist/memory/scope-backfill.js +77 -0
  158. package/dist/memory/types.d.ts +97 -16
  159. package/dist/model-runs/store.d.ts +13 -0
  160. package/dist/model-runs/store.js +457 -0
  161. package/dist/model-runs/tool-trace-store.d.ts +4 -0
  162. package/dist/model-runs/tool-trace-store.js +114 -0
  163. package/dist/model-runs/types.d.ts +69 -0
  164. package/dist/model-runs/types.js +5 -0
  165. package/dist/runtime/trusted-provenance.d.ts +3 -0
  166. package/dist/runtime/trusted-provenance.js +6 -0
  167. package/dist/search/feedback-store.d.ts +59 -0
  168. package/dist/search/feedback-store.js +211 -0
  169. package/dist/search/question-type.d.ts +5 -0
  170. package/dist/search/question-type.js +41 -0
  171. package/dist/search/ranker-features.d.ts +23 -0
  172. package/dist/search/ranker-features.js +104 -0
  173. package/dist/search/ranker-rescore.d.ts +31 -0
  174. package/dist/search/ranker-rescore.js +226 -0
  175. package/dist/search/ranker-trainer.d.ts +94 -0
  176. package/dist/search/ranker-trainer.js +860 -0
  177. package/dist/search/search-quality.d.ts +36 -0
  178. package/dist/search/search-quality.js +29 -0
  179. package/dist/test-utils.d.ts +14 -0
  180. package/dist/test-utils.js +52 -0
  181. package/package.json +28 -3
@@ -0,0 +1,860 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.featureSetVersion = exports.SEARCH_RANKER_FEATURE_SET_VERSION = exports.RANKER_QUALITY_FIXTURES = void 0;
4
+ exports.trainOfflineRanker = trainOfflineRanker;
5
+ exports.scoreWithRankerModel = scoreWithRankerModel;
6
+ exports.evaluateAgainstBaselines = evaluateAgainstBaselines;
7
+ exports.insertRankerModelVersion = insertRankerModelVersion;
8
+ exports.activateRankerModel = activateRankerModel;
9
+ exports.ndcgAtK = ndcgAtK;
10
+ exports.mrr = mrr;
11
+ exports.seedQualityFixtureFeedback = seedQualityFixtureFeedback;
12
+ const node_crypto_1 = require("node:crypto");
13
+ const feedback_store_js_1 = require("./feedback-store.js");
14
+ const ranker_features_js_1 = require("./ranker-features.js");
15
+ Object.defineProperty(exports, "featureSetVersion", { enumerable: true, get: function () { return ranker_features_js_1.featureSetVersion; } });
16
+ Object.defineProperty(exports, "SEARCH_RANKER_FEATURE_SET_VERSION", { enumerable: true, get: function () { return ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION; } });
17
+ const question_type_js_1 = require("./question-type.js");
18
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
19
+ const LEARNING_RATE = 0.05;
20
+ const MINI_BATCH_SIZE = 32;
21
+ const MAX_EPOCHS = 500;
22
+ const EARLY_STOP_DELTA = 1e-5;
23
+ const MIN_NEUTRAL_PER_QUERY = 3;
24
+ // 0.01 is a small but meaningful margin across 8 fixture queries: it blocks
25
+ // tie/noise wins without demanding a large jump on a tiny evaluation set.
26
+ const RANKER_QUALITY_MARGIN = 0.01;
27
+ function seededShuffle(items, seed) {
28
+ const shuffled = [...items];
29
+ let state = seed >>> 0;
30
+ for (let index = shuffled.length - 1; index > 0; index -= 1) {
31
+ state = (state * 1664525 + 1013904223) >>> 0;
32
+ const swapIndex = state % (index + 1);
33
+ const current = shuffled[index];
34
+ shuffled[index] = shuffled[swapIndex];
35
+ shuffled[swapIndex] = current;
36
+ }
37
+ return shuffled;
38
+ }
39
+ function reorderFixtureCandidates(query, candidates) {
40
+ if (candidates.length <= 1) {
41
+ return [...candidates];
42
+ }
43
+ const seed = Array.from(query).reduce((total, char) => total + char.charCodeAt(0), 0);
44
+ return seededShuffle(candidates, seed);
45
+ }
46
+ const RAW_RANKER_QUALITY_FIXTURES = [
47
+ {
48
+ query: 'fix the stale case status correction',
49
+ question_type: 'correction',
50
+ candidates: [
51
+ {
52
+ id: 'correction-good-1',
53
+ source_type: 'decision',
54
+ bm25_score: 0.18,
55
+ vector_score: 0.25,
56
+ case_rollup_score: 0.3,
57
+ recency_score: 0.4,
58
+ relevance: 3,
59
+ },
60
+ {
61
+ id: 'correction-bad-1',
62
+ source_type: 'wiki_page',
63
+ bm25_score: 0.95,
64
+ vector_score: 0.86,
65
+ case_rollup_score: 0.2,
66
+ recency_score: 0.2,
67
+ relevance: 0,
68
+ },
69
+ {
70
+ id: 'correction-mid-1',
71
+ source_type: 'case',
72
+ bm25_score: 0.45,
73
+ vector_score: 0.42,
74
+ case_rollup_score: 0.35,
75
+ recency_score: 0.1,
76
+ relevance: 1,
77
+ },
78
+ {
79
+ id: 'correction-bad-2',
80
+ source_type: 'connector_event',
81
+ bm25_score: 0.74,
82
+ vector_score: 0.71,
83
+ case_rollup_score: 0.25,
84
+ recency_score: 0.3,
85
+ relevance: 0,
86
+ },
87
+ {
88
+ id: 'correction-bad-3',
89
+ source_type: 'checkpoint',
90
+ bm25_score: 0.61,
91
+ vector_score: 0.67,
92
+ case_rollup_score: 0.15,
93
+ recency_score: 0.2,
94
+ relevance: 0,
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ query: 'revert the mistaken merge decision',
100
+ question_type: 'correction',
101
+ candidates: [
102
+ {
103
+ id: 'correction-good-2',
104
+ source_type: 'decision',
105
+ bm25_score: 0.22,
106
+ vector_score: 0.2,
107
+ case_rollup_score: 0.33,
108
+ recency_score: 0.2,
109
+ relevance: 3,
110
+ },
111
+ {
112
+ id: 'correction-bad-4',
113
+ source_type: 'connector_event',
114
+ bm25_score: 0.92,
115
+ vector_score: 0.88,
116
+ case_rollup_score: 0.2,
117
+ recency_score: 0.3,
118
+ relevance: 0,
119
+ },
120
+ {
121
+ id: 'correction-mid-2',
122
+ source_type: 'case',
123
+ bm25_score: 0.5,
124
+ vector_score: 0.49,
125
+ case_rollup_score: 0.38,
126
+ recency_score: 0.1,
127
+ relevance: 1,
128
+ },
129
+ {
130
+ id: 'correction-bad-5',
131
+ source_type: 'wiki_page',
132
+ bm25_score: 0.7,
133
+ vector_score: 0.76,
134
+ case_rollup_score: 0.2,
135
+ recency_score: 0.2,
136
+ relevance: 0,
137
+ },
138
+ {
139
+ id: 'correction-bad-6',
140
+ source_type: 'checkpoint',
141
+ bm25_score: 0.6,
142
+ vector_score: 0.57,
143
+ case_rollup_score: 0.19,
144
+ recency_score: 0.2,
145
+ relevance: 0,
146
+ },
147
+ ],
148
+ },
149
+ {
150
+ query: 'find the Obsidian doc for the case',
151
+ question_type: 'artifact',
152
+ candidates: [
153
+ {
154
+ id: 'artifact-good-1',
155
+ source_type: 'wiki_page',
156
+ bm25_score: 0.21,
157
+ vector_score: 0.28,
158
+ case_rollup_score: 0.3,
159
+ recency_score: 0.2,
160
+ relevance: 3,
161
+ },
162
+ {
163
+ id: 'artifact-bad-1',
164
+ source_type: 'decision',
165
+ bm25_score: 0.96,
166
+ vector_score: 0.87,
167
+ case_rollup_score: 0.21,
168
+ recency_score: 0.4,
169
+ relevance: 0,
170
+ },
171
+ {
172
+ id: 'artifact-mid-1',
173
+ source_type: 'case',
174
+ bm25_score: 0.44,
175
+ vector_score: 0.47,
176
+ case_rollup_score: 0.36,
177
+ recency_score: 0.2,
178
+ relevance: 1,
179
+ },
180
+ {
181
+ id: 'artifact-bad-2',
182
+ source_type: 'connector_event',
183
+ bm25_score: 0.73,
184
+ vector_score: 0.74,
185
+ case_rollup_score: 0.18,
186
+ recency_score: 0.3,
187
+ relevance: 0,
188
+ },
189
+ {
190
+ id: 'artifact-bad-3',
191
+ source_type: 'checkpoint',
192
+ bm25_score: 0.68,
193
+ vector_score: 0.62,
194
+ case_rollup_score: 0.19,
195
+ recency_score: 0.2,
196
+ relevance: 0,
197
+ },
198
+ ],
199
+ },
200
+ {
201
+ query: 'which pdf described the rollout',
202
+ question_type: 'artifact',
203
+ candidates: [
204
+ {
205
+ id: 'artifact-good-2',
206
+ source_type: 'wiki_page',
207
+ bm25_score: 0.19,
208
+ vector_score: 0.26,
209
+ case_rollup_score: 0.31,
210
+ recency_score: 0.2,
211
+ relevance: 3,
212
+ },
213
+ {
214
+ id: 'artifact-bad-4',
215
+ source_type: 'decision',
216
+ bm25_score: 0.91,
217
+ vector_score: 0.89,
218
+ case_rollup_score: 0.21,
219
+ recency_score: 0.3,
220
+ relevance: 0,
221
+ },
222
+ {
223
+ id: 'artifact-mid-2',
224
+ source_type: 'case',
225
+ bm25_score: 0.49,
226
+ vector_score: 0.43,
227
+ case_rollup_score: 0.35,
228
+ recency_score: 0.3,
229
+ relevance: 1,
230
+ },
231
+ {
232
+ id: 'artifact-bad-5',
233
+ source_type: 'connector_event',
234
+ bm25_score: 0.76,
235
+ vector_score: 0.72,
236
+ case_rollup_score: 0.2,
237
+ recency_score: 0.2,
238
+ relevance: 0,
239
+ },
240
+ {
241
+ id: 'artifact-bad-6',
242
+ source_type: 'checkpoint',
243
+ bm25_score: 0.63,
244
+ vector_score: 0.61,
245
+ case_rollup_score: 0.19,
246
+ recency_score: 0.2,
247
+ relevance: 0,
248
+ },
249
+ ],
250
+ },
251
+ {
252
+ query: 'when did the blocker first happen',
253
+ question_type: 'timeline',
254
+ candidates: [
255
+ {
256
+ id: 'timeline-good-1',
257
+ source_type: 'connector_event',
258
+ bm25_score: 0.2,
259
+ vector_score: 0.29,
260
+ case_rollup_score: 0.3,
261
+ recency_score: 0.2,
262
+ relevance: 3,
263
+ },
264
+ {
265
+ id: 'timeline-bad-1',
266
+ source_type: 'decision',
267
+ bm25_score: 0.93,
268
+ vector_score: 0.91,
269
+ case_rollup_score: 0.18,
270
+ recency_score: 0.3,
271
+ relevance: 0,
272
+ },
273
+ {
274
+ id: 'timeline-mid-1',
275
+ source_type: 'checkpoint',
276
+ bm25_score: 0.48,
277
+ vector_score: 0.46,
278
+ case_rollup_score: 0.34,
279
+ recency_score: 0.2,
280
+ relevance: 1,
281
+ },
282
+ {
283
+ id: 'timeline-bad-2',
284
+ source_type: 'case',
285
+ bm25_score: 0.78,
286
+ vector_score: 0.74,
287
+ case_rollup_score: 0.18,
288
+ recency_score: 0.2,
289
+ relevance: 0,
290
+ },
291
+ {
292
+ id: 'timeline-bad-3',
293
+ source_type: 'wiki_page',
294
+ bm25_score: 0.64,
295
+ vector_score: 0.63,
296
+ case_rollup_score: 0.19,
297
+ recency_score: 0.2,
298
+ relevance: 0,
299
+ },
300
+ ],
301
+ },
302
+ {
303
+ query: 'history before the regression',
304
+ question_type: 'timeline',
305
+ candidates: [
306
+ {
307
+ id: 'timeline-good-2',
308
+ source_type: 'connector_event',
309
+ bm25_score: 0.23,
310
+ vector_score: 0.27,
311
+ case_rollup_score: 0.31,
312
+ recency_score: 0.2,
313
+ relevance: 3,
314
+ },
315
+ {
316
+ id: 'timeline-bad-4',
317
+ source_type: 'decision',
318
+ bm25_score: 0.94,
319
+ vector_score: 0.9,
320
+ case_rollup_score: 0.18,
321
+ recency_score: 0.3,
322
+ relevance: 0,
323
+ },
324
+ {
325
+ id: 'timeline-mid-2',
326
+ source_type: 'checkpoint',
327
+ bm25_score: 0.47,
328
+ vector_score: 0.45,
329
+ case_rollup_score: 0.36,
330
+ recency_score: 0.2,
331
+ relevance: 1,
332
+ },
333
+ {
334
+ id: 'timeline-bad-5',
335
+ source_type: 'case',
336
+ bm25_score: 0.75,
337
+ vector_score: 0.77,
338
+ case_rollup_score: 0.19,
339
+ recency_score: 0.2,
340
+ relevance: 0,
341
+ },
342
+ {
343
+ id: 'timeline-bad-6',
344
+ source_type: 'wiki_page',
345
+ bm25_score: 0.62,
346
+ vector_score: 0.64,
347
+ case_rollup_score: 0.18,
348
+ recency_score: 0.2,
349
+ relevance: 0,
350
+ },
351
+ ],
352
+ },
353
+ {
354
+ query: 'current status of the rollout',
355
+ question_type: 'status',
356
+ candidates: [
357
+ {
358
+ id: 'status-good-1',
359
+ source_type: 'case',
360
+ bm25_score: 0.2,
361
+ vector_score: 0.24,
362
+ case_rollup_score: 0.36,
363
+ recency_score: 0.2,
364
+ relevance: 3,
365
+ },
366
+ {
367
+ id: 'status-bad-1',
368
+ source_type: 'decision',
369
+ bm25_score: 0.97,
370
+ vector_score: 0.9,
371
+ case_rollup_score: 0.2,
372
+ recency_score: 0.3,
373
+ relevance: 0,
374
+ },
375
+ {
376
+ id: 'status-mid-1',
377
+ source_type: 'checkpoint',
378
+ bm25_score: 0.46,
379
+ vector_score: 0.47,
380
+ case_rollup_score: 0.34,
381
+ recency_score: 0.2,
382
+ relevance: 1,
383
+ },
384
+ {
385
+ id: 'status-bad-2',
386
+ source_type: 'connector_event',
387
+ bm25_score: 0.74,
388
+ vector_score: 0.73,
389
+ case_rollup_score: 0.19,
390
+ recency_score: 0.2,
391
+ relevance: 0,
392
+ },
393
+ {
394
+ id: 'status-bad-3',
395
+ source_type: 'wiki_page',
396
+ bm25_score: 0.65,
397
+ vector_score: 0.62,
398
+ case_rollup_score: 0.18,
399
+ recency_score: 0.2,
400
+ relevance: 0,
401
+ },
402
+ ],
403
+ },
404
+ {
405
+ query: 'latest progress on case assembly',
406
+ question_type: 'status',
407
+ candidates: [
408
+ {
409
+ id: 'status-good-2',
410
+ source_type: 'case',
411
+ bm25_score: 0.22,
412
+ vector_score: 0.23,
413
+ case_rollup_score: 0.37,
414
+ recency_score: 0.2,
415
+ relevance: 3,
416
+ },
417
+ {
418
+ id: 'status-bad-4',
419
+ source_type: 'decision',
420
+ bm25_score: 0.92,
421
+ vector_score: 0.91,
422
+ case_rollup_score: 0.2,
423
+ recency_score: 0.3,
424
+ relevance: 0,
425
+ },
426
+ {
427
+ id: 'status-mid-2',
428
+ source_type: 'checkpoint',
429
+ bm25_score: 0.48,
430
+ vector_score: 0.45,
431
+ case_rollup_score: 0.34,
432
+ recency_score: 0.2,
433
+ relevance: 1,
434
+ },
435
+ {
436
+ id: 'status-bad-5',
437
+ source_type: 'connector_event',
438
+ bm25_score: 0.77,
439
+ vector_score: 0.75,
440
+ case_rollup_score: 0.2,
441
+ recency_score: 0.2,
442
+ relevance: 0,
443
+ },
444
+ {
445
+ id: 'status-bad-6',
446
+ source_type: 'wiki_page',
447
+ bm25_score: 0.66,
448
+ vector_score: 0.63,
449
+ case_rollup_score: 0.18,
450
+ recency_score: 0.2,
451
+ relevance: 0,
452
+ },
453
+ ],
454
+ },
455
+ ];
456
+ exports.RANKER_QUALITY_FIXTURES = RAW_RANKER_QUALITY_FIXTURES.map((fixture) => ({
457
+ ...fixture,
458
+ candidates: reorderFixtureCandidates(fixture.query, fixture.candidates),
459
+ }));
460
+ function asSourceForFeedback(sourceType) {
461
+ switch (sourceType) {
462
+ case 'wiki_page':
463
+ return 'wiki_page';
464
+ case 'case':
465
+ return 'case';
466
+ case 'connector_event':
467
+ return 'connector_event';
468
+ case 'checkpoint':
469
+ return 'checkpoint';
470
+ default:
471
+ return 'decision';
472
+ }
473
+ }
474
+ function parseDate(input, field) {
475
+ const date = new Date(input);
476
+ if (Number.isNaN(date.getTime())) {
477
+ throw new Error(`${field} must be a valid ISO timestamp`);
478
+ }
479
+ return date;
480
+ }
481
+ function iso(date) {
482
+ return date.toISOString();
483
+ }
484
+ function effectiveWindow(input) {
485
+ const retentionDays = (0, feedback_store_js_1.getFeedbackRetentionDays)(input.adapter);
486
+ const now = input.now ?? new Date();
487
+ const retentionCutoff = new Date(now.getTime() - retentionDays * MS_PER_DAY);
488
+ const until = input.until ? parseDate(input.until, 'until') : now;
489
+ const since = input.since ? parseDate(input.since, 'since') : retentionCutoff;
490
+ if (since.getTime() > until.getTime()) {
491
+ throw new Error('since must be before until');
492
+ }
493
+ const retentionWarning = Boolean(input.since && since.getTime() < retentionCutoff.getTime());
494
+ if (retentionWarning) {
495
+ console.warn(JSON.stringify({
496
+ event: 'training_window_exceeds_retention',
497
+ since: iso(since),
498
+ retention_cutoff_at: iso(retentionCutoff),
499
+ }));
500
+ }
501
+ return {
502
+ since: iso(since),
503
+ until: iso(until),
504
+ retention_cutoff_at: iso(retentionCutoff),
505
+ retention_days_at_train_time: retentionDays,
506
+ retention_warning: retentionWarning,
507
+ };
508
+ }
509
+ function labelForKind(kind) {
510
+ if (kind === 'accept' || kind === 'click') {
511
+ return 1;
512
+ }
513
+ if (kind === 'reject' || kind === 'hide' || kind === 'shown') {
514
+ return 0;
515
+ }
516
+ return null;
517
+ }
518
+ function rowToExample(row) {
519
+ const label = labelForKind(row.feedback_kind);
520
+ if (label === null) {
521
+ return null;
522
+ }
523
+ const features = (0, ranker_features_js_1.serializeFeatures)((0, ranker_features_js_1.extractFeatures)({
524
+ ...row,
525
+ source_type: row.result_source_type,
526
+ rank_position: row.rank_position,
527
+ }, row.query, { question_type: row.question_type }));
528
+ return {
529
+ query: row.query,
530
+ question_type: row.question_type,
531
+ features,
532
+ label,
533
+ feedback_kind: row.feedback_kind,
534
+ created_at: row.created_at,
535
+ feedback_id: row.feedback_id,
536
+ };
537
+ }
538
+ function capNeutralRows(rows) {
539
+ const byQuery = new Map();
540
+ for (const row of rows) {
541
+ const key = row.query_hash_hex || row.query;
542
+ const current = byQuery.get(key) ?? [];
543
+ current.push(row);
544
+ byQuery.set(key, current);
545
+ }
546
+ const capped = [];
547
+ for (const groupRows of byQuery.values()) {
548
+ const explicit = groupRows.filter((row) => row.feedback_kind !== 'shown');
549
+ const shown = groupRows.filter((row) => row.feedback_kind === 'shown');
550
+ const neutralLimit = Math.max(explicit.length * 3, MIN_NEUTRAL_PER_QUERY);
551
+ capped.push(...explicit, ...shown.slice(0, neutralLimit));
552
+ }
553
+ capped.sort((left, right) => {
554
+ const byCreatedAt = left.created_at.localeCompare(right.created_at);
555
+ return byCreatedAt !== 0 ? byCreatedAt : left.feedback_id.localeCompare(right.feedback_id);
556
+ });
557
+ return capped;
558
+ }
559
+ function sigmoid(value) {
560
+ if (value >= 35) {
561
+ return 1;
562
+ }
563
+ if (value <= -35) {
564
+ return 0;
565
+ }
566
+ return 1 / (1 + Math.exp(-value));
567
+ }
568
+ function dot(left, right) {
569
+ const length = Math.min(left.length, right.length);
570
+ let total = 0;
571
+ for (let index = 0; index < length; index += 1) {
572
+ total += left[index] * right[index];
573
+ }
574
+ return total;
575
+ }
576
+ function hasBothLabels(examples) {
577
+ const labels = new Set(examples.map((example) => example.label));
578
+ return labels.has(0) && labels.has(1);
579
+ }
580
+ function trainWeights(examples, featureCount) {
581
+ const coefficients = new Array(featureCount).fill(0);
582
+ let intercept = 0;
583
+ if (examples.length === 0 || !hasBothLabels(examples)) {
584
+ return { coefficients, intercept };
585
+ }
586
+ for (let epoch = 0; epoch < MAX_EPOCHS; epoch += 1) {
587
+ const shuffledExamples = seededShuffle(examples, epoch + 1);
588
+ let epochDelta = 0;
589
+ let batchCount = 0;
590
+ for (let batchStart = 0; batchStart < shuffledExamples.length; batchStart += MINI_BATCH_SIZE) {
591
+ const batch = shuffledExamples.slice(batchStart, batchStart + MINI_BATCH_SIZE);
592
+ const gradients = new Array(featureCount).fill(0);
593
+ let interceptGradient = 0;
594
+ for (const example of batch) {
595
+ const prediction = sigmoid(dot(coefficients, example.features) + intercept);
596
+ const error = prediction - example.label;
597
+ for (let index = 0; index < featureCount; index += 1) {
598
+ gradients[index] += error * example.features[index];
599
+ }
600
+ interceptGradient += error;
601
+ }
602
+ let delta = 0;
603
+ for (let index = 0; index < featureCount; index += 1) {
604
+ const update = (LEARNING_RATE * gradients[index]) / batch.length;
605
+ coefficients[index] -= update;
606
+ delta += Math.abs(update);
607
+ }
608
+ const interceptUpdate = (LEARNING_RATE * interceptGradient) / batch.length;
609
+ intercept -= interceptUpdate;
610
+ delta += Math.abs(interceptUpdate);
611
+ epochDelta += delta;
612
+ batchCount += 1;
613
+ }
614
+ if (batchCount > 0 && epochDelta / batchCount < EARLY_STOP_DELTA) {
615
+ break;
616
+ }
617
+ }
618
+ return { coefficients, intercept };
619
+ }
620
+ function questionWeights(examples, featureCount, fallback) {
621
+ const output = {};
622
+ for (const questionType of question_type_js_1.QUESTION_TYPES) {
623
+ const subset = examples.filter((example) => example.question_type === questionType);
624
+ output[questionType] = hasBothLabels(subset)
625
+ ? trainWeights(subset, featureCount).coefficients
626
+ : [...fallback];
627
+ }
628
+ return output;
629
+ }
630
+ function modelIdFor(input) {
631
+ const compactTime = input.trainedAt.replace(/[^0-9]/g, '').slice(0, 17);
632
+ const uniqueSuffix = (0, node_crypto_1.randomBytes)(4).toString('hex');
633
+ return `ranker_${compactTime}_${input.rows}_${input.distinctQueries}_${uniqueSuffix}`;
634
+ }
635
+ async function trainOfflineRanker(input) {
636
+ const minFeedbackRows = input.minFeedbackRows ?? 1000;
637
+ const minDistinctQueries = input.minDistinctQueries ?? 10;
638
+ const window = effectiveWindow(input);
639
+ const rows = (0, feedback_store_js_1.listSearchFeedback)(input.adapter, {
640
+ since: window.since,
641
+ until: window.until,
642
+ });
643
+ const cappedRows = capNeutralRows(rows);
644
+ const examples = cappedRows
645
+ .map(rowToExample)
646
+ .filter((example) => Boolean(example));
647
+ const distinctQueries = new Set(examples.map((example) => example.query.trim().toLowerCase()))
648
+ .size;
649
+ const counts = {
650
+ feedbackRows: examples.length,
651
+ distinctQueries,
652
+ };
653
+ const examplesHaveBothLabels = hasBothLabels(examples);
654
+ if (counts.feedbackRows < minFeedbackRows ||
655
+ counts.distinctQueries < minDistinctQueries ||
656
+ !examplesHaveBothLabels) {
657
+ return {
658
+ status: 'insufficient_data',
659
+ effectiveWindow: window,
660
+ counts,
661
+ };
662
+ }
663
+ const featureCount = examples[0].features.length;
664
+ const trained = trainWeights(examples, featureCount);
665
+ const trainedAt = input.now ? input.now.toISOString() : new Date().toISOString();
666
+ const model = {
667
+ model_id: modelIdFor({
668
+ trainedAt,
669
+ rows: counts.feedbackRows,
670
+ distinctQueries: counts.distinctQueries,
671
+ }),
672
+ feature_set_version: input.featureSetVersion ?? (0, ranker_features_js_1.featureSetVersion)(),
673
+ trained_at: trainedAt,
674
+ training_rows_count: examples.length,
675
+ coefficients: trained.coefficients,
676
+ intercept: trained.intercept,
677
+ question_type_weights: questionWeights(examples, featureCount, trained.coefficients),
678
+ training_window: window,
679
+ };
680
+ return {
681
+ status: 'trained',
682
+ model,
683
+ effectiveWindow: window,
684
+ counts,
685
+ };
686
+ }
687
+ function rankByScore(items, score) {
688
+ return items
689
+ .map((item) => ({ item, score: score(item) }))
690
+ .sort((left, right) => {
691
+ const diff = right.score - left.score;
692
+ if (Math.abs(diff) > 1e-12) {
693
+ return diff;
694
+ }
695
+ return left.item.id.localeCompare(right.item.id);
696
+ })
697
+ .map((entry) => entry.item);
698
+ }
699
+ function reciprocalRankRanks(items, leftScore, rightScore) {
700
+ const left = rankByScore(items, leftScore);
701
+ const right = rankByScore(items, rightScore);
702
+ const leftRanks = new Map(left.map((item, index) => [item.id, index + 1]));
703
+ const rightRanks = new Map(right.map((item, index) => [item.id, index + 1]));
704
+ return rankByScore(items, (item) => {
705
+ const leftRank = leftRanks.get(item.id) ?? items.length;
706
+ const rightRank = rightRanks.get(item.id) ?? items.length;
707
+ return 1 / (60 + leftRank) + 1 / (60 + rightRank);
708
+ });
709
+ }
710
+ function scoreWithRankerModel(model, row, query, questionType) {
711
+ const features = (0, ranker_features_js_1.serializeFeatures)((0, ranker_features_js_1.extractFeatures)(row, query, { question_type: questionType }));
712
+ const coefficients = model.question_type_weights[questionType] ?? model.coefficients;
713
+ return sigmoid(dot(coefficients, features) + model.intercept);
714
+ }
715
+ function baselineMetrics(fixtures, ranker) {
716
+ const perQuery = {};
717
+ let ndcgTotal = 0;
718
+ let mrrTotal = 0;
719
+ for (const fixture of fixtures) {
720
+ const ranked = ranker(fixture);
721
+ const relevance = new Map(fixture.candidates.map((candidate) => [candidate.id, candidate.relevance]));
722
+ const relevantSet = new Set(fixture.candidates
723
+ .filter((candidate) => candidate.relevance > 0)
724
+ .map((candidate) => candidate.id));
725
+ const ndcg = ndcgAtK(ranked, relevance, 5);
726
+ const mrrScore = mrr(ranked, relevantSet);
727
+ perQuery[fixture.query] = { ndcg, mrr: mrrScore };
728
+ ndcgTotal += ndcg;
729
+ mrrTotal += mrrScore;
730
+ }
731
+ return {
732
+ ndcg: ndcgTotal / fixtures.length,
733
+ mrr: mrrTotal / fixtures.length,
734
+ per_query: perQuery,
735
+ };
736
+ }
737
+ async function evaluateAgainstBaselines(_input, model) {
738
+ const bm25 = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => candidate.bm25_score));
739
+ const vector = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => candidate.vector_score));
740
+ const rrf = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => reciprocalRankRanks(fixture.candidates, (candidate) => candidate.bm25_score, (candidate) => candidate.vector_score));
741
+ const logistic = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => 0.5 * candidate.bm25_score + 0.5 * candidate.vector_score));
742
+ const learned = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => scoreWithRankerModel(model, candidate, fixture.query, fixture.question_type)));
743
+ const bestBaselineNdcg = Math.max(bm25.ndcg, vector.ndcg, rrf.ndcg, logistic.ndcg);
744
+ const bestBaselineMrr = Math.max(bm25.mrr, vector.mrr, rrf.mrr, logistic.mrr);
745
+ return {
746
+ bm25,
747
+ vector,
748
+ rrf,
749
+ logistic,
750
+ learned,
751
+ passes: learned.ndcg >= bestBaselineNdcg + RANKER_QUALITY_MARGIN &&
752
+ learned.mrr >= bestBaselineMrr + RANKER_QUALITY_MARGIN,
753
+ };
754
+ }
755
+ function runTransaction(adapter, fn) {
756
+ if (!adapter.transaction) {
757
+ return fn();
758
+ }
759
+ const result = adapter.transaction(fn);
760
+ if (typeof result === 'function') {
761
+ return result();
762
+ }
763
+ return result;
764
+ }
765
+ async function insertRankerModelVersion(adapter, model, evaluation) {
766
+ const coefficientsPayload = JSON.stringify({
767
+ coefficients: model.coefficients,
768
+ intercept: model.intercept,
769
+ question_type_weights: model.question_type_weights,
770
+ training_rows_count: model.training_rows_count,
771
+ });
772
+ const metricsPayload = JSON.stringify({
773
+ learned: evaluation.learned,
774
+ passes: evaluation.passes,
775
+ });
776
+ const baselinePayload = JSON.stringify({
777
+ bm25: evaluation.bm25,
778
+ vector: evaluation.vector,
779
+ rrf: evaluation.rrf,
780
+ logistic: evaluation.logistic,
781
+ });
782
+ const trainingWindowPayload = JSON.stringify(model.training_window ?? {
783
+ since: null,
784
+ until: null,
785
+ retention_cutoff_at: null,
786
+ retention_days_at_train_time: null,
787
+ retention_warning: false,
788
+ });
789
+ adapter
790
+ .prepare(`
791
+ INSERT INTO ranker_model_versions (
792
+ model_id, model_version, feature_set_version, coefficients_json, metrics_json,
793
+ training_window_json, baseline_metrics_json, quality_gate_status, trained_at,
794
+ trained_by, active
795
+ )
796
+ VALUES (?, 'offline-logistic-v1', ?, ?, ?, ?, ?, ?, ?, 'mama train-ranker', 0)
797
+ `)
798
+ .run(model.model_id, model.feature_set_version, coefficientsPayload, metricsPayload, trainingWindowPayload, baselinePayload, evaluation.passes ? 'passed' : 'failed', model.trained_at);
799
+ return { model_id: model.model_id, active: false };
800
+ }
801
+ async function activateRankerModel(adapter, model_id) {
802
+ const row = adapter
803
+ .prepare(`
804
+ SELECT model_id, feature_set_version, quality_gate_status
805
+ FROM ranker_model_versions
806
+ WHERE model_id = ?
807
+ `)
808
+ .get(model_id);
809
+ if (!row) {
810
+ return 'not_found';
811
+ }
812
+ if (row.feature_set_version !== ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION) {
813
+ return 'feature_set_mismatch';
814
+ }
815
+ if (row.quality_gate_status !== 'passed') {
816
+ return 'quality_gate_failed';
817
+ }
818
+ runTransaction(adapter, () => {
819
+ adapter.prepare('UPDATE ranker_model_versions SET active = 0 WHERE active = 1').run();
820
+ adapter.prepare('UPDATE ranker_model_versions SET active = 1 WHERE model_id = ?').run(model_id);
821
+ });
822
+ return 'activated';
823
+ }
824
+ function ndcgAtK(results, relevanceMap, k) {
825
+ const gains = results.slice(0, k).map((result, index) => {
826
+ const relevance = relevanceMap.get(result.id) ?? 0;
827
+ return (2 ** relevance - 1) / Math.log2(index + 2);
828
+ });
829
+ const dcg = gains.reduce((sum, gain) => sum + gain, 0);
830
+ const ideal = Array.from(relevanceMap.values())
831
+ .sort((left, right) => right - left)
832
+ .slice(0, k)
833
+ .map((relevance, index) => (2 ** relevance - 1) / Math.log2(index + 2))
834
+ .reduce((sum, gain) => sum + gain, 0);
835
+ return ideal === 0 ? 0 : dcg / ideal;
836
+ }
837
+ function mrr(results, relevantSet) {
838
+ const index = results.findIndex((result) => relevantSet.has(result.id));
839
+ return index === -1 ? 0 : 1 / (index + 1);
840
+ }
841
+ function seedQualityFixtureFeedback(adapter, createdAt = '2026-04-18T00:00:00.000Z') {
842
+ let counter = 0;
843
+ for (const fixture of exports.RANKER_QUALITY_FIXTURES) {
844
+ for (const candidate of fixture.candidates) {
845
+ counter += 1;
846
+ const kind = candidate.relevance >= 3 ? 'accept' : 'reject';
847
+ adapter
848
+ .prepare(`
849
+ INSERT INTO search_feedback (
850
+ feedback_id, query, query_hash, question_type, result_source_type, result_source_id,
851
+ case_id, feedback_kind, rank_position, score_before, score_after, session_id,
852
+ actor, metadata_json, created_at, updated_at
853
+ )
854
+ VALUES (?, ?, zeroblob(32), ?, ?, ?, NULL, ?, ?, NULL, NULL, ?, 'test:ranker-fixture', '{}', ?, NULL)
855
+ `)
856
+ .run(`quality-${counter}`, fixture.query, fixture.question_type, asSourceForFeedback(candidate.source_type), candidate.id, kind, counter % 5, `fixture:${fixture.question_type}`, createdAt);
857
+ }
858
+ }
859
+ }
860
+ //# sourceMappingURL=ranker-trainer.js.map