@remnic/core 9.69.38 → 9.69.39

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 (245) hide show
  1. package/dist/access-admin-ops-surface.d.ts +2 -2
  2. package/dist/access-admin-ops-surface.js +18 -18
  3. package/dist/access-authorization-probe.d.ts +2 -2
  4. package/dist/access-authorization-probe.js +19 -19
  5. package/dist/access-boundary.d.ts +2 -2
  6. package/dist/access-boundary.js +18 -18
  7. package/dist/access-cli.js +67 -66
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +2 -2
  10. package/dist/access-extraction-force-flush.js +18 -18
  11. package/dist/access-http-briefing.d.ts +2 -2
  12. package/dist/access-http-briefing.js +19 -19
  13. package/dist/access-http-lcm-compaction.d.ts +2 -2
  14. package/dist/access-http-lifecycle-flush.d.ts +2 -2
  15. package/dist/access-http-offline-stream.d.ts +2 -2
  16. package/dist/access-http-query.js +20 -20
  17. package/dist/access-http.d.ts +3 -3
  18. package/dist/access-http.js +35 -35
  19. package/dist/access-identity-continuity-surface.d.ts +1 -1
  20. package/dist/access-identity-continuity-surface.js +18 -18
  21. package/dist/access-lcm-surface.d.ts +2 -2
  22. package/dist/access-lcm-surface.js +18 -18
  23. package/dist/access-mcp.d.ts +2 -2
  24. package/dist/access-mcp.js +25 -25
  25. package/dist/access-namespace-preflight.js +18 -18
  26. package/dist/access-observe-transcript.d.ts +1 -1
  27. package/dist/access-observe-write-surface.d.ts +2 -2
  28. package/dist/access-observe-write-surface.js +18 -18
  29. package/dist/access-offline-manifest.d.ts +1 -1
  30. package/dist/access-operations-batch.js +19 -19
  31. package/dist/access-operations.d.ts +2 -2
  32. package/dist/access-operations.js +24 -24
  33. package/dist/access-recall-concurrency.d.ts +2 -2
  34. package/dist/access-recall-concurrency.js +18 -18
  35. package/dist/access-recall-response.d.ts +2 -2
  36. package/dist/access-recall-response.js +18 -18
  37. package/dist/access-recall-surface.d.ts +2 -2
  38. package/dist/access-recall-surface.js +18 -18
  39. package/dist/{access-service-gBcdRUHY.d.ts → access-service-MY4bsr_Q.d.ts} +1 -1
  40. package/dist/access-service-helpers.d.ts +2 -2
  41. package/dist/access-service.d.ts +2 -2
  42. package/dist/access-service.js +18 -18
  43. package/dist/access-surface-catalog.d.ts +2 -2
  44. package/dist/access-surface-catalog.js +20 -20
  45. package/dist/active-recall.js +2 -2
  46. package/dist/adapters/index.js +4 -4
  47. package/dist/adapters/registry.js +2 -2
  48. package/dist/bootstrap.d.ts +1 -1
  49. package/dist/briefing.js +7 -7
  50. package/dist/causal-consolidation.js +8 -8
  51. package/dist/{chunk-2PTNJTBE.js → chunk-2ZTDRNCS.js} +2 -2
  52. package/dist/{chunk-BX4EOMKT.js → chunk-3EKVN2OP.js} +2 -2
  53. package/dist/{chunk-DPMTLJCT.js → chunk-3NPNBVFR.js} +2 -2
  54. package/dist/{chunk-OKO57IXB.js → chunk-4JVQRFZD.js} +4 -4
  55. package/dist/{chunk-6PRY6CLC.js → chunk-4RZLTZVM.js} +1 -1
  56. package/dist/{chunk-PPQEP4OH.js → chunk-54NX5EG4.js} +2 -2
  57. package/dist/{chunk-2OGTN37H.js → chunk-5SPCMAUC.js} +3 -3
  58. package/dist/{chunk-TUKGXQ5T.js → chunk-5YCNDC3C.js} +2 -2
  59. package/dist/chunk-6AGWBKQU.js +94 -0
  60. package/dist/chunk-6AGWBKQU.js.map +1 -0
  61. package/dist/{chunk-BJEJUOGM.js → chunk-6YAIN5XJ.js} +2 -2
  62. package/dist/{chunk-X2NFFBTO.js → chunk-A7GQBJP5.js} +4 -4
  63. package/dist/{chunk-ZKZFFWLV.js → chunk-AAZEKXUP.js} +2 -2
  64. package/dist/{chunk-6ZMNTRU3.js → chunk-AENM6GKU.js} +2 -2
  65. package/dist/{chunk-7S53BPTU.js → chunk-CZTPU65V.js} +8 -8
  66. package/dist/{chunk-UQNYPK7G.js → chunk-D545Z7UH.js} +2 -2
  67. package/dist/{chunk-GT4ZHOKT.js → chunk-ENBPQEVU.js} +2 -2
  68. package/dist/{chunk-TRHBQVVO.js → chunk-EYKIDK33.js} +11 -11
  69. package/dist/{chunk-EY5JAWZC.js → chunk-F4EHXVP7.js} +2 -2
  70. package/dist/{chunk-474DL7UB.js → chunk-FNHLI2RE.js} +3 -3
  71. package/dist/{chunk-2MT4ADLQ.js → chunk-FWWWUPOE.js} +2 -2
  72. package/dist/{chunk-M3MYMWXG.js → chunk-FXHOZGSE.js} +2 -2
  73. package/dist/{chunk-VHOSM5G4.js → chunk-GG5FHZJJ.js} +2 -2
  74. package/dist/{chunk-CBFYCDYO.js → chunk-GI4ZBWPQ.js} +3 -3
  75. package/dist/{chunk-KDFDXEXZ.js → chunk-GMIQV6DD.js} +2 -2
  76. package/dist/{chunk-XGRXQR6T.js → chunk-IL6ODG6M.js} +2 -2
  77. package/dist/{chunk-XWLLFKHM.js → chunk-IQEJIOEU.js} +7 -7
  78. package/dist/{chunk-MLWMR2GE.js → chunk-JKKII6GX.js} +2 -2
  79. package/dist/{chunk-T32B72QX.js → chunk-JTQR6L2N.js} +2 -2
  80. package/dist/{chunk-BRZWWUCJ.js → chunk-LMLGBG6H.js} +2 -2
  81. package/dist/{chunk-3OYSIVOO.js → chunk-LPYYY4AP.js} +152 -112
  82. package/dist/chunk-LPYYY4AP.js.map +1 -0
  83. package/dist/{chunk-A6CKMIPK.js → chunk-MUTVRKKY.js} +8 -8
  84. package/dist/{chunk-JPOX2AJB.js → chunk-NXGSONWM.js} +2 -2
  85. package/dist/{chunk-GGAXFTNH.js → chunk-O4M4BDIJ.js} +2 -2
  86. package/dist/{chunk-VV4ZVUE6.js → chunk-O76F5QPP.js} +2 -2
  87. package/dist/{chunk-G43PMSME.js → chunk-OPBFP4TF.js} +3 -3
  88. package/dist/{chunk-JNSENHV5.js → chunk-PCOVEDPK.js} +3 -3
  89. package/dist/{chunk-VMNCZ627.js → chunk-Q432REND.js} +2 -2
  90. package/dist/{chunk-QMWKENNK.js → chunk-Q4AUIFUA.js} +4 -4
  91. package/dist/{chunk-74AHCL56.js → chunk-Q7CLGFA7.js} +2 -2
  92. package/dist/{chunk-5AERZWJD.js → chunk-QTQ6XKGT.js} +3 -3
  93. package/dist/{chunk-K5DA5TRG.js → chunk-RLJHJQUY.js} +2 -2
  94. package/dist/{chunk-GQQVEH6V.js → chunk-TA4C3XB7.js} +2 -2
  95. package/dist/{chunk-V6G2PSXA.js → chunk-TNZNSMVJ.js} +26 -26
  96. package/dist/{chunk-JA2QJOAX.js → chunk-TSP5WH72.js} +2 -2
  97. package/dist/{chunk-NWMUSKED.js → chunk-UHRZ324M.js} +3 -3
  98. package/dist/{chunk-62QIQEUC.js → chunk-UZEHHXYC.js} +13 -13
  99. package/dist/{chunk-TTSJ7WUC.js → chunk-VBSNUH4J.js} +2 -2
  100. package/dist/{chunk-R4VAUNNC.js → chunk-VJIE4D2I.js} +2 -2
  101. package/dist/{chunk-BYZKLEWG.js → chunk-VSL63CFJ.js} +5 -5
  102. package/dist/{chunk-4B3HBIFX.js → chunk-WBM2MVBK.js} +2 -2
  103. package/dist/{chunk-ULDBGA5W.js → chunk-WIWLX65T.js} +2 -2
  104. package/dist/{chunk-XN6XT6GR.js → chunk-X4TD4O2F.js} +2 -2
  105. package/dist/{chunk-UIOIDW3L.js → chunk-XH3HGI2N.js} +2 -2
  106. package/dist/chunk-YDVAM7S4.js +221 -0
  107. package/dist/chunk-YDVAM7S4.js.map +1 -0
  108. package/dist/{chunk-VELCIWJZ.js → chunk-YKREX63Z.js} +19 -10
  109. package/dist/chunk-YKREX63Z.js.map +1 -0
  110. package/dist/{chunk-HVS7TH2Y.js → chunk-ZBW7A667.js} +2 -2
  111. package/dist/{cli-BNg-TAlz.d.ts → cli-DGq0MNpO.d.ts} +2 -2
  112. package/dist/cli.d.ts +3 -3
  113. package/dist/cli.js +49 -49
  114. package/dist/coding/export-okf-codegraph.js +6 -6
  115. package/dist/compounding/engine.js +7 -7
  116. package/dist/config.js +2 -2
  117. package/dist/connectors/codex-materialize-runner.js +7 -7
  118. package/dist/connectors/index.js +7 -7
  119. package/dist/convergence-refresh.js +9 -9
  120. package/dist/corpus-watermark.js +9 -9
  121. package/dist/deep-recall-http-glue.d.ts +2 -2
  122. package/dist/deep-recall-mcp-tools.d.ts +2 -2
  123. package/dist/entity-retrieval.js +7 -7
  124. package/dist/explicit-capture.d.ts +1 -1
  125. package/dist/external-wiki-access.d.ts +2 -2
  126. package/dist/external-wiki-access.js +19 -19
  127. package/dist/external-wiki-mcp-tools.d.ts +2 -2
  128. package/dist/extraction.js +7 -7
  129. package/dist/harmonic-retrieval.js +1 -1
  130. package/dist/index.d.ts +6 -6
  131. package/dist/index.js +78 -69
  132. package/dist/lcm/engine.js +2 -2
  133. package/dist/lcm/index.js +4 -4
  134. package/dist/location/index.js +9 -9
  135. package/dist/maintenance/memory-governance.js +7 -7
  136. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  137. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  138. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  139. package/dist/memory-subject-cli.d.ts +2 -2
  140. package/dist/memory-subject.d.ts +2 -2
  141. package/dist/namespaces/migrate.js +8 -8
  142. package/dist/namespaces/search.js +4 -4
  143. package/dist/namespaces/storage.js +7 -7
  144. package/dist/operator-doctor-corpus.js +10 -10
  145. package/dist/operator-toolkit.js +17 -17
  146. package/dist/orchestration/maintenance.js +15 -15
  147. package/dist/{orchestrator-DocO0_hw.d.ts → orchestrator-ComHLm49.d.ts} +13 -2
  148. package/dist/orchestrator.d.ts +1 -1
  149. package/dist/orchestrator.js +67 -66
  150. package/dist/{public-http-C0FoTiL_.d.ts → public-http-CuK4_Cb1.d.ts} +1 -1
  151. package/dist/recall-state-view-admission.js +4 -4
  152. package/dist/recall-state-view-anchors.d.ts +85 -0
  153. package/dist/recall-state-view-anchors.js +21 -0
  154. package/dist/recall-state-view-anchors.js.map +1 -0
  155. package/dist/recall-state-view-inject.js +2 -2
  156. package/dist/recall-state-view-render.js +2 -2
  157. package/dist/recall-state-view-widen.js +2 -2
  158. package/dist/recall-state-view-wire.js +3 -3
  159. package/dist/recall-state-view.d.ts +37 -1
  160. package/dist/recall-state-view.js +11 -3
  161. package/dist/resume-bundles.js +3 -3
  162. package/dist/search/factory.js +3 -3
  163. package/dist/search/index.js +7 -7
  164. package/dist/semantic-consolidation.js +8 -8
  165. package/dist/semantic-rule-promotion.js +7 -7
  166. package/dist/semantic-rule-verifier.js +7 -7
  167. package/dist/source-agent-qualifier.js +7 -7
  168. package/dist/standup/build.js +7 -7
  169. package/dist/storage.js +6 -6
  170. package/dist/support-passport/index.d.ts +5 -5
  171. package/dist/support-passport/index.js +21 -21
  172. package/dist/transfer/export-okf.js +7 -7
  173. package/dist/verified-recall.js +7 -7
  174. package/dist/who-knows-cli.d.ts +2 -2
  175. package/dist/who-knows-cli.js +8 -8
  176. package/dist/who-knows.d.ts +2 -2
  177. package/dist/who-knows.js +7 -7
  178. package/package.json +2 -2
  179. package/src/orchestration/recall-internal.ts +91 -74
  180. package/src/orchestration/recall-rerank-coordinator.ts +41 -4
  181. package/src/orchestration/recall-search-pipeline.ts +25 -18
  182. package/src/orchestration/recall-state-view-pipeline.test.ts +89 -0
  183. package/src/recall-rerank-coordinator.test.ts +153 -0
  184. package/src/recall-state-view-anchors.ts +163 -0
  185. package/src/recall-state-view-widen.ts +21 -7
  186. package/src/recall-state-view-zero-diff.test.ts +30 -0
  187. package/src/recall-state-view.test.ts +161 -0
  188. package/src/recall-state-view.ts +206 -35
  189. package/src/trust-score-recall-paths.test.ts +1 -1
  190. package/dist/chunk-255PC37A.js +0 -120
  191. package/dist/chunk-255PC37A.js.map +0 -1
  192. package/dist/chunk-3OYSIVOO.js.map +0 -1
  193. package/dist/chunk-VELCIWJZ.js.map +0 -1
  194. /package/dist/{chunk-2PTNJTBE.js.map → chunk-2ZTDRNCS.js.map} +0 -0
  195. /package/dist/{chunk-BX4EOMKT.js.map → chunk-3EKVN2OP.js.map} +0 -0
  196. /package/dist/{chunk-DPMTLJCT.js.map → chunk-3NPNBVFR.js.map} +0 -0
  197. /package/dist/{chunk-OKO57IXB.js.map → chunk-4JVQRFZD.js.map} +0 -0
  198. /package/dist/{chunk-6PRY6CLC.js.map → chunk-4RZLTZVM.js.map} +0 -0
  199. /package/dist/{chunk-PPQEP4OH.js.map → chunk-54NX5EG4.js.map} +0 -0
  200. /package/dist/{chunk-2OGTN37H.js.map → chunk-5SPCMAUC.js.map} +0 -0
  201. /package/dist/{chunk-TUKGXQ5T.js.map → chunk-5YCNDC3C.js.map} +0 -0
  202. /package/dist/{chunk-BJEJUOGM.js.map → chunk-6YAIN5XJ.js.map} +0 -0
  203. /package/dist/{chunk-X2NFFBTO.js.map → chunk-A7GQBJP5.js.map} +0 -0
  204. /package/dist/{chunk-ZKZFFWLV.js.map → chunk-AAZEKXUP.js.map} +0 -0
  205. /package/dist/{chunk-6ZMNTRU3.js.map → chunk-AENM6GKU.js.map} +0 -0
  206. /package/dist/{chunk-7S53BPTU.js.map → chunk-CZTPU65V.js.map} +0 -0
  207. /package/dist/{chunk-UQNYPK7G.js.map → chunk-D545Z7UH.js.map} +0 -0
  208. /package/dist/{chunk-GT4ZHOKT.js.map → chunk-ENBPQEVU.js.map} +0 -0
  209. /package/dist/{chunk-TRHBQVVO.js.map → chunk-EYKIDK33.js.map} +0 -0
  210. /package/dist/{chunk-EY5JAWZC.js.map → chunk-F4EHXVP7.js.map} +0 -0
  211. /package/dist/{chunk-474DL7UB.js.map → chunk-FNHLI2RE.js.map} +0 -0
  212. /package/dist/{chunk-2MT4ADLQ.js.map → chunk-FWWWUPOE.js.map} +0 -0
  213. /package/dist/{chunk-M3MYMWXG.js.map → chunk-FXHOZGSE.js.map} +0 -0
  214. /package/dist/{chunk-VHOSM5G4.js.map → chunk-GG5FHZJJ.js.map} +0 -0
  215. /package/dist/{chunk-CBFYCDYO.js.map → chunk-GI4ZBWPQ.js.map} +0 -0
  216. /package/dist/{chunk-KDFDXEXZ.js.map → chunk-GMIQV6DD.js.map} +0 -0
  217. /package/dist/{chunk-XGRXQR6T.js.map → chunk-IL6ODG6M.js.map} +0 -0
  218. /package/dist/{chunk-XWLLFKHM.js.map → chunk-IQEJIOEU.js.map} +0 -0
  219. /package/dist/{chunk-MLWMR2GE.js.map → chunk-JKKII6GX.js.map} +0 -0
  220. /package/dist/{chunk-T32B72QX.js.map → chunk-JTQR6L2N.js.map} +0 -0
  221. /package/dist/{chunk-BRZWWUCJ.js.map → chunk-LMLGBG6H.js.map} +0 -0
  222. /package/dist/{chunk-A6CKMIPK.js.map → chunk-MUTVRKKY.js.map} +0 -0
  223. /package/dist/{chunk-JPOX2AJB.js.map → chunk-NXGSONWM.js.map} +0 -0
  224. /package/dist/{chunk-GGAXFTNH.js.map → chunk-O4M4BDIJ.js.map} +0 -0
  225. /package/dist/{chunk-VV4ZVUE6.js.map → chunk-O76F5QPP.js.map} +0 -0
  226. /package/dist/{chunk-G43PMSME.js.map → chunk-OPBFP4TF.js.map} +0 -0
  227. /package/dist/{chunk-JNSENHV5.js.map → chunk-PCOVEDPK.js.map} +0 -0
  228. /package/dist/{chunk-VMNCZ627.js.map → chunk-Q432REND.js.map} +0 -0
  229. /package/dist/{chunk-QMWKENNK.js.map → chunk-Q4AUIFUA.js.map} +0 -0
  230. /package/dist/{chunk-74AHCL56.js.map → chunk-Q7CLGFA7.js.map} +0 -0
  231. /package/dist/{chunk-5AERZWJD.js.map → chunk-QTQ6XKGT.js.map} +0 -0
  232. /package/dist/{chunk-K5DA5TRG.js.map → chunk-RLJHJQUY.js.map} +0 -0
  233. /package/dist/{chunk-GQQVEH6V.js.map → chunk-TA4C3XB7.js.map} +0 -0
  234. /package/dist/{chunk-V6G2PSXA.js.map → chunk-TNZNSMVJ.js.map} +0 -0
  235. /package/dist/{chunk-JA2QJOAX.js.map → chunk-TSP5WH72.js.map} +0 -0
  236. /package/dist/{chunk-NWMUSKED.js.map → chunk-UHRZ324M.js.map} +0 -0
  237. /package/dist/{chunk-62QIQEUC.js.map → chunk-UZEHHXYC.js.map} +0 -0
  238. /package/dist/{chunk-TTSJ7WUC.js.map → chunk-VBSNUH4J.js.map} +0 -0
  239. /package/dist/{chunk-R4VAUNNC.js.map → chunk-VJIE4D2I.js.map} +0 -0
  240. /package/dist/{chunk-BYZKLEWG.js.map → chunk-VSL63CFJ.js.map} +0 -0
  241. /package/dist/{chunk-4B3HBIFX.js.map → chunk-WBM2MVBK.js.map} +0 -0
  242. /package/dist/{chunk-ULDBGA5W.js.map → chunk-WIWLX65T.js.map} +0 -0
  243. /package/dist/{chunk-XN6XT6GR.js.map → chunk-X4TD4O2F.js.map} +0 -0
  244. /package/dist/{chunk-UIOIDW3L.js.map → chunk-XH3HGI2N.js.map} +0 -0
  245. /package/dist/{chunk-HVS7TH2Y.js.map → chunk-ZBW7A667.js.map} +0 -0
@@ -45,6 +45,7 @@ import {
45
45
  } from "./recall-search-prefilter.js";
46
46
  import { shouldFilterSupersededFromRecall } from "../temporal-supersession.js";
47
47
  import { isValidAsOf, isValidityExpiredNow } from "../temporal-validity.js";
48
+ import { StateViewAnchorTracker, stateViewPacketActive } from "../recall-state-view-anchors.js";
48
49
  import type { TrustStageResultItem } from "../trust-score-stage.js";
49
50
  import type { MemoryFile, PluginConfig, QmdSearchResult, RecallPlanMode } from "../types.js";
50
51
  import {
@@ -987,17 +988,22 @@ export class RecallSearchPipelineCoordinator {
987
988
  options.recallResultLimit,
988
989
  options.prompt,
989
990
  caps,
991
+ // #2859: packet semantics are off under a historical asOf pin.
992
+ stateViewPacketActive(options.stateViewActive, options.asOfMs),
990
993
  );
991
994
  options.resultPartitionSink.partition = partition;
992
995
  return partition.appliedResults;
993
996
  }
994
- return this.deps.diversifyAndLimitRecallResults(
995
- "memories",
996
- results,
997
- options.recallResultLimit,
998
- options.prompt,
999
- caps,
1000
- );
997
+ return this.deps.recallRerankCoordinator
998
+ .diversifyRecallResultsWithHeadroom(
999
+ "memories",
1000
+ results,
1001
+ options.recallResultLimit,
1002
+ options.prompt,
1003
+ caps,
1004
+ stateViewPacketActive(options.stateViewActive, options.asOfMs),
1005
+ )
1006
+ .appliedResults;
1001
1007
  }
1002
1008
 
1003
1009
  async loadSearchResultMemoryMap(
@@ -1132,10 +1138,11 @@ export class RecallSearchPipelineCoordinator {
1132
1138
  // when its successor SURVIVES this filter — a successor rejected by
1133
1139
  // the connector/lifecycle/validity/dedicated-surface/status gates
1134
1140
  // must never anchor (or spend a result slot on) its superseded row.
1135
- // Anchors are therefore collected from rows that survive the pass
1136
- // below; superseded rows defer admission to the post-pass fixpoint.
1141
+ // #2859: anchors are namespace-qualified and include the successor
1142
+ // `supersedes` back-pointer (see StateViewAnchorTracker); superseded
1143
+ // rows defer admission to the post-pass fixpoint.
1137
1144
  const stateViewActive = options?.stateViewActive === true;
1138
- const stateViewSurvivorIds = stateViewActive ? new Set<string>() : null;
1145
+ const stateViewAnchors = stateViewActive ? new StateViewAnchorTracker() : null;
1139
1146
  const stateViewDeferred: number[] = [];
1140
1147
  const stateViewBuilt: (QmdSearchResult | null)[] | null = stateViewActive
1141
1148
  ? new Array<QmdSearchResult | null>(results.length).fill(null)
@@ -1150,8 +1157,11 @@ export class RecallSearchPipelineCoordinator {
1150
1157
  ? {
1151
1158
  id: memory.frontmatter.id,
1152
1159
  status: memory.frontmatter.status,
1160
+ supersedes: memory.frontmatter.supersedes,
1153
1161
  supersededBy: memory.frontmatter.supersededBy,
1154
1162
  supersededAt: memory.frontmatter.supersededAt,
1163
+ validAt: memory.frontmatter.valid_at,
1164
+ invalidAt: memory.frontmatter.invalid_at,
1155
1165
  }
1156
1166
  : {}),
1157
1167
  });
@@ -1307,10 +1317,7 @@ export class RecallSearchPipelineCoordinator {
1307
1317
  const builtRow = buildRecallRow(r, memory);
1308
1318
  if (stateViewBuilt !== null) {
1309
1319
  stateViewBuilt[resultIdx] = builtRow;
1310
- if (memory) {
1311
- const id = memory.frontmatter.id;
1312
- if (typeof id === "string" && id.length > 0) stateViewSurvivorIds!.add(id);
1313
- }
1320
+ if (memory && stateViewAnchors) stateViewAnchors.admit(r.namespace, memory.frontmatter);
1314
1321
  }
1315
1322
  filtered.push(builtRow);
1316
1323
  }
@@ -1324,12 +1331,12 @@ export class RecallSearchPipelineCoordinator {
1324
1331
  progress = false;
1325
1332
  for (const deferredIdx of stateViewDeferred) {
1326
1333
  if (admitted.has(deferredIdx)) continue;
1327
- const deferredMemory = memoryForResult(memoryByPath, results[deferredIdx]!);
1334
+ const deferredRow = results[deferredIdx]!;
1335
+ const deferredMemory = memoryForResult(memoryByPath, deferredRow);
1328
1336
  if (!deferredMemory) continue;
1329
- if (stateViewSurvivorIds!.has(deferredMemory.frontmatter.supersededBy ?? "")) {
1337
+ if (stateViewAnchors!.anchored(deferredRow.namespace, deferredMemory.frontmatter)) {
1330
1338
  admitted.add(deferredIdx);
1331
- const id = deferredMemory.frontmatter.id;
1332
- if (typeof id === "string" && id.length > 0) stateViewSurvivorIds!.add(id);
1339
+ stateViewAnchors!.admit(deferredRow.namespace, deferredMemory.frontmatter);
1333
1340
  progress = true;
1334
1341
  }
1335
1342
  }
@@ -209,3 +209,92 @@ test("an anchored predecessor keeps its rank position when admitted", async () =
209
209
  await rm(memoryDir, { recursive: true, force: true });
210
210
  }
211
211
  });
212
+
213
+ test("#2859 namespace fanout collision: a same-id successor in another namespace cannot anchor", async () => {
214
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-sv-fanout-"));
215
+ try {
216
+ const coordinator = await makeCoordinator(memoryDir);
217
+ const A_OLD = "facts/a-old.md";
218
+ const B_NEW = "facts/b-new.md";
219
+ // ns-a: superseded predecessor whose successor id m-1 is NOT in the
220
+ // candidate set. ns-b: an unrelated active row that reuses id m-1.
221
+ // Bare-id matching (pre-#2859) admitted the ns-a predecessor on the
222
+ // ns-b anchor; qualified keys must not.
223
+ const results: QmdSearchResult[] = [
224
+ { ...result(B_NEW, 0.9), namespace: "ns-b" },
225
+ { ...result(A_OLD, 0.7), namespace: "ns-a" },
226
+ ];
227
+ const memories = new Map<string, MemoryFile>([
228
+ [`ns-b\0${B_NEW}`, memory(B_NEW, { id: "m-1", status: "active" })],
229
+ [
230
+ `ns-a\0${A_OLD}`,
231
+ memory(A_OLD, {
232
+ id: "m-0",
233
+ status: "superseded",
234
+ supersededBy: "m-1",
235
+ supersededAt: "2026-08-01",
236
+ }),
237
+ ],
238
+ ]);
239
+ const safe = coordinator.filterSearchResultsByRecallSafety(results, memories, {
240
+ stateViewActive: true,
241
+ });
242
+ assert.deepEqual(
243
+ safe.map((r) => r.path),
244
+ [B_NEW],
245
+ "a foreign-namespace successor must never anchor the superseded row"
246
+ );
247
+
248
+ // Positive control: ns-a's own m-1 present → the pair is admitted.
249
+ const A_NEW = "facts/a-new.md";
250
+ const pairResults: QmdSearchResult[] = [
251
+ { ...result(A_NEW, 0.9), namespace: "ns-a" },
252
+ { ...result(A_OLD, 0.7), namespace: "ns-a" },
253
+ ];
254
+ const pairMemories = new Map<string, MemoryFile>([
255
+ [`ns-a\0${A_NEW}`, memory(A_NEW, { id: "m-1", status: "active" })],
256
+ [`ns-a\0${A_OLD}`, memories.get(`ns-a\0${A_OLD}`)!],
257
+ ]);
258
+ const pair = coordinator.filterSearchResultsByRecallSafety(pairResults, pairMemories, {
259
+ stateViewActive: true,
260
+ });
261
+ assert.deepEqual(
262
+ pair.map((r) => r.path),
263
+ [A_NEW, A_OLD],
264
+ "the same-namespace successor anchors the pair as before"
265
+ );
266
+ } finally {
267
+ await rm(memoryDir, { recursive: true, force: true });
268
+ }
269
+ });
270
+
271
+ test("#2859 back-pointer anchors a deferred row whose supersededBy is absent", async () => {
272
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-sv-backptr-"));
273
+ try {
274
+ const coordinator = await makeCoordinator(memoryDir);
275
+ const results = [result(NEW, 0.9), result(OLD, 0.7)];
276
+ const memories = new Map<string, MemoryFile>([
277
+ [NEW, memory(NEW, { id: "m-new", status: "active", supersedes: "m-old" })],
278
+ [
279
+ OLD,
280
+ memory(OLD, {
281
+ id: "m-old",
282
+ status: "superseded",
283
+ supersededAt: "2026-08-01",
284
+ }),
285
+ ],
286
+ ]);
287
+ const safe = coordinator.filterSearchResultsByRecallSafety(results, memories, {
288
+ stateViewActive: true,
289
+ });
290
+ assert.deepEqual(
291
+ safe.map((r) => r.path),
292
+ [NEW, OLD],
293
+ "the successor's supersedes back-pointer must admit its predecessor"
294
+ );
295
+ assert.equal(safe[1]?.supersedes, undefined, "the admitted row itself has no back-pointer");
296
+ assert.equal(safe[0]?.supersedes, "m-old", "the survivor carries its supersedes back-pointer");
297
+ } finally {
298
+ await rm(memoryDir, { recursive: true, force: true });
299
+ }
300
+ });
@@ -567,3 +567,156 @@ test("trust rerank preserves duplicate paths owned by different namespaces (#202
567
567
  );
568
568
  assert.equal(outcome.trustByPath?.size, 2, "trust metadata must have one entry per namespaced result");
569
569
  });
570
+
571
+ test("#2859 state-view cap: pairs reconcile before cap/MMR and count as one evidence packet", () => {
572
+ // MMR off so the ordered pool equals the input order — the packet
573
+ // semantics under test are then fully deterministic.
574
+ const config = parseConfig({ recallMmrEnabled: false });
575
+ const corpus = makeFakes([]);
576
+ const coordinator = new RecallRerankCoordinator({
577
+ getConfig: () => config,
578
+ getStorage: corpus.getStorage,
579
+ readQmdResultMemory: async () => null,
580
+ });
581
+ const sv = (
582
+ id: string,
583
+ score: number,
584
+ chain: Partial<QmdSearchResult> = {},
585
+ ): QmdSearchResult => ({ ...result(id, score), id, status: "active", ...chain });
586
+ const rows: QmdSearchResult[] = [
587
+ sv("p1", 0.95, { status: "superseded", supersededBy: "s1" }),
588
+ sv("orphan", 0.9, { status: "superseded", supersededBy: "absent" }),
589
+ sv("s1", 0.85),
590
+ sv("f1", 0.8),
591
+ sv("f2", 0.75),
592
+ ];
593
+ const caps = resolveCapabilities(config);
594
+
595
+ // Packet view: [p1+s1], [f1], [f2] — the orphan is reconciled away
596
+ // BEFORE the cap, and the admitted pair consumes a single slot.
597
+ const partition = coordinator.diversifyRecallResultsWithHeadroom(
598
+ "memories",
599
+ rows,
600
+ 2,
601
+ undefined,
602
+ caps,
603
+ true,
604
+ );
605
+ assert.deepEqual(
606
+ partition.appliedResults.map((r) => r.id),
607
+ ["p1", "s1", "f1"],
608
+ "a complete pair is one packet: pred+succ plus one filler for limit 2",
609
+ );
610
+ assert.deepEqual(
611
+ partition.headroomResults.map((r) => r.id),
612
+ ["f2"],
613
+ "headroom is the remainder of the reconciled pool",
614
+ );
615
+
616
+ // The slice never splits a packet at the boundary: even with the
617
+ // successor ranked LAST, the pair is admitted together.
618
+ const splitRisk: QmdSearchResult[] = [
619
+ sv("p1", 0.95, { status: "superseded", supersededBy: "s1" }),
620
+ sv("f1", 0.9),
621
+ sv("f2", 0.85),
622
+ sv("s1", 0.8),
623
+ ];
624
+ const applied = coordinator.diversifyAndLimitRecallResults(
625
+ "memories",
626
+ splitRisk,
627
+ 2,
628
+ undefined,
629
+ caps,
630
+ true,
631
+ );
632
+ assert.deepEqual(
633
+ applied.map((r) => r.id),
634
+ ["p1", "f1", "s1"],
635
+ "no underfill: the boundary keeps the evidence packet complete",
636
+ );
637
+
638
+ // Flag absent → the legacy row slice, byte-identical (zero-diff default).
639
+ const legacy = coordinator.diversifyAndLimitRecallResults(
640
+ "memories",
641
+ rows,
642
+ 2,
643
+ undefined,
644
+ caps,
645
+ );
646
+ assert.deepEqual(
647
+ legacy.map((r) => r.id),
648
+ ["p1", "orphan"],
649
+ "without the state view the cap stays the plain top-N row slice",
650
+ );
651
+ });
652
+
653
+ test("#2859 packet cap after filter: a quarantined successor does not consume budget", () => {
654
+ const config = parseConfig({ recallMmrEnabled: false });
655
+ const corpus = makeFakes([]);
656
+ const coordinator = new RecallRerankCoordinator({
657
+ getConfig: () => config,
658
+ getStorage: corpus.getStorage,
659
+ readQmdResultMemory: async () => null,
660
+ });
661
+ const sv = (
662
+ id: string,
663
+ score: number,
664
+ chain: Partial<QmdSearchResult> = {},
665
+ ): QmdSearchResult => ({ ...result(id, score), id, status: "active", ...chain });
666
+ // Trust already removed s1. Cap-after-filter must drop the orphaned
667
+ // predecessor and promote the next live packet instead of returning empty.
668
+ const afterTrust: QmdSearchResult[] = [
669
+ sv("p1", 0.95, { status: "superseded", supersededBy: "s1" }),
670
+ sv("f1", 0.8),
671
+ sv("f2", 0.75),
672
+ ];
673
+ const partition = coordinator.diversifyRecallResultsWithHeadroom(
674
+ "memories",
675
+ afterTrust,
676
+ 1,
677
+ undefined,
678
+ resolveCapabilities(config),
679
+ true,
680
+ );
681
+ assert.deepEqual(
682
+ partition.appliedResults.map((row) => row.id),
683
+ ["f1"],
684
+ "disallowed pair must not occupy the only packet slot",
685
+ );
686
+ assert.deepEqual(
687
+ partition.headroomResults.map((row) => row.id),
688
+ ["f2"],
689
+ );
690
+ });
691
+
692
+ test("#2859 linkless superseded status does not consume a packet slot", () => {
693
+ const config = parseConfig({ recallMmrEnabled: false });
694
+ const corpus = makeFakes([]);
695
+ const coordinator = new RecallRerankCoordinator({
696
+ getConfig: () => config,
697
+ getStorage: corpus.getStorage,
698
+ readQmdResultMemory: async () => null,
699
+ });
700
+ const sv = (
701
+ id: string,
702
+ score: number,
703
+ chain: Partial<QmdSearchResult> = {},
704
+ ): QmdSearchResult => ({ ...result(id, score), id, status: "active", ...chain });
705
+ const rows: QmdSearchResult[] = [
706
+ sv("legacy", 0.99, { status: "superseded" }),
707
+ sv("live", 0.5),
708
+ ];
709
+ const applied = coordinator.diversifyAndLimitRecallResults(
710
+ "memories",
711
+ rows,
712
+ 1,
713
+ undefined,
714
+ resolveCapabilities(config),
715
+ true,
716
+ );
717
+ assert.deepEqual(
718
+ applied.map((row) => row.id),
719
+ ["live"],
720
+ "status-only superseded rows are rejected before the packet cap",
721
+ );
722
+ });
@@ -0,0 +1,163 @@
1
+ import { isChangeOrientedQuery, stateViewKey } from "./recall-state-view.js";
2
+ /**
3
+ * State-view anchor admission (#2859).
4
+ *
5
+ * Shared namespace-qualified admission tracker for the recall safety
6
+ * filter (QMD/embedding/cold paths) and the recent-scan path. A deferred
7
+ * superseded row is admitted only when an in-set successor anchors it —
8
+ * through the row's own `supersededBy` or through an admitted successor's
9
+ * `supersedes` back-pointer. Identities are `namespace\0id`, so identical
10
+ * ids across namespace fanout never cross-anchor.
11
+ */
12
+ import { shouldFilterSupersededFromRecall } from "./temporal-supersession.js";
13
+ import type { MemoryFile } from "./types.js";
14
+
15
+ export interface StateViewAnchorFrontmatter {
16
+ id?: string;
17
+ supersedes?: string;
18
+ supersededBy?: string;
19
+ }
20
+
21
+ /**
22
+ * #2859 — pair reconciliation and packet-counting caps apply only WITHOUT
23
+ * a historical pin: under asOf the validity filter legitimately orphans
24
+ * predecessors (see annotateStateView's asOf mode).
25
+ */
26
+ export function stateViewPacketActive(stateViewActive: boolean | undefined, asOfMs: number | undefined): boolean {
27
+ return stateViewActive === true && !(typeof asOfMs === "number" && Number.isFinite(asOfMs));
28
+ }
29
+
30
+ /**
31
+ * #1952/#2859 — config flag OR per-call override, gated on change intent.
32
+ * Packet semantics are off under a historical asOf pin (see stateViewPacketActive).
33
+ */
34
+ export function resolveRecallStateViewFlags(
35
+ optionFlag: boolean | undefined,
36
+ configFlag: boolean | undefined,
37
+ retrievalQuery: string,
38
+ asOfMs: number | undefined,
39
+ ): { stateViewActive: boolean; stateViewPacketActive: boolean } {
40
+ const stateViewActive =
41
+ (optionFlag === true || configFlag === true) && isChangeOrientedQuery(retrievalQuery);
42
+ return { stateViewActive, stateViewPacketActive: stateViewPacketActive(stateViewActive, asOfMs) };
43
+ }
44
+
45
+ export class StateViewAnchorTracker {
46
+ private readonly admitted = new Set<string>();
47
+ private readonly supersedesAnchors = new Set<string>();
48
+
49
+ has(namespace: string | undefined, frontmatter: StateViewAnchorFrontmatter): boolean {
50
+ const id = frontmatter.id;
51
+ return typeof id === "string" && id.length > 0 ? this.admitted.has(stateViewKey(namespace, id)) : false;
52
+ }
53
+
54
+ /**
55
+ * Register a row as admitted (a survivor of every gate, or a deferred
56
+ * row whose successor anchored it). The row's `supersedes` back-pointer
57
+ * then anchors its own predecessor.
58
+ */
59
+ admit(namespace: string | undefined, frontmatter: StateViewAnchorFrontmatter): void {
60
+ const id = frontmatter.id;
61
+ if (typeof id !== "string" || id.length === 0) return;
62
+ this.admitted.add(stateViewKey(namespace, id));
63
+ if (frontmatter.supersedes) {
64
+ this.supersedesAnchors.add(stateViewKey(namespace, frontmatter.supersedes));
65
+ }
66
+ }
67
+
68
+ /** True when an admitted successor anchors this superseded row. */
69
+ anchored(namespace: string | undefined, frontmatter: StateViewAnchorFrontmatter): boolean {
70
+ const id = frontmatter.id;
71
+ if (typeof id !== "string" || id.length === 0) return false;
72
+ const successorId = frontmatter.supersededBy;
73
+ return (
74
+ (typeof successorId === "string" && this.admitted.has(stateViewKey(namespace, successorId))) ||
75
+ this.supersedesAnchors.has(stateViewKey(namespace, id))
76
+ );
77
+ }
78
+ }
79
+
80
+ export interface StateViewRecentAdmissionDeps {
81
+ namespaceOf: (path: string) => string;
82
+ isExcluded: (path: string) => boolean;
83
+ supersessionOptions: { enabled: boolean; includeInRecall: boolean };
84
+ }
85
+
86
+ /**
87
+ * #2859 — recent-scan admission: seed the tracker with every memory that
88
+ * survives the supersession gate, then admit superseded rows anchored by
89
+ * an in-set successor (fixpoint — an admitted link anchors the next).
90
+ */
91
+ export function admitStateViewRecentMemories(
92
+ memories: readonly MemoryFile[],
93
+ deps: StateViewRecentAdmissionDeps
94
+ ): StateViewAnchorTracker {
95
+ const tracker = new StateViewAnchorTracker();
96
+ for (const m of memories) {
97
+ if (deps.isExcluded(m.path)) continue;
98
+ if (m.frontmatter.status === "superseded") continue;
99
+ if (shouldFilterSupersededFromRecall(m.frontmatter, deps.supersessionOptions)) continue;
100
+ tracker.admit(deps.namespaceOf(m.path), m.frontmatter);
101
+ }
102
+ for (let progress = true; progress; ) {
103
+ progress = false;
104
+ for (const m of memories) {
105
+ const namespace = deps.namespaceOf(m.path);
106
+ if (tracker.has(namespace, m.frontmatter)) continue;
107
+ if (deps.isExcluded(m.path)) continue;
108
+ if (m.frontmatter.status !== "superseded") continue;
109
+ if (!shouldFilterSupersededFromRecall(m.frontmatter, deps.supersessionOptions)) continue;
110
+ if (tracker.anchored(namespace, m.frontmatter)) {
111
+ tracker.admit(namespace, m.frontmatter);
112
+ progress = true;
113
+ }
114
+ }
115
+ }
116
+ return tracker;
117
+ }
118
+
119
+ export interface RecentScanMemoryFilterDeps extends StateViewRecentAdmissionDeps {
120
+ stateViewActive: boolean;
121
+ asOfMs: number | undefined;
122
+ }
123
+
124
+ /**
125
+ * Recent-scan admission for the fallback path.
126
+ *
127
+ * Superseded-status filtering delegates to
128
+ * shouldFilterSupersededFromRecall so recent-scan and the QMD
129
+ * safety filter share semantics (kill switch, audit mode, PR #402).
130
+ * #1952: an active state view admits a superseded memory whose
131
+ * successor ALSO survives this admission filter (fixpoint below).
132
+ * PR #713: when `as_of` is active, pass superseded candidates
133
+ * through here — boostSearchResults's `[valid_at, invalid_at)`
134
+ * evaluation is the authoritative historical gate. Other
135
+ * non-active statuses stay excluded.
136
+ * #2859: namespace-qualified anchors with the supersedes
137
+ * back-pointer — mirrors the safety-filter fixpoint.
138
+ */
139
+ export function filterRecentScanMemories(
140
+ memories: readonly MemoryFile[],
141
+ deps: RecentScanMemoryFilterDeps,
142
+ ): MemoryFile[] {
143
+ const asOfActive = typeof deps.asOfMs === "number" && Number.isFinite(deps.asOfMs);
144
+ const stateViewAnchors =
145
+ deps.stateViewActive && !asOfActive ? admitStateViewRecentMemories(memories, deps) : null;
146
+ return memories.filter((m) => {
147
+ if (deps.isExcluded(m.path)) return false;
148
+ const status = m.frontmatter.status;
149
+ if (!status || status === "active") return true;
150
+ if (status === "superseded") {
151
+ if (asOfActive) return true;
152
+ if (stateViewAnchors?.has(deps.namespaceOf(m.path), m.frontmatter)) {
153
+ return true;
154
+ }
155
+ // Include superseded memory only if the canonical gate says
156
+ // NOT to filter it (kill switch off or audit mode on).
157
+ return !shouldFilterSupersededFromRecall(m.frontmatter, deps.supersessionOptions);
158
+ }
159
+ // Other non-active statuses (archived, retired, etc.) are
160
+ // excluded from the recent-scan path by default.
161
+ return false;
162
+ });
163
+ }
@@ -9,8 +9,9 @@ import {
9
9
  annotateStateView,
10
10
  isChangeOrientedQuery,
11
11
  parseRecallStateViews,
12
- resultStateViewId,
12
+ resultStateViewKey,
13
13
  shouldWidenSuperseded,
14
+ stateViewKey,
14
15
  type StateViewResult,
15
16
  } from "./recall-state-view.js";
16
17
 
@@ -45,21 +46,34 @@ export function widenRecallStateViews<T extends StateViewResult>(
45
46
  if (!enabled || !isChangeOrientedQuery(query)) return results;
46
47
  const asOfActive = typeof asOfMs === "number" && Number.isFinite(asOfMs);
47
48
 
49
+ // #2859: matches are namespace-qualified, and a successor's `supersedes`
50
+ // back-pointer anchors its predecessor even when the predecessor's own
51
+ // `supersededBy` is absent.
48
52
  const candidateIds = new Set<string>();
53
+ const supersedesAnchors = new Set<string>();
49
54
  for (const result of results) {
50
- const id = resultStateViewId(result);
51
- if (id) candidateIds.add(id);
55
+ const key = resultStateViewKey(result);
56
+ if (!key) continue;
57
+ candidateIds.add(key);
58
+ if (result.supersedes) {
59
+ supersedesAnchors.add(stateViewKey(result.namespace, result.supersedes));
60
+ }
52
61
  }
53
62
 
54
63
  const seen = new Set(candidateIds);
55
64
  const extra: T[] = [];
56
65
  if (!asOfActive) {
57
66
  for (const item of pool) {
58
- const id = resultStateViewId(item);
59
- if (!id || seen.has(id)) continue;
60
- if (!shouldWidenSuperseded(item.supersededBy, candidateIds)) continue;
67
+ const key = resultStateViewKey(item);
68
+ if (!key || seen.has(key)) continue;
69
+ const successorKey = item.supersededBy
70
+ ? stateViewKey(item.namespace, item.supersededBy)
71
+ : undefined;
72
+ if (!shouldWidenSuperseded(successorKey, candidateIds) && !supersedesAnchors.has(key)) {
73
+ continue;
74
+ }
61
75
  extra.push(item);
62
- seen.add(id);
76
+ seen.add(key);
63
77
  }
64
78
  }
65
79
 
@@ -143,6 +143,7 @@ type LiveFixtureRow = StateViewLine & {
143
143
  score: number;
144
144
  supersededAt?: string;
145
145
  supersededBy?: string;
146
+ supersedes?: string;
146
147
  };
147
148
 
148
149
  // Review round 2: the earlier follow-up ran `injectStateViewLines`, which has
@@ -176,6 +177,7 @@ function liveEntries(
176
177
  stateLabel: result.stateLabel,
177
178
  ...(result.supersededAt ? { supersededAt: result.supersededAt } : {}),
178
179
  ...(result.supersededBy ? { supersededBy: result.supersededBy } : {}),
180
+ ...(result.supersedes ? { supersedes: result.supersedes } : {}),
179
181
  }));
180
182
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- test fixture
181
183
  const widened = applyRecallStateViews(qmdResults as any[], query, config);
@@ -289,3 +291,31 @@ test("the live route still renders every current entry it was given", () => {
289
291
  assert.match(entries[0]!, /the API limit is 100\/min/);
290
292
  assert.match(entries[1]!, /we chose SQLite/);
291
293
  });
294
+
295
+ test("the enabled route renders history for a supersedes-only pair", () => {
296
+ const results = [
297
+ {
298
+ memoryId: "m-1",
299
+ text: "we use PostgreSQL",
300
+ stateLabel: "current" as const,
301
+ score: 0.9,
302
+ supersedes: "m-0",
303
+ },
304
+ {
305
+ memoryId: "m-0",
306
+ text: "we use SQLite",
307
+ stateLabel: "current" as const,
308
+ supersededAt: "2026-08-01",
309
+ score: 0.7,
310
+ },
311
+ ];
312
+ const query = "what changed about the database?";
313
+ const enabled = liveEntries(results, query, true);
314
+ const old = enabled.entries.find((entry) => entry.includes("we use SQLite"));
315
+ assert.equal(
316
+ enabled.widened.find((row) => row.id === "m-0")?.stateLabel,
317
+ "historical",
318
+ );
319
+ assert.ok(old, "enabled render includes the predecessor");
320
+ assert.match(old!, /\[superseded 2026-08-01 by m-1\] we use SQLite/);
321
+ });