@remnic/core 10.0.84 → 10.0.85

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 (399) hide show
  1. package/dist/access-admin-ops-surface.js +67 -66
  2. package/dist/access-authorization-probe.js +68 -67
  3. package/dist/access-boundary.js +67 -66
  4. package/dist/access-cli.js +146 -144
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +67 -66
  7. package/dist/access-http-briefing.js +68 -67
  8. package/dist/access-http-query.js +69 -68
  9. package/dist/access-http.js +91 -89
  10. package/dist/access-identity-continuity-surface.js +67 -66
  11. package/dist/access-lcm-surface.js +67 -66
  12. package/dist/access-mcp-frames.d.ts +29 -0
  13. package/dist/access-mcp-frames.d.ts.map +1 -0
  14. package/dist/access-mcp-frames.js +10 -0
  15. package/dist/access-mcp.d.ts +1 -1
  16. package/dist/access-mcp.d.ts.map +1 -1
  17. package/dist/access-mcp.js +82 -80
  18. package/dist/access-namespace-preflight.js +67 -66
  19. package/dist/access-observe-write-category.js +9 -9
  20. package/dist/access-observe-write-surface.d.ts.map +1 -1
  21. package/dist/access-observe-write-surface.js +67 -66
  22. package/dist/access-offline-manifest.js +3 -3
  23. package/dist/access-operations-batch.js +71 -70
  24. package/dist/access-operations.js +78 -77
  25. package/dist/access-recall-concurrency.js +67 -66
  26. package/dist/access-recall-response.js +67 -66
  27. package/dist/access-recall-surface.js +67 -66
  28. package/dist/access-schema.js +4 -4
  29. package/dist/access-service.js +67 -66
  30. package/dist/access-surface-catalog.js +69 -68
  31. package/dist/access-surface-catalog.js.map +1 -1
  32. package/dist/active-recall.js +31 -31
  33. package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
  34. package/dist/briefing.js +25 -25
  35. package/dist/buffer-persistence.d.ts +8 -4
  36. package/dist/buffer-persistence.d.ts.map +1 -1
  37. package/dist/buffer-persistence.js +1 -1
  38. package/dist/buffer-turn-helpers.d.ts +5 -0
  39. package/dist/buffer-turn-helpers.d.ts.map +1 -1
  40. package/dist/buffer-turn-helpers.js +1 -1
  41. package/dist/buffer.d.ts +4 -1
  42. package/dist/buffer.d.ts.map +1 -1
  43. package/dist/buffer.js +9 -9
  44. package/dist/calibration.js +2 -2
  45. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
  46. package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
  47. package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
  48. package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
  49. package/dist/causal-behavior.js +2 -2
  50. package/dist/causal-chain.js +2 -2
  51. package/dist/causal-consolidation.js +32 -32
  52. package/dist/causal-retrieval.js +2 -2
  53. package/dist/causal-trajectory-graph.js +2 -2
  54. package/dist/causal-trajectory.js +1 -1
  55. package/dist/{chunk-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
  56. package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
  57. package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
  58. package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
  59. package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
  60. package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
  61. package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
  62. package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
  63. package/dist/chunk-3ODZLCHC.js.map +1 -0
  64. package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
  65. package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
  66. package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
  67. package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
  68. package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
  69. package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
  70. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  71. package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
  72. package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
  73. package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
  74. package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
  75. package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
  76. package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
  77. package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
  78. package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
  79. package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
  80. package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
  81. package/dist/chunk-BMIM6ESC.js.map +1 -0
  82. package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
  83. package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
  84. package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
  85. package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
  86. package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
  87. package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
  88. package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
  89. package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
  90. package/dist/{chunk-K3B72DKJ.js → chunk-E7V5AK7I.js} +2 -2
  91. package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
  92. package/dist/chunk-ELWI2R6H.js.map +1 -0
  93. package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
  94. package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
  95. package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
  96. package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
  97. package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
  98. package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
  99. package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
  100. package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
  101. package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
  102. package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
  103. package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
  104. package/dist/chunk-H34INI3P.js.map +1 -0
  105. package/dist/{chunk-HLKUZW42.js → chunk-H4D5YURJ.js} +80 -94
  106. package/dist/chunk-H4D5YURJ.js.map +1 -0
  107. package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
  108. package/dist/chunk-HVFX2N2G.js.map +1 -0
  109. package/dist/chunk-IGLJNHOX.js +62 -0
  110. package/dist/chunk-IGLJNHOX.js.map +1 -0
  111. package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
  112. package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
  113. package/dist/chunk-JFH6JAGY.js.map +1 -0
  114. package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
  115. package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
  116. package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
  117. package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
  118. package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
  119. package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
  120. package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
  121. package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
  122. package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
  123. package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
  124. package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
  125. package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
  126. package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
  127. package/dist/{chunk-3JPTUCD7.js → chunk-ONN2E5QD.js} +32 -23
  128. package/dist/chunk-ONN2E5QD.js.map +1 -0
  129. package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
  130. package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
  131. package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
  132. package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
  133. package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
  134. package/dist/chunk-QV2VZKD5.js +31 -0
  135. package/dist/chunk-QV2VZKD5.js.map +1 -0
  136. package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
  137. package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
  138. package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
  139. package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
  140. package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
  141. package/dist/chunk-SGHV6LZ7.js.map +1 -0
  142. package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
  143. package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
  144. package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
  145. package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
  146. package/dist/chunk-UDKWFDV2.js.map +1 -0
  147. package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
  148. package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
  149. package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
  150. package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
  151. package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
  152. package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
  153. package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
  154. package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
  155. package/dist/chunk-WLNWFHX3.js.map +1 -0
  156. package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
  157. package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
  158. package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
  159. package/dist/chunk-XO23OKMK.js.map +1 -0
  160. package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
  161. package/dist/chunk-XSFQN7TP.js.map +1 -0
  162. package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
  163. package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
  164. package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
  165. package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
  166. package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
  167. package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
  168. package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
  169. package/dist/cli.js +110 -108
  170. package/dist/coding/export-okf-codegraph.js +25 -25
  171. package/dist/coding/pre-action-gate.js +2 -2
  172. package/dist/compounding/engine.js +27 -27
  173. package/dist/config.js +28 -28
  174. package/dist/connectors/codex-materialize-runner.js +25 -25
  175. package/dist/connectors/index.js +25 -25
  176. package/dist/contradiction/index.js +4 -4
  177. package/dist/convergence-refresh.js +27 -27
  178. package/dist/conversation-index/backend.js +2 -2
  179. package/dist/corpus-watermark.js +27 -27
  180. package/dist/deep-recall-http-glue.js +5 -5
  181. package/dist/entity-retrieval.js +25 -25
  182. package/dist/event-order-recall.js +2 -2
  183. package/dist/explicit-capture.js +5 -5
  184. package/dist/external-wiki-access.js +68 -67
  185. package/dist/extraction-evidence-class.js +2 -2
  186. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  187. package/dist/extraction-quote-grounding.js +1 -1
  188. package/dist/extraction-source-grounding-rules.js +2 -2
  189. package/dist/extraction-source-grounding.js +5 -5
  190. package/dist/extraction-span-materialize.js +2 -2
  191. package/dist/extraction.js +40 -40
  192. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  193. package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
  194. package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
  195. package/dist/graph-jsonl.js +3 -3
  196. package/dist/graph-snapshot.js +3 -3
  197. package/dist/graph.js +2 -2
  198. package/dist/harmonic-retrieval.js +25 -25
  199. package/dist/index.js +195 -193
  200. package/dist/lcm/engine.js +2 -2
  201. package/dist/lcm/index.js +2 -2
  202. package/dist/local-llm.js +2 -2
  203. package/dist/location/index.js +28 -28
  204. package/dist/maintenance/memory-governance.js +25 -25
  205. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
  206. package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
  207. package/dist/maintenance/rebuild-memory-projection.js +26 -26
  208. package/dist/memory-browse-operations.js +68 -67
  209. package/dist/memory-subject.js +2 -2
  210. package/dist/namespaces/migrate.js +26 -26
  211. package/dist/namespaces/search.d.ts +8 -0
  212. package/dist/namespaces/search.d.ts.map +1 -1
  213. package/dist/namespaces/search.js +10 -6
  214. package/dist/namespaces/storage.js +25 -25
  215. package/dist/operator-doctor-corpus.js +28 -28
  216. package/dist/operator-toolkit.js +39 -39
  217. package/dist/orchestration/backlog-scheduler.d.ts +3 -2
  218. package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
  219. package/dist/orchestration/backlog-scheduler.js +157 -155
  220. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  221. package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
  222. package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
  223. package/dist/orchestration/buffer-drain-runner.js +147 -145
  224. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  225. package/dist/orchestration/fact-dedup-window.d.ts +1 -4
  226. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  227. package/dist/orchestration/maintenance.js +39 -39
  228. package/dist/orchestrator.js +146 -144
  229. package/dist/provenance.js +2 -2
  230. package/dist/recall-navigation-operations.js +68 -67
  231. package/dist/recall-navigation.js +67 -66
  232. package/dist/reconcile/manifest.js +2 -2
  233. package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
  234. package/dist/resolution-23ZBDM6A.js.map +1 -0
  235. package/dist/resume-bundles.js +29 -29
  236. package/dist/salvage-envelope.js +4 -4
  237. package/dist/search/factory.js +5 -5
  238. package/dist/search/index.js +9 -9
  239. package/dist/semantic-consolidation.js +26 -26
  240. package/dist/semantic-rule-promotion.js +25 -25
  241. package/dist/semantic-rule-verifier.js +25 -25
  242. package/dist/source-agent-qualifier.js +31 -31
  243. package/dist/standup/build.js +25 -25
  244. package/dist/storage/buffer-drain-claims.d.ts +18 -6
  245. package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
  246. package/dist/storage/buffer-drain-claims.js +2 -2
  247. package/dist/storage/buffer-session-files.js +2 -2
  248. package/dist/storage/content-hash-index.d.ts +8 -0
  249. package/dist/storage/content-hash-index.d.ts.map +1 -1
  250. package/dist/storage.d.ts +10 -1
  251. package/dist/storage.d.ts.map +1 -1
  252. package/dist/storage.js +24 -24
  253. package/dist/subject-config.js +3 -3
  254. package/dist/summarizer.js +3 -3
  255. package/dist/support-passport/index.js +70 -69
  256. package/dist/transfer/autodetect.js +1 -1
  257. package/dist/transfer/backup.js +2 -2
  258. package/dist/transfer/capsule-export.js +3 -3
  259. package/dist/transfer/capsule-gunzip.d.ts +14 -0
  260. package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
  261. package/dist/transfer/capsule-import.d.ts.map +1 -1
  262. package/dist/transfer/capsule-import.js +3 -2
  263. package/dist/transfer/capsule-merge.d.ts.map +1 -1
  264. package/dist/transfer/export-okf.js +25 -25
  265. package/dist/verified-recall.js +25 -25
  266. package/dist/who-knows-cli.js +26 -26
  267. package/dist/who-knows.js +25 -25
  268. package/dist/write-envelope.js +3 -3
  269. package/package.json +2 -2
  270. package/src/access-mcp-frames.test.ts +47 -0
  271. package/src/access-mcp-frames.ts +74 -0
  272. package/src/access-mcp.test.ts +146 -0
  273. package/src/access-mcp.ts +30 -30
  274. package/src/access-observe-write-surface.ts +4 -4
  275. package/src/backlog-scheduler.test.ts +443 -3
  276. package/src/buffer-persistence.ts +15 -6
  277. package/src/buffer-turn-helpers.ts +6 -0
  278. package/src/buffer.ts +6 -6
  279. package/src/cli.ts +4 -4
  280. package/src/extraction-quote-grounding.test.ts +49 -0
  281. package/src/extraction-quote-grounding.ts +50 -12
  282. package/src/fact-dedup-window.test.ts +103 -20
  283. package/src/namespaces/search.test.ts +339 -0
  284. package/src/namespaces/search.ts +54 -1
  285. package/src/orchestration/backlog-scheduler.ts +13 -12
  286. package/src/orchestration/buffer-drain-runner.ts +40 -16
  287. package/src/orchestration/extraction-persist.ts +2 -10
  288. package/src/orchestration/fact-dedup-window.ts +5 -12
  289. package/src/storage/buffer-drain-claims.test.ts +330 -6
  290. package/src/storage/buffer-drain-claims.ts +226 -80
  291. package/src/storage/buffer-session-files.ts +2 -2
  292. package/src/storage/content-hash-index.ts +9 -0
  293. package/src/storage.ts +15 -0
  294. package/src/transfer/capsule-gunzip.ts +39 -0
  295. package/src/transfer/capsule-import.test.ts +73 -0
  296. package/src/transfer/capsule-import.ts +2 -2
  297. package/src/transfer/capsule-merge.test.ts +34 -0
  298. package/src/transfer/capsule-merge.ts +2 -10
  299. package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
  300. package/dist/chunk-3JPTUCD7.js.map +0 -1
  301. package/dist/chunk-4KLQI3L2.js.map +0 -1
  302. package/dist/chunk-6LCWKS62.js.map +0 -1
  303. package/dist/chunk-7PMOCQOL.js.map +0 -1
  304. package/dist/chunk-B74NPUHT.js.map +0 -1
  305. package/dist/chunk-HLKUZW42.js.map +0 -1
  306. package/dist/chunk-KQUSP6LB.js.map +0 -1
  307. package/dist/chunk-LU66I56Q.js.map +0 -1
  308. package/dist/chunk-MPDLLLOG.js.map +0 -1
  309. package/dist/chunk-WAD32CZG.js.map +0 -1
  310. package/dist/chunk-XYP5WBDO.js.map +0 -1
  311. package/dist/chunk-YK7GOPIE.js.map +0 -1
  312. package/dist/chunk-Z3DDQXM4.js.map +0 -1
  313. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
  314. /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
  315. /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  316. /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
  317. /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
  318. /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
  319. /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
  320. /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
  321. /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
  322. /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
  323. /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
  324. /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
  325. /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
  326. /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
  327. /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
  328. /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
  329. /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
  330. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  331. /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
  332. /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
  333. /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
  334. /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
  335. /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
  336. /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
  337. /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
  338. /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
  339. /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
  340. /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
  341. /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
  342. /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
  343. /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
  344. /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
  345. /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
  346. /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
  347. /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
  348. /package/dist/{chunk-K3B72DKJ.js.map → chunk-E7V5AK7I.js.map} +0 -0
  349. /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
  350. /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
  351. /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
  352. /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
  353. /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
  354. /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
  355. /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
  356. /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
  357. /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
  358. /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
  359. /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
  360. /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
  361. /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
  362. /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
  363. /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
  364. /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
  365. /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
  366. /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
  367. /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
  368. /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
  369. /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
  370. /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
  371. /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
  372. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
  373. /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
  374. /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
  375. /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
  376. /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
  377. /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
  378. /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
  379. /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
  380. /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
  381. /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
  382. /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
  383. /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
  384. /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
  385. /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
  386. /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
  387. /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
  388. /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
  389. /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
  390. /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
  391. /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
  392. /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
  393. /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
  394. /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
  395. /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
  396. /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
  397. /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
  398. /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
  399. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -1,4 +1,5 @@
1
- import type { BufferEntryState, BufferState, BufferSurpriseEvent, BufferTurn } from "./types.js";
1
+ import type { DrainedTurnSets } from "./buffer-turn-helpers.js";
2
+ import type { BufferEntryState, BufferState, BufferSurpriseEvent } from "./types.js";
2
3
  import type { BufferSessionSaveOutcome } from "./storage/buffer-drain-claims.js";
3
4
 
4
5
  /**
@@ -26,8 +27,8 @@ export interface BufferStorage {
26
27
  export interface BufferPersistOutcome {
27
28
  /** Sessions whose in-memory entry moved into a drain claim: the caller must evict them (a claimed session no longer lives in this process). */
28
29
  handedOver: string[];
29
- /** Per session, the turns a drain already extracted: the caller subtracts them from its in-memory copy. */
30
- drainedTurns: Map<string, BufferTurn[]>;
30
+ /** Per session, the turns a drain extracted, kept in their original lists. */
31
+ drainedTurns: Map<string, DrainedTurnSets>;
31
32
  }
32
33
 
33
34
  /**
@@ -73,17 +74,20 @@ export class BufferSessionPersistence {
73
74
  storage: BufferStorage,
74
75
  state: BufferState,
75
76
  peekEntry: (key: string) => BufferEntryState | undefined,
77
+ reconcile?: { key: string; drained: DrainedTurnSets },
76
78
  ): Promise<BufferPersistOutcome> {
79
+ const drainedTurns = new Map<string, DrainedTurnSets>(
80
+ reconcile ? [[reconcile.key, reconcile.drained]] : [],
81
+ );
77
82
  const saveSession = storage.saveBufferSession?.bind(storage);
78
83
  const deleteSession = storage.deleteBufferSession?.bind(storage);
79
84
  if (!saveSession || !deleteSession) {
80
85
  await storage.saveBuffer(state);
81
86
  this.dirty.clear();
82
87
  this.deleted.clear();
83
- return { handedOver: [], drainedTurns: new Map() };
88
+ return { handedOver: [], drainedTurns };
84
89
  }
85
90
  const handedOver: string[] = [];
86
- const drainedTurns = new Map<string, BufferTurn[]>();
87
91
  for (const key of [...this.deleted]) {
88
92
  await deleteSession(key);
89
93
  this.deleted.delete(key);
@@ -104,7 +108,12 @@ export class BufferSessionPersistence {
104
108
  if (outcome?.status === "handed_over") handedOver.push(key);
105
109
  // A drain tombstone: the turns it extracted must leave this process's
106
110
  // copy too, or the owner would extract them a second time.
107
- if (outcome?.drainedTurns && outcome.drainedTurns.length > 0) drainedTurns.set(key, outcome.drainedTurns);
111
+ if (
112
+ outcome?.drainedTurns &&
113
+ (outcome.drainedTurns.turns.length > 0 || outcome.drainedTurns.retainedTurns.length > 0)
114
+ ) {
115
+ drainedTurns.set(key, outcome.drainedTurns);
116
+ }
108
117
  }
109
118
  return { handedOver, drainedTurns };
110
119
  }
@@ -1,5 +1,11 @@
1
1
  import type { BufferEntryState, BufferState, BufferTurn } from "./types.js";
2
2
 
3
+ /** Extracted identities retain whether they came from active or retained turns. */
4
+ export interface DrainedTurnSets {
5
+ turns: BufferTurn[];
6
+ retainedTurns: BufferTurn[];
7
+ }
8
+
3
9
  export interface BufferTurnOwner {
4
10
  /**
5
11
  * Server-resolved principal that owns this session at the authenticated
package/src/buffer.ts CHANGED
@@ -300,16 +300,16 @@ export class SmartBuffer {
300
300
  this.loaded = true;
301
301
  }
302
302
 
303
- private async saveUnlocked(): Promise<void> {
304
- const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined);
303
+ private async saveUnlocked(reconcile?: { key: string; drained: import("./buffer-turn-helpers.js").DrainedTurnSets }): Promise<void> {
304
+ const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined, reconcile);
305
305
  // A drain tombstone: the turns the drain extracted leave this copy too,
306
306
  // so a stale owner never extracts (or persists) them a second time.
307
307
  for (const [key, drained] of drainedTurns) {
308
308
  const entry = this.peekEntry(key);
309
309
  if (!entry) continue;
310
- entry.turns = subtractBufferTurns(entry.turns, drained);
310
+ entry.turns = subtractBufferTurns(entry.turns, drained.turns);
311
311
  if (entry.retainedTurns && entry.retainedTurns.length > 0) {
312
- entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained);
312
+ entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained.retainedTurns);
313
313
  if (entry.retainedTurns.length === 0) delete entry.retainedTurns;
314
314
  }
315
315
  if (key === "default") this.state.turns = entry.turns;
@@ -340,8 +340,8 @@ export class SmartBuffer {
340
340
  return bufferEntryCounts(this.state);
341
341
  }
342
342
 
343
- async save(): Promise<void> {
344
- await this.enqueueMutation(async () => this.saveUnlocked());
343
+ async save(reconcile?: { key: string; drained: import("./buffer-turn-helpers.js").DrainedTurnSets }): Promise<void> {
344
+ await this.enqueueMutation(async () => this.saveUnlocked(reconcile));
345
345
  }
346
346
 
347
347
  /**
package/src/cli.ts CHANGED
@@ -4895,7 +4895,7 @@ export function registerCli(
4895
4895
  // No sidecar — attempt in-memory decryption if the secure-store is
4896
4896
  // unlocked. decryptCapsuleFileInMemory returns the plaintext gzip
4897
4897
  // bytes; we then gunzip and parse exactly like the plaintext path.
4898
- const { gunzipSync } = await import("node:zlib");
4898
+ const { gunzipCapsuleArchive } = await import("./transfer/capsule-gunzip.js");
4899
4899
  const { parseExportBundle } = await import("./transfer/types.js");
4900
4900
  let decryptedBuf: Buffer;
4901
4901
  try {
@@ -4917,7 +4917,7 @@ export function registerCli(
4917
4917
  process.exitCode = 1;
4918
4918
  return;
4919
4919
  }
4920
- const json = gunzipSync(decryptedBuf).toString("utf-8");
4920
+ const json = gunzipCapsuleArchive(decryptedBuf, "capsule inspect", archivePath);
4921
4921
  const parsed2 = parseExportBundle(JSON.parse(json));
4922
4922
  if (parsed2.capsuleVersion !== 2) {
4923
4923
  process.stderr.write(
@@ -4929,10 +4929,10 @@ export function registerCli(
4929
4929
  manifest = (parsed2.bundle as { manifest: Record<string, unknown> }).manifest;
4930
4930
  } else {
4931
4931
  const { readFile } = await import("node:fs/promises");
4932
- const { gunzipSync } = await import("node:zlib");
4932
+ const { gunzipCapsuleArchive } = await import("./transfer/capsule-gunzip.js");
4933
4933
  const { parseExportBundle } = await import("./transfer/types.js");
4934
4934
  const buf = await readFile(archivePath);
4935
- const json = gunzipSync(buf).toString("utf-8");
4935
+ const json = gunzipCapsuleArchive(buf, "capsule inspect", archivePath);
4936
4936
  const parsed2 = parseExportBundle(JSON.parse(json));
4937
4937
  if (parsed2.capsuleVersion !== 2) {
4938
4938
  process.stderr.write(
@@ -410,6 +410,55 @@ test("a claim that inverts a negated quote is rejected, a restated exclusion is
410
410
  assert.deepEqual(keptContents(grounded), ["Alice would avoid approving the budget."]);
411
411
  });
412
412
 
413
+ test("a claim that keeps one quote negation and flips another is rejected", () => {
414
+ const source = "Notes: Alice did not approve the budget. Bob did not review the ledger.";
415
+ const quote = "Alice did not approve the budget. Bob did not review the ledger.";
416
+ const grounded = filterExtractionResultBySource(
417
+ resultOf([
418
+ { content: "Alice did not approve the budget, and Bob reviewed the ledger.", quote },
419
+ {
420
+ content: "Alice did not approve the budget, and Bob did not review the ledger.",
421
+ quote,
422
+ },
423
+ ]),
424
+ source,
425
+ source,
426
+ );
427
+ assert.deepEqual(keptContents(grounded), [
428
+ "Alice did not approve the budget, and Bob did not review the ledger.",
429
+ ]);
430
+ });
431
+
432
+ test("a claim that keeps the denial in a pronoun clause is kept", () => {
433
+ const source = "Notes: Alice did not approve the budget.";
434
+ const quote = "Alice did not approve the budget.";
435
+ const claims = [
436
+ "Alice discussed the budget; she did not approve it.",
437
+ "Alice reviewed the budget, but she did not approve it.",
438
+ ];
439
+ const grounded = filterExtractionResultBySource(
440
+ resultOf(claims.map((content) => ({ content, quote }))),
441
+ source,
442
+ source,
443
+ );
444
+ assert.deepEqual(keptContents(grounded), claims);
445
+ });
446
+
447
+ test("a negator in a claim clause that shares only the subject does not keep the denial", () => {
448
+ const source = "Notes: Alice did not approve the budget. Alice did not review the contract.";
449
+ const grounded = filterExtractionResultBySource(
450
+ resultOf([
451
+ {
452
+ content: "Alice did not review the contract. She approved the budget.",
453
+ quote: "Alice did not approve the budget.",
454
+ },
455
+ ]),
456
+ source,
457
+ source,
458
+ );
459
+ assert.equal(grounded.facts.length, 0);
460
+ });
461
+
413
462
  test("a claim that inverts a denial verb in the quote is rejected", () => {
414
463
  const source = "Notes: Alice denied approving the budget. Bob reviewed it.";
415
464
  const grounded = filterExtractionResultBySource(
@@ -130,24 +130,62 @@ function hasNegation(text: string): boolean {
130
130
  return NEGATOR_PATTERN.test(text.replace(/[\u2018\u2019]/gu, "'")) || hasDenialReporting(text);
131
131
  }
132
132
 
133
+ const CLAUSE_PATTERN = /[^;.!?\n]+[;.!?]*/gu;
134
+ /** Split "X, and Y" claim coordination without touching list commas. */
135
+ const CLAIM_COORD_SPLIT = /,\s{1,8}(?:and|but|or|nor)\s{1,8}/iu;
136
+
137
+ function splitClaimClauses(content: string): string[] {
138
+ return (content.match(CLAUSE_PATTERN) ?? [content]).flatMap((clause) => {
139
+ const parts = clause
140
+ .split(CLAIM_COORD_SPLIT)
141
+ .map((part) => part.trim())
142
+ .filter((part) => part.length > 0);
143
+ return parts.length > 0 ? parts : [clause];
144
+ });
145
+ }
146
+
147
+ function clauseOverlap(clauseTokens: readonly string[], target: string) {
148
+ const has = tokenPool(tokenize(target));
149
+ const shared = clauseTokens.filter(has).length;
150
+ return { shared, ratio: clauseTokens.length === 0 ? 0 : shared / clauseTokens.length };
151
+ }
152
+
153
+ /** What a quote clause's first negator governs: the words after it. */
154
+ function negatedScope(clause: string): string {
155
+ const match = NEGATOR_PATTERN.exec(clause.replace(/[\u2018\u2019]/gu, "'"));
156
+ return match ? clause.slice(match.index + match[0].length) : clause;
157
+ }
158
+
133
159
  /**
134
160
  * True when the claim asserts what a quote clause denies or does not assert:
135
161
  * "did not approve the budget" or "denied approving it" restated as "approved
136
162
  * the budget", "suspects Bob stole funds" or "Is Alice the CEO?" restated as a
137
- * plain statement. A claim that keeps a negator, exclusion word, or reporting
138
- * marker ("do not X" reworded as "X is excluded") is not a misstatement.
163
+ * plain statement. The claim must restate the quote clause, and none of its
164
+ * clauses that share a word with what the negator governs may keep the
165
+ * negator, exclusion word, or reporting marker ("do not X" reworded as "X is
166
+ * excluded", or "Alice reviewed it, but she did not approve it"). A negator in
167
+ * a clause about something else does not count, so a claim that keeps one
168
+ * quote negation can still misstate another quote clause, and a claim clause
169
+ * that shares only the subject does not satisfy the check.
139
170
  */
140
171
  function misstatesQuote(content: string, quote: string): boolean {
141
- const keepsNegation = hasNegation(content) || EXCLUSION_PATTERN.test(content);
142
- const keepsReporting = hasNonAssertiveReporting(content) || content.trim().endsWith("?");
143
- const contentHas = tokenPool(tokenize(content));
144
- return (quote.match(/[^;.!?\n]+[;.!?]*/gu) ?? []).some((clause) => {
145
- const negated = hasNegation(clause) && !keepsNegation;
146
- const unasserted = (clause.trim().endsWith("?") || hasNonAssertiveReporting(clause)) && !keepsReporting;
147
- if (!negated && !unasserted) return false;
148
- const clauseTokens = [...tokenize(clause)];
149
- const shared = clauseTokens.filter(contentHas).length;
150
- return shared >= GROUNDING_MIN_SHARED_TOKENS && shared / clauseTokens.length >= 0.5;
172
+ const claimClauses = splitClaimClauses(content);
173
+ return (quote.match(CLAUSE_PATTERN) ?? []).some((quoteClause) => {
174
+ const needsNegation = hasNegation(quoteClause);
175
+ const needsReporting = quoteClause.trim().endsWith("?") || hasNonAssertiveReporting(quoteClause);
176
+ if (!needsNegation && !needsReporting) return false;
177
+ const clauseTokens = [...tokenize(quoteClause)];
178
+ const whole = clauseOverlap(clauseTokens, content);
179
+ if (whole.shared < GROUNDING_MIN_SHARED_TOKENS || whole.ratio < 0.5) return false;
180
+ const clausesSharing = (tokens: readonly string[]) =>
181
+ claimClauses.filter((claimClause) => clauseOverlap(tokens, claimClause).shared > 0);
182
+ if (needsNegation) {
183
+ const scopeTokens = [...tokenize(negatedScope(quoteClause))];
184
+ const related = clausesSharing(scopeTokens.length > 0 ? scopeTokens : clauseTokens);
185
+ if (!related.some((text) => hasNegation(text) || EXCLUSION_PATTERN.test(text))) return true;
186
+ }
187
+ return needsReporting
188
+ && !clausesSharing(clauseTokens).some((text) => hasNonAssertiveReporting(text) || text.trim().endsWith("?"));
151
189
  });
152
190
  }
153
191
 
@@ -1,14 +1,27 @@
1
- import test from "node:test";
1
+ import test, { mock } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdtemp, rm } from "node:fs/promises";
3
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { StorageManager } from "./storage.js";
7
7
  import { withHeldFileLock } from "./utils/serialize-mutations.js";
8
8
  import { confirmActiveFactDuplicate, persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
9
9
 
10
- function probe(storage: StorageManager) {
11
- return { hasContentHashDedup: (s: StorageManager, key: string) => s.hasFactContentHash(key) };
10
+ const SKY_OPTS = {
11
+ category: "fact",
12
+ contentHashDedupKey: "the sky is blue",
13
+ connector: undefined,
14
+ citationEnabled: false,
15
+ citationTemplate: "",
16
+ } as const;
17
+ const CONTENDED_LOCK_OPTIONS = { maxWaitMs: 40, pollMs: 10, retryMaxAttempts: 2, retryBaseMs: 10 };
18
+
19
+ /** Hold a fresh fact-hash index lock the way a peer mid-publish does; returns the release. */
20
+ async function holdIndexLock(root: string): Promise<() => Promise<void>> {
21
+ const lockPath = path.join(root, "state", "fact-hashes.txt.lock");
22
+ await mkdir(path.dirname(lockPath), { recursive: true });
23
+ await writeFile(lockPath, `99999 peer-owner ${new Date().toISOString()}\n`);
24
+ return () => rm(lockPath, { force: true });
12
25
  }
13
26
 
14
27
  async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () => Promise<void> }> {
@@ -19,14 +32,7 @@ async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () =>
19
32
  test("confirmActiveFactDuplicate sees a fact another session committed into the same namespace", async () => {
20
33
  const { storage, cleanup } = await makeStorage();
21
34
  try {
22
- const recheck = () =>
23
- confirmActiveFactDuplicate(probe(storage), storage, {
24
- category: "fact",
25
- contentHashDedupKey: "the sky is blue",
26
- connector: undefined,
27
- citationEnabled: false,
28
- citationTemplate: "",
29
- });
35
+ const recheck = () => confirmActiveFactDuplicate(storage, SKY_OPTS);
30
36
  assert.equal(await recheck(), false, "clean namespace must not report a duplicate");
31
37
 
32
38
  // A concurrent session (separate manager, same dir) commits the fact.
@@ -48,14 +54,7 @@ test("persistUnderFactDedupWindow suppresses on recheck hit and writes on a miss
48
54
  await storage.writeMemory("fact", "the sky is blue");
49
55
  return "written";
50
56
  };
51
- const recheck = () =>
52
- confirmActiveFactDuplicate(probe(storage), storage, {
53
- category: "fact",
54
- contentHashDedupKey: "the sky is blue",
55
- connector: undefined,
56
- citationEnabled: false,
57
- citationTemplate: "",
58
- });
57
+ const recheck = () => confirmActiveFactDuplicate(storage, SKY_OPTS);
59
58
 
60
59
  // First session: recheck misses, write proceeds.
61
60
  const first = await persistUnderFactDedupWindow(storage["baseDir"], recheck, write);
@@ -114,3 +113,87 @@ test("persistUnderFactDedupWindow fails closed when a peer holds fact-dedup.lock
114
113
  await cleanup();
115
114
  }
116
115
  });
116
+
117
+ test("lookupContentHash never pairs a stale MISS with authority while a peer holds the index lock", async () => {
118
+ const { storage, cleanup } = await makeStorage();
119
+ try {
120
+ storage.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
121
+ assert.equal(await storage.isFactContentHashAuthoritative(), true, "starts authoritative");
122
+ const peer = new StorageManager(storage["baseDir"]);
123
+ await peer.writeMemory("fact", "The sky is BLUE!");
124
+ const release = await holdIndexLock(storage["baseDir"]);
125
+ try {
126
+ assert.deepEqual(
127
+ await storage.lookupContentHash(SKY_OPTS.contentHashDedupKey),
128
+ { present: false, authoritative: false },
129
+ "the rebuild cannot run, so the stale MISS must not claim authority",
130
+ );
131
+ assert.equal(await confirmActiveFactDuplicate(storage, SKY_OPTS), true, "the corpus confirms the peer's fact");
132
+ } finally {
133
+ await release();
134
+ }
135
+ assert.deepEqual(
136
+ await storage.lookupContentHash(SKY_OPTS.contentHashDedupKey),
137
+ { present: true, authoritative: true },
138
+ "once the lock is free the rebuild publishes and the hit is authoritative",
139
+ );
140
+ } finally {
141
+ await cleanup();
142
+ }
143
+ });
144
+
145
+ test("confirmActiveFactDuplicate stays a duplicate when the peer releases the index lock right after the lookup", async () => {
146
+ const { storage, cleanup } = await makeStorage();
147
+ try {
148
+ storage.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
149
+ assert.equal(await storage.isFactContentHashAuthoritative(), true, "starts authoritative");
150
+ const peer = new StorageManager(storage["baseDir"]);
151
+ await peer.writeMemory("fact", "The sky is BLUE!");
152
+ const release = await holdIndexLock(storage["baseDir"]);
153
+ const lookup = storage.lookupContentHash.bind(storage);
154
+ mock.method(storage, "lookupContentHash", async (content: string) => {
155
+ const answer = await lookup(content);
156
+ await release();
157
+ return answer;
158
+ });
159
+ assert.equal(
160
+ await confirmActiveFactDuplicate(storage, SKY_OPTS),
161
+ true,
162
+ "a lock released after the lookup must not turn the stale MISS into a clean not-duplicate",
163
+ );
164
+ } finally {
165
+ await cleanup();
166
+ }
167
+ });
168
+
169
+ test("two managers on one directory commit the same fact through the window exactly once", async () => {
170
+ const { storage, cleanup } = await makeStorage();
171
+ try {
172
+ const peer = new StorageManager(storage["baseDir"]);
173
+ const commit = (manager: StorageManager) =>
174
+ persistUnderFactDedupWindow(
175
+ manager.dir,
176
+ () => confirmActiveFactDuplicate(manager, SKY_OPTS),
177
+ () => manager.writeMemory("fact", "The sky is blue"),
178
+ );
179
+ const outcomes = await Promise.all([commit(storage), commit(peer)]);
180
+ assert.equal(outcomes.filter((outcome) => !outcome.suppressed).length, 1, "one writer wins, the other is suppressed");
181
+ const stored = await new StorageManager(storage["baseDir"]).readAllMemories();
182
+ assert.equal(stored.filter((memory) => memory.content.includes("sky is blue")).length, 1);
183
+ } finally {
184
+ await cleanup();
185
+ }
186
+ });
187
+
188
+ test("confirmActiveFactDuplicate keeps the authoritative MISS fast path", async () => {
189
+ const { storage, cleanup } = await makeStorage();
190
+ try {
191
+ assert.equal(await storage.isFactContentHashAuthoritative(), true, "warm the index so no rebuild reads the corpus");
192
+ const corpusReads = mock.method(storage, "readAllMemories");
193
+ assert.equal(await confirmActiveFactDuplicate(storage, SKY_OPTS), false);
194
+ assert.equal(corpusReads.mock.callCount(), 0, "authoritative MISS must not scan the corpus");
195
+ } finally {
196
+ await cleanup();
197
+ }
198
+ });
199
+