hippo-memory 1.61.0 → 1.63.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 (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  /**
2
- * AT1 rejected-value tombstone — shared reject/unreject/list flow.
3
- * docs/plans/2026-08-15-at1-rejected-value-tombstone.md (T2, plan §4).
2
+ * Rejected-value tombstone: shared reject/unreject/list flow.
4
3
  *
5
4
  * The CLI (`hippo reject`/`rejections`/`unreject`) and the Context-based
6
5
  * `api.reject`/`api.unreject`/`api.listRejections` surfaces both need the
@@ -24,16 +23,7 @@ import { deleteEntryCore } from './store/delete-and-batch.js';
24
23
  import { heldTexts } from './same-text.js';
25
24
  import { mergedSuccessor } from './merged-row.js';
26
25
  import { rejectionDigest, normalizeValueForRejection, insertRejectedValue, deleteRejectedValue, listRejectedValues, } from './rejection.js';
27
- /**
28
- * `hippo reject` / `api.reject` core flow. ONE connection, one transaction:
29
- * insert the tombstone, enumerate + remove every live tenant row whose
30
- * normalized digest matches (kind-aware), one aggregate `reject_value`
31
- * audit, COMMIT. Then post-commit (mirrors the existing purge+reaper
32
- * pattern verbatim from api.archiveRaw, api.ts:1913-1938): best-effort
33
- * mirror purge per removed id, `mirror_cleaned_at` stamps for raw ids.
34
- * index.json itself is only refreshed by `rebuildIndex()`.
35
- */
36
- export function rejectValue(opts) {
26
+ function assertRejectOpts(opts) {
37
27
  if (!opts.reason.trim()) {
38
28
  throw new Error('reject requires a non-empty --reason (the tombstone stores no content; reason is its only identity).');
39
29
  }
@@ -41,10 +31,8 @@ export function rejectValue(opts) {
41
31
  throw new Error('reject requires either a memory id or --value.');
42
32
  }
43
33
  if (opts.memoryId !== undefined && opts.value !== undefined) {
44
- // P2 fix: the CLI's flag parser already refuses both forms together;
45
- // the shared flow itself didn't enforce it, so a direct api caller
46
- // passing both silently got the memoryId path with `value` ignored —
47
- // surprising for a caller who thought they were rejecting `value`.
34
+ // Enforced here, not only in the CLI parser, so a direct api caller passing both
35
+ // is refused instead of silently getting the memoryId path with `value` ignored.
48
36
  throw new Error('reject accepts either a memory id or --value, not both.');
49
37
  }
50
38
  if (opts.value !== undefined && normalizeValueForRejection(opts.value).length === 0) {
@@ -53,28 +41,122 @@ export function rejectValue(opts) {
53
41
  // and pollute the listing.
54
42
  throw new Error('reject --value requires non-empty content.');
55
43
  }
56
- const db = openStore(opts.hippoRoot);
57
- try {
58
- let content;
59
- if (opts.memoryId !== undefined) {
60
- // SAFETY: row's shape matches the two columns named in the SELECT above.
61
- const row = db
62
- .prepare(`SELECT content, tenant_id FROM memories WHERE id = ?`)
63
- .get(opts.memoryId);
64
- if (!row || row.tenant_id !== opts.tenantId) {
65
- throw new Error(`memory not found: ${opts.memoryId}`);
66
- }
67
- content = row.content;
44
+ }
45
+ function contentToReject(db, opts) {
46
+ if (opts.memoryId === undefined)
47
+ return opts.value;
48
+ // SAFETY: row's shape matches the two columns named in the SELECT above.
49
+ const row = db
50
+ .prepare(`SELECT content, tenant_id FROM memories WHERE id = ?`)
51
+ .get(opts.memoryId);
52
+ if (!row || row.tenant_id !== opts.tenantId) {
53
+ throw new Error(`memory not found: ${opts.memoryId}`);
54
+ }
55
+ return row.content;
56
+ }
57
+ function removeLiveRows(db, opts, holdsValue, removal) {
58
+ const { removedIds, removedRawIds, successors } = removal;
59
+ const merged = [];
60
+ for (const row of selectAllEntries(db, opts.tenantId)) {
61
+ if (!holdsValue(row.content)) {
62
+ if (heldTexts(row).some(holdsValue))
63
+ merged.push(row);
64
+ continue;
65
+ }
66
+ if (row.kind === 'raw') {
67
+ // Append-only trigger respected — archiveRawMemory is the only
68
+ // legitimate removal path for kind='raw', and its inner SAVEPOINT
69
+ // composes safely inside this BEGIN/COMMIT.
70
+ archiveRawMemory(db, row.id, { reason: opts.reason, who: opts.actor });
71
+ removedRawIds.push(row.id);
68
72
  }
69
73
  else {
70
- content = opts.value;
74
+ // suppressForgetAudit: the aggregate reject_value row below is the
75
+ // trail for these removals, not N individual forget rows.
76
+ deleteEntryCore(db, row.id, { actor: opts.actor, suppressForgetAudit: true });
77
+ }
78
+ removedIds.push(row.id);
79
+ }
80
+ for (const row of merged) {
81
+ const successor = mergedSuccessor(row, holdsValue, new Set(removedIds));
82
+ deleteEntryCore(db, row.id, { actor: opts.actor, suppressForgetAudit: true });
83
+ removedIds.push(row.id);
84
+ if (!successor)
85
+ continue;
86
+ const kept = stampOriginProject(opts.hippoRoot, successor);
87
+ writeEntryDbOnly(db, kept, { actor: opts.actor });
88
+ successors.push(kept);
89
+ }
90
+ }
91
+ // Dormant copies (src/dormant.ts), whole or inside a merged row, go too, in the same transaction: a
92
+ // rejected value may not linger where `hippo dormant restore` could
93
+ // bring it back. They have no markdown mirror, so the post-commit
94
+ // mirror purge below is a no-op for them; they join removedIds for the
95
+ // audit trail and the caller's report.
96
+ function removeDormantCopies(db, tenantId, digest, holdsValue, removal) {
97
+ const { removedIds, dormantSuccessorIds } = removal;
98
+ removedIds.push(...purgeDormantByDigest(db, tenantId, digest));
99
+ for (const dormant of listDormantSnapshots(db, tenantId)) {
100
+ const successor = mergedSuccessor(dormant.entry, holdsValue, new Set(removedIds));
101
+ if (successor === undefined)
102
+ continue;
103
+ removedIds.push(dormant.entry.id);
104
+ if (!successor) {
105
+ deleteDormantRow(db, tenantId, dormant.entry.id);
106
+ continue;
107
+ }
108
+ replaceDormantEntry(db, tenantId, dormant.entry.id, successor);
109
+ dormantSuccessorIds.push(successor.id);
110
+ }
111
+ }
112
+ function auditRejectValue(db, opts, digest, removedIds) {
113
+ try {
114
+ appendAuditEvent(db, {
115
+ tenantId: opts.tenantId,
116
+ actor: opts.actor,
117
+ op: 'reject_value',
118
+ targetId: opts.memoryId,
119
+ metadata: { digest, removedIds, count: removedIds.length },
120
+ });
121
+ }
122
+ catch (error) {
123
+ // Inside the open transaction: the reject commits without its trail row rather than rolling back over bookkeeping.
124
+ reportAuditWriteFailure('reject_value', String(error), opts.memoryId);
125
+ }
126
+ }
127
+ // Post-commit, db handle still open (same pattern as api.archiveRaw):
128
+ // best-effort mirror purge per removed id, reaper-backstop stamp for
129
+ // raw ids.
130
+ function purgeRemovedMirrors(db, hippoRoot, removal) {
131
+ for (const id of removal.removedIds) {
132
+ // purgeMirrorBestEffort retries once, then for non-raw ids (which the
133
+ // reaper never scans) reports the EXPLICIT leftover path(s). See its own doc comment (store.ts, near
134
+ // removeEntryMirrors) for the full rationale.
135
+ const mirrorOk = purgeMirrorBestEffort(hippoRoot, id, removal.removedRawIds.includes(id), 'hippo reject');
136
+ if (mirrorOk && removal.removedRawIds.includes(id)) {
137
+ db.prepare(`UPDATE raw_archive SET mirror_cleaned_at = ? WHERE memory_id = ?`).run(new Date().toISOString(), id);
71
138
  }
139
+ }
140
+ for (const successor of removal.successors)
141
+ writeEntryMirrors(hippoRoot, successor);
142
+ }
143
+ /**
144
+ * `hippo reject` / `api.reject` core flow. ONE connection, one transaction:
145
+ * insert the tombstone, enumerate + remove every live tenant row whose
146
+ * normalized digest matches (kind-aware), one aggregate `reject_value`
147
+ * audit, COMMIT. Then post-commit (mirrors the existing purge+reaper
148
+ * pattern verbatim from api.archiveRaw, api.ts:1913-1938): best-effort
149
+ * mirror purge per removed id, `mirror_cleaned_at` stamps for raw ids.
150
+ * index.json itself is only refreshed by `rebuildIndex()`.
151
+ */
152
+ export function rejectValue(opts) {
153
+ assertRejectOpts(opts);
154
+ const db = openStore(opts.hippoRoot);
155
+ try {
156
+ const content = contentToReject(db, opts);
72
157
  const digest = rejectionDigest(content);
73
158
  const now = new Date().toISOString();
74
- const removedIds = [];
75
- const removedRawIds = [];
76
- const successors = [];
77
- const dormantSuccessorIds = [];
159
+ const removal = { removedIds: [], removedRawIds: [], successors: [], dormantSuccessorIds: [] };
78
160
  db.exec('BEGIN');
79
161
  try {
80
162
  insertRejectedValue(db, {
@@ -90,69 +172,9 @@ export function rejectValue(opts) {
90
172
  // on ~1-5k-row stores — acceptable, documented. A digest column on
91
173
  // memories is the escape if stores grow 100x; not needed now.
92
174
  const holdsValue = (text) => rejectionDigest(text) === digest;
93
- const merged = [];
94
- for (const row of selectAllEntries(db, opts.tenantId)) {
95
- if (!holdsValue(row.content)) {
96
- if (heldTexts(row).some(holdsValue))
97
- merged.push(row);
98
- continue;
99
- }
100
- if (row.kind === 'raw') {
101
- // Append-only trigger respected — archiveRawMemory is the only
102
- // legitimate removal path for kind='raw', and its inner SAVEPOINT
103
- // composes safely inside this BEGIN/COMMIT.
104
- archiveRawMemory(db, row.id, { reason: opts.reason, who: opts.actor });
105
- removedRawIds.push(row.id);
106
- }
107
- else {
108
- // suppressForgetAudit: the aggregate reject_value row below is the
109
- // trail for these removals, not N individual forget rows (plan
110
- // §4, round-3 advisory 2 — mirrors api.ts:1873-1877).
111
- deleteEntryCore(db, row.id, { actor: opts.actor, suppressForgetAudit: true });
112
- }
113
- removedIds.push(row.id);
114
- }
115
- for (const row of merged) {
116
- const successor = mergedSuccessor(row, holdsValue, new Set(removedIds));
117
- deleteEntryCore(db, row.id, { actor: opts.actor, suppressForgetAudit: true });
118
- removedIds.push(row.id);
119
- if (!successor)
120
- continue;
121
- const kept = stampOriginProject(opts.hippoRoot, successor);
122
- writeEntryDbOnly(db, kept, { actor: opts.actor });
123
- successors.push(kept);
124
- }
125
- // Dormant copies (src/dormant.ts), whole or inside a merged row, go too, in the same transaction: a
126
- // rejected value may not linger where `hippo dormant restore` could
127
- // bring it back. They have no markdown mirror, so the post-commit
128
- // mirror purge below is a no-op for them; they join removedIds for the
129
- // audit trail and the caller's report.
130
- removedIds.push(...purgeDormantByDigest(db, opts.tenantId, digest));
131
- for (const dormant of listDormantSnapshots(db, opts.tenantId)) {
132
- const successor = mergedSuccessor(dormant.entry, holdsValue, new Set(removedIds));
133
- if (successor === undefined)
134
- continue;
135
- removedIds.push(dormant.entry.id);
136
- if (!successor) {
137
- deleteDormantRow(db, opts.tenantId, dormant.entry.id);
138
- continue;
139
- }
140
- replaceDormantEntry(db, opts.tenantId, dormant.entry.id, successor);
141
- dormantSuccessorIds.push(successor.id);
142
- }
143
- try {
144
- appendAuditEvent(db, {
145
- tenantId: opts.tenantId,
146
- actor: opts.actor,
147
- op: 'reject_value',
148
- targetId: opts.memoryId,
149
- metadata: { digest, removedIds, count: removedIds.length },
150
- });
151
- }
152
- catch (error) {
153
- // Inside the open transaction: the reject commits without its trail row rather than rolling back over bookkeeping.
154
- reportAuditWriteFailure('reject_value', String(error), opts.memoryId);
155
- }
175
+ removeLiveRows(db, opts, holdsValue, removal);
176
+ removeDormantCopies(db, opts.tenantId, digest, holdsValue, removal);
177
+ auditRejectValue(db, opts, digest, removal.removedIds);
156
178
  db.exec('COMMIT');
157
179
  }
158
180
  catch (err) {
@@ -164,22 +186,8 @@ export function rejectValue(opts) {
164
186
  }
165
187
  throw err;
166
188
  }
167
- // Post-commit, db handle still open (same pattern as api.archiveRaw):
168
- // best-effort mirror purge per removed id, reaper-backstop stamp for
169
- // raw ids.
170
- for (const id of removedIds) {
171
- // AT1 fix: purgeMirrorBestEffort retries once, then — for non-raw ids,
172
- // which cleanupArchivedMirrors' reaper never scans — reports the
173
- // EXPLICIT leftover path(s) instead of the false "will retry via
174
- // reaper" claim. See its own doc comment (store.ts, near
175
- // removeEntryMirrors) for the full rationale.
176
- const mirrorOk = purgeMirrorBestEffort(opts.hippoRoot, id, removedRawIds.includes(id), 'hippo reject');
177
- if (mirrorOk && removedRawIds.includes(id)) {
178
- db.prepare(`UPDATE raw_archive SET mirror_cleaned_at = ? WHERE memory_id = ?`).run(new Date().toISOString(), id);
179
- }
180
- }
181
- for (const successor of successors)
182
- writeEntryMirrors(opts.hippoRoot, successor);
189
+ purgeRemovedMirrors(db, opts.hippoRoot, removal);
190
+ const { removedIds, removedRawIds, successors, dormantSuccessorIds } = removal;
183
191
  return { digest, content, removedIds, removedRawIds, successorIds: successors.map((s) => s.id), dormantSuccessorIds };
184
192
  }
185
193
  finally {
@@ -192,11 +200,8 @@ export function rejectValue(opts) {
192
200
  * (plan §4): no per-write force flag.
193
201
  */
194
202
  export function unrejectValue(hippoRoot, tenantId, digestOrPrefix, actor) {
195
- // P2 fix: an empty/blank prefix startsWith-matches EVERY digest (every
196
- // string starts with ''), which would previously fall through to the
197
- // ambiguous-candidates branch and list the whole tombstone set instead of
198
- // failing loud on the actually-invalid input. Reject before the DB round
199
- // trip.
203
+ // An empty/blank prefix startsWith-matches EVERY digest and would list the whole
204
+ // tombstone set as ambiguous, so reject it before the DB round trip.
200
205
  if (digestOrPrefix.trim().length === 0) {
201
206
  return { status: 'not_found' };
202
207
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * AT1 rejected-value tombstone — core invariant.
3
- * docs/plans/2026-08-15-at1-rejected-value-tombstone.md
2
+ * Rejected-value tombstone: core invariant.
4
3
  *
5
4
  * Exact-normalized-value semantics: a human who rejects a fact can refuse
6
5
  * byte-stable re-ingestion of the same value across remember/capture/import/
@@ -64,7 +63,7 @@ export interface RejectedValueRow {
64
63
  export declare function findRejectedValue(db: DatabaseSyncLike, tenantId: string, digest: string): RejectedValueRow | null;
65
64
  /**
66
65
  * Insert (or refresh) a tombstone row. Caller owns the transaction — used by
67
- * the T2 `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
66
+ * the `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
68
67
  */
69
68
  export declare function insertRejectedValue(db: DatabaseSyncLike, opts: {
70
69
  tenantId: string;
@@ -76,11 +75,11 @@ export declare function insertRejectedValue(db: DatabaseSyncLike, opts: {
76
75
  normalizedChars: number;
77
76
  }): void;
78
77
  /**
79
- * Delete a tombstone by tenant + exact digest — the T2 `unreject` verb, the
80
- * only v1 escape hatch (plan §4).
78
+ * Delete a tombstone by tenant + exact digest: the `unreject` verb, the
79
+ * only escape hatch.
81
80
  */
82
81
  export declare function deleteRejectedValue(db: DatabaseSyncLike, tenantId: string, digest: string): boolean;
83
- /** List tombstones for a tenant, newest first — the T2 `rejections` verb. */
82
+ /** List tombstones for a tenant, newest first: the `rejections` verb. */
84
83
  export declare function listRejectedValues(db: DatabaseSyncLike, tenantId: string): RejectedValueRow[];
85
84
  /**
86
85
  * The write-path guard's check helper, called from `upsertEntryRow`
package/dist/rejection.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
- * AT1 rejected-value tombstone — core invariant.
3
- * docs/plans/2026-08-15-at1-rejected-value-tombstone.md
2
+ * Rejected-value tombstone: core invariant.
4
3
  *
5
4
  * Exact-normalized-value semantics: a human who rejects a fact can refuse
6
5
  * byte-stable re-ingestion of the same value across remember/capture/import/
@@ -80,7 +79,7 @@ export function findRejectedValue(db, tenantId, digest) {
80
79
  }
81
80
  /**
82
81
  * Insert (or refresh) a tombstone row. Caller owns the transaction — used by
83
- * the T2 `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
82
+ * the `reject` verb and `resolveConflict`'s `rejectLoserValue` path.
84
83
  */
85
84
  export function insertRejectedValue(db, opts) {
86
85
  db.prepare(`INSERT INTO rejected_values(tenant_id, digest, reason, rejected_by, rejected_at, source_memory_id, normalized_chars)
@@ -93,14 +92,14 @@ export function insertRejectedValue(db, opts) {
93
92
  normalized_chars = excluded.normalized_chars`).run(opts.tenantId, opts.digest, opts.reason, opts.rejectedBy, opts.rejectedAt, opts.sourceMemoryId ?? null, opts.normalizedChars);
94
93
  }
95
94
  /**
96
- * Delete a tombstone by tenant + exact digest — the T2 `unreject` verb, the
97
- * only v1 escape hatch (plan §4).
95
+ * Delete a tombstone by tenant + exact digest: the `unreject` verb, the
96
+ * only escape hatch.
98
97
  */
99
98
  export function deleteRejectedValue(db, tenantId, digest) {
100
99
  const result = db.prepare(`DELETE FROM rejected_values WHERE tenant_id = ? AND digest = ?`).run(tenantId, digest);
101
100
  return (result.changes ?? 0) > 0;
102
101
  }
103
- /** List tombstones for a tenant, newest first — the T2 `rejections` verb. */
102
+ /** List tombstones for a tenant, newest first: the `rejections` verb. */
104
103
  export function listRejectedValues(db, tenantId) {
105
104
  // SAFETY: rows' shape matches the columns named in the SELECT above.
106
105
  const rows = db
@@ -134,15 +133,8 @@ export function checkRejectionGuard(db, tenantId, entryId, content) {
134
133
  // id the caller is already writing, and the tombstone lookup above is the
135
134
  // tenant-scoped decision. Matches deleteEntry's own by-id SELECT.
136
135
  //
137
- // P2 fix: also read tenant_id. Content-digest-only comparison let a
138
- // same-id upsert that ONLY changes tenantId slip through as an "unchanged
139
- // re-persist" — content C sitting quietly (never rejected) in tenant A
140
- // could be re-tagged into tenant B, and since C's digest already matched
141
- // this row's stored digest, the guard exempted it even though B is the
142
- // tenant that rejected C (that is WHY `tombstone` above is non-null: the
143
- // lookup already ran under the INCOMING/destination tenantId). A tenant
144
- // change on the SAME id is therefore always a content introduction into
145
- // the destination tenant, exactly as if the row were new there.
136
+ // Also read tenant_id: a same-id upsert that only changes tenantId introduces the
137
+ // content into the destination tenant, exactly as if the row were new there.
146
138
  // SAFETY: storedRow's shape matches the two columns named in the SELECT above.
147
139
  const storedRow = db.prepare(`SELECT content, tenant_id FROM memories WHERE id = ?`).get(entryId);
148
140
  const isNewRow = storedRow === undefined;
@@ -1,5 +1,5 @@
1
1
  import type { RerankerFn, RerankProvenance } from './types.js';
2
- import { type JsonValue } from '../http-util.js';
2
+ import type { JsonValue } from '../json.js';
3
3
  /** The two pretrained CLEF decision models served by Cloudflare Workers AI. */
4
4
  export type ClefModel = 'clef-flash' | 'clef';
5
5
  /** True when `name` is one of the CLEF reranker names. */
@@ -16,7 +16,6 @@ export declare function createJevReranker(localFallback: RerankerFn): RerankerFn
16
16
  /** Orders `head` by `scores[i]`, keeping any upstream pre-rerank rank. Shared with CLEF. */
17
17
  export declare function rankByScores(head: readonly SearchResult[], scores: readonly number[]): RerankResult[];
18
18
  /** Track 4 reranker: hosted TypeSafe Jev, opt-in and paid (TYPESAFE_API_KEY), one batched call per recall.
19
- * Any failure warns once and delegates to the local cross-encoder. Scores are not bit-stable run to run.
20
- * Cost, env vars, evidence and limits: docs/evals/2026-09-19-jev-reranker.md. */
19
+ * Any failure warns once and delegates to the local cross-encoder. Scores are not bit-stable run to run. */
21
20
  export declare const jevReranker: RerankerFn;
22
21
  //# sourceMappingURL=jev.d.ts.map
@@ -5,10 +5,10 @@ import { log } from '../log.js';
5
5
  const ENDPOINT = 'https://api.typesafe.ai/v1/systemone';
6
6
  const DEFAULT_TIMEOUT_MS = 5_000;
7
7
  const TRUNCATE_CHARS = 1200;
8
- // Pinned, not `jev-latest`: every number in docs/evals/2026-09-19-jev-reranker.md was
9
- // measured on this version, and the alias moves whenever the vendor ships a release.
8
+ // Pinned, not `jev-latest`: the eval numbers were measured on this version,
9
+ // and the alias moves whenever the vendor ships a release.
10
10
  const DEFAULT_MODEL = 'jev-1.13.0';
11
- // The pool size every number in docs/evals/2026-09-19-jev-reranker.md was measured at.
11
+ // The pool size the eval numbers were measured at.
12
12
  export const JEV_DEFAULT_TOP_K = 40;
13
13
  function truncate(s, n) {
14
14
  return s.length <= n ? s : `${s.slice(0, n)}...`;
@@ -124,7 +124,6 @@ export function rankByScores(head, scores) {
124
124
  return scored;
125
125
  }
126
126
  /** Track 4 reranker: hosted TypeSafe Jev, opt-in and paid (TYPESAFE_API_KEY), one batched call per recall.
127
- * Any failure warns once and delegates to the local cross-encoder. Scores are not bit-stable run to run.
128
- * Cost, env vars, evidence and limits: docs/evals/2026-09-19-jev-reranker.md. */
127
+ * Any failure warns once and delegates to the local cross-encoder. Scores are not bit-stable run to run. */
129
128
  export const jevReranker = createJevReranker(crossEncoderReranker);
130
129
  //# sourceMappingURL=jev.js.map
@@ -4,8 +4,7 @@ import type { RerankerFn } from './types.js';
4
4
  * OpenAI-compatible endpoint. Gated on HIPPO_LLM_RERANKER_URL to prevent
5
5
  * accidental cost.
6
6
  *
7
- * Skeleton only — see docs/plans/2026-05-10-f6-reranker-hardening.md Task 8.
8
- * Full characterisation deferred to a follow-on plan.
7
+ * Skeleton only; full characterisation is deferred.
9
8
  *
10
9
  * Timeout: defaults to 30s; overridable via HIPPO_LLM_RERANKER_TIMEOUT_MS.
11
10
  * On timeout or any fetch failure, the reranker warns once per process and
@@ -7,8 +7,7 @@ const DEFAULT_TIMEOUT_MS = 30_000;
7
7
  * OpenAI-compatible endpoint. Gated on HIPPO_LLM_RERANKER_URL to prevent
8
8
  * accidental cost.
9
9
  *
10
- * Skeleton only — see docs/plans/2026-05-10-f6-reranker-hardening.md Task 8.
11
- * Full characterisation deferred to a follow-on plan.
10
+ * Skeleton only; full characterisation is deferred.
12
11
  *
13
12
  * Timeout: defaults to 30s; overridable via HIPPO_LLM_RERANKER_TIMEOUT_MS.
14
13
  * On timeout or any fetch failure, the reranker warns once per process and
@@ -9,8 +9,7 @@ import type { SearchResult } from '../search/types.js';
9
9
  * Rerankers MUST be deterministic for a given (query, results) input
10
10
  * unless explicitly documented as stochastic (the LLM track, and the
11
11
  * hosted jev reranker, whose scores move slightly run to run).
12
- * Determinism is required for paired A/B and for the workload-validity
13
- * gate in docs/evals/2026-05-10-f6-reranker-prereg.md.
12
+ * Determinism is required for paired A/B and for the workload-validity gate.
14
13
  *
15
14
  * @returns Reordered (and optionally rescaled) results.
16
15
  */
package/dist/rrf.d.ts CHANGED
@@ -4,13 +4,13 @@
4
4
  * Fuses N ranked candidate lists into a single ordering by summing
5
5
  * weighted 1/(k + rank) contributions per candidate. The constant K is
6
6
  * the canonical 60 from the original paper and the value already in use
7
- * across hippo's `hybridSearch` since v1.0. Do NOT tune K without an
7
+ * across hippo's `hybridSearch`. Do NOT tune K without an
8
8
  * explicit cross-corpus eval — it is calibrated against IR benchmarks
9
9
  * and works robustly across BM25/dense/cross-encoder rank-list shapes.
10
10
  *
11
11
  * Generic over the candidate id type so this helper can be shared by
12
12
  * `src/search.ts::hybridSearch` (T = number, idx into MemoryEntry[]) and
13
- * the LongMemEval F9 hybrid retrieve benchmark (T = string, session_id).
13
+ * the LongMemEval hybrid retrieve benchmark (T = string, session_id).
14
14
  *
15
15
  * Behaviour MUST stay byte-identical to the inline implementation that
16
16
  * lived in `src/search.ts:354-374` before extraction (commit ab6c5eb).
package/dist/rrf.js CHANGED
@@ -4,13 +4,13 @@
4
4
  * Fuses N ranked candidate lists into a single ordering by summing
5
5
  * weighted 1/(k + rank) contributions per candidate. The constant K is
6
6
  * the canonical 60 from the original paper and the value already in use
7
- * across hippo's `hybridSearch` since v1.0. Do NOT tune K without an
7
+ * across hippo's `hybridSearch`. Do NOT tune K without an
8
8
  * explicit cross-corpus eval — it is calibrated against IR benchmarks
9
9
  * and works robustly across BM25/dense/cross-encoder rank-list shapes.
10
10
  *
11
11
  * Generic over the candidate id type so this helper can be shared by
12
12
  * `src/search.ts::hybridSearch` (T = number, idx into MemoryEntry[]) and
13
- * the LongMemEval F9 hybrid retrieve benchmark (T = string, session_id).
13
+ * the LongMemEval hybrid retrieve benchmark (T = string, session_id).
14
14
  *
15
15
  * Behaviour MUST stay byte-identical to the inline implementation that
16
16
  * lived in `src/search.ts:354-374` before extraction (commit ab6c5eb).
@@ -1,5 +1,5 @@
1
1
  import type { MemoryEntry } from '../memory.js';
2
- import type { ResultCost, SearchResult } from './types.js';
2
+ import { type ResultCost, type SearchResult } from './types.js';
3
3
  export interface SearchOptions {
4
4
  budget?: number;
5
5
  now?: Date;
@@ -9,6 +9,7 @@ import { currentEntries } from './as-of.js';
9
9
  import { applyRankBoosts, strengthRecencyMultipliers, NO_SUMMARY_SCORING } from './boosts.js';
10
10
  import { temporalContext } from './temporal.js';
11
11
  import { dedupeExtracted, fitBudget, withDagChildren } from './finalize.js';
12
+ import { DEFAULT_RECALL_BUDGET } from './types.js';
12
13
  /** Synchronous BM25 search: relevance * strength * recency and the rank boosts, without embeddings or the outcome nudge. */
13
14
  export function search(query, entries, options = {}) {
14
15
  const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
@@ -38,6 +39,6 @@ export function search(query, entries, options = {}) {
38
39
  scored.push({ entry: pool[i], score, bm25, cosine: 0, tokens: estimateTokens(pool[i].content) });
39
40
  }
40
41
  scored.sort(compareScoredResults);
41
- return fitBudget(withDagChildren(dedupeExtracted(scored), pool), options.budget ?? 4000, options.minResults ?? 1, options.cost);
42
+ return fitBudget(withDagChildren(dedupeExtracted(scored), pool), options.budget ?? DEFAULT_RECALL_BUDGET, options.minResults ?? 1, options.cost);
42
43
  }
43
44
  //# sourceMappingURL=bm25-search.js.map
@@ -5,6 +5,7 @@ import { pathBoostMultiplier } from '../path-context.js';
5
5
  import { scopeMatch } from '../scope.js';
6
6
  import { temporalBoost } from './temporal.js';
7
7
  export const CHURN_STALE_RANK_MULTIPLIER = 0.5; // SHORTCUT: untuned; measure on real recall before any default.
8
+ const DECISION_TAG_BOOST = 1.2;
8
9
  const DEFAULT_SUMMARY_DEBOOST = 0.85;
9
10
  const DEFAULT_FRESHNESS_BOOST = 1.05;
10
11
  const FRESHNESS_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
@@ -69,7 +70,7 @@ export function strengthRecencyMultipliers(entry, now) {
69
70
  }
70
71
  /** Applies every rank multiplier one at a time, in a fixed order, so float rounding matches across paths. */
71
72
  export function applyRankBoosts(score, entry, ctx) {
72
- const decisionBoost = entry.tags.includes('decision') ? 1.2 : 1.0;
73
+ const decisionBoost = entry.tags.includes('decision') ? DECISION_TAG_BOOST : 1.0;
73
74
  const churnStaleMultiplier = churnStaleFactor(entry);
74
75
  const pathBoost = pathBoostMultiplier(entry.tags, ctx.pathTags);
75
76
  const outcomeBoost = ctx.outcome ? outcomeMultiplier(entry) : 1.0;
@@ -3,7 +3,7 @@ import type { RerankerFn, RerankerOptions } from '../rerankers/types.js';
3
3
  import { type BM25Corpus } from './bm25.js';
4
4
  import { type HybridVectorCandidates } from './vector.js';
5
5
  import { type GraphStreamOptions } from './fusion.js';
6
- import type { ResultCost, SearchResult } from './types.js';
6
+ import { type ResultCost, type SearchResult } from './types.js';
7
7
  export interface HybridSearchOptions {
8
8
  budget?: number;
9
9
  now?: Date;
@@ -12,6 +12,7 @@ import { fuseRanks } from './fusion.js';
12
12
  import { scoreHybridPool } from './hybrid-score.js';
13
13
  import { applyMmrWindow, applyReranker } from './rerank.js';
14
14
  import { dedupeExtracted, fitBudget, withDagChildren } from './finalize.js';
15
+ import { DEFAULT_RECALL_BUDGET } from './types.js';
15
16
  /** BM25 blended with cosine similarity when stored vectors and a provider are available, BM25 * strength * recency otherwise. */
16
17
  export async function hybridSearch(query, entries, options = {}) {
17
18
  const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
@@ -42,7 +43,7 @@ export async function hybridSearch(query, entries, options = {}) {
42
43
  });
43
44
  scored.sort(compareScoredResults);
44
45
  const ordered = await orderHybrid(query, withDagChildren(dedupeExtracted(scored), arm.entries), arm, options);
45
- return fitBudget(ordered, options.budget ?? 4000, options.minResults ?? 1, options.cost);
46
+ return fitBudget(ordered, options.budget ?? DEFAULT_RECALL_BUDGET, options.minResults ?? 1, options.cost);
46
47
  }
47
48
  function hybridBoostContext(query, pool, now, options) {
48
49
  return {
@@ -1,7 +1,7 @@
1
1
  import { type MemoryEntry } from '../memory.js';
2
2
  import { type PhysicsConfig } from '../physics-config.js';
3
3
  import { type HybridVectorCandidates } from './vector.js';
4
- import type { ResultCost, SearchResult } from './types.js';
4
+ import { type ResultCost, type SearchResult } from './types.js';
5
5
  export interface PhysicsSearchOptions {
6
6
  budget?: number;
7
7
  now?: Date;
@@ -15,6 +15,7 @@ import { fitBudget } from './finalize.js';
15
15
  import { hybridSearch } from './hybrid.js';
16
16
  import { currentEntries } from './as-of.js';
17
17
  import { vectorCandidatesOutside } from './vector.js';
18
+ import { DEFAULT_RECALL_BUDGET } from './types.js';
18
19
  /** Scores memories by gravity, momentum and cluster amplification; rows without physics state rank through hybridSearch. */
19
20
  export async function physicsSearch(query, entries, options = {}) {
20
21
  const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
@@ -41,7 +42,7 @@ export async function physicsSearch(query, entries, options = {}) {
41
42
  : [];
42
43
  const merged = mergeScorePools(physicsResults, classicResults);
43
44
  merged.sort(compareScoredResults);
44
- return fitBudget(merged, options.budget ?? 4000, options.minResults ?? 1, options.cost);
45
+ return fitBudget(merged, options.budget ?? DEFAULT_RECALL_BUDGET, options.minResults ?? 1, options.cost);
45
46
  }
46
47
  /** The caller's vector, else the provider's; null sends the caller to hybridSearch. */
47
48
  async function physicsQueryVector(query, root, given) {
@@ -1,4 +1,6 @@
1
1
  import type { MemoryEntry } from '../memory.js';
2
+ export declare const DEFAULT_RECALL_BUDGET = 4000;
3
+ export declare const DEFAULT_LOCAL_BUMP = 1.2;
2
4
  /** One score mutation applied after candidate generation; allocated only under `recall --why` or `RecallOpts.explain`. */
3
5
  export interface RerankStep {
4
6
  /** One of: interference, value, utility, reranker, goal-boost, retrieval-count-downweight. */
@@ -1,2 +1,4 @@
1
- export {};
1
+ export const DEFAULT_RECALL_BUDGET = 4000;
2
+ // Local memories outrank global ones by this factor; getContext applies it as a 1/x global discount.
3
+ export const DEFAULT_LOCAL_BUMP = 1.2;
2
4
  //# sourceMappingURL=types.js.map
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Secret detection for memory content (v39 memory scope isolation, S4;
3
- * docs/plans/2026-07-01-memory-scope-isolation.md).
2
+ * Secret detection for memory content (memory scope isolation).
4
3
  *
5
4
  * Conservative, provider-bounded patterns only - a bare `sk-` in prose must
6
5
  * NOT flag. Detection gates two surfaces:
@@ -11,7 +10,7 @@
11
10
  * project origin. Explicit recall is unaffected - recalling a secret is
12
11
  * a deliberate act.
13
12
  *
14
- * This is deliberately a thin slice of the A4 lifecycle-compliance item
13
+ * This is deliberately a thin slice of lifecycle compliance
15
14
  * (no PII detection). Write-time scrubbing covers only text no person
16
15
  * typed into hippo; see `vetSecrets`.
17
16
  *
@@ -37,8 +36,8 @@ export declare function detectSecret(entry: {
37
36
  * Replace secret-shaped substrings in free text with a redaction marker.
38
37
  * Reuses the same `SECRET_PATTERNS` / co-occurrence guard as `detectSecret`
39
38
  * (which only flags whole-entry content) so callers that must persist raw
40
- * text that never passes through the normal capture content gate — e.g. the
41
- * CS1 pre-compact snapshot fields — can scrub it in place instead.
39
+ * text that never passes through the normal capture content gate (e.g. the
40
+ * pre-compact snapshot fields) can scrub it in place instead.
42
41
  */
43
42
  export declare function redactSecrets(text: string): string;
44
43
  /** Memories never hold a raw email address (AGENTS.md); phone numbers are left alone, as their patterns misfire on ids. */