@remnic/core 10.0.91 → 10.0.93

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 (522) hide show
  1. package/dist/access-admin-ops-surface.js +334 -61
  2. package/dist/access-authorization-probe.js +2 -283
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +1 -282
  6. package/dist/access-cli.js +288 -304
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-coding-context-resolution.js +3 -3
  9. package/dist/access-extraction-force-flush.js +334 -61
  10. package/dist/access-http-briefing.js +334 -62
  11. package/dist/access-http-query.js +334 -63
  12. package/dist/access-http.js +332 -114
  13. package/dist/access-identity-continuity-surface.js +334 -61
  14. package/dist/access-lcm-surface.js +334 -61
  15. package/dist/access-mcp-frames.d.ts +20 -6
  16. package/dist/access-mcp-frames.d.ts.map +1 -1
  17. package/dist/access-mcp-frames.js +1 -1
  18. package/dist/access-mcp.d.ts +5 -0
  19. package/dist/access-mcp.d.ts.map +1 -1
  20. package/dist/access-mcp.js +332 -89
  21. package/dist/access-namespace-preflight.js +2 -282
  22. package/dist/access-observe-transcript.js +3 -3
  23. package/dist/access-observe-write-category.js +6 -6
  24. package/dist/access-observe-write-surface.js +334 -61
  25. package/dist/access-offline-manifest.js +12 -11
  26. package/dist/access-operations-batch.js +332 -66
  27. package/dist/access-operations.js +332 -75
  28. package/dist/access-recall-concurrency.js +334 -61
  29. package/dist/access-recall-diagnostics.js +6 -5
  30. package/dist/access-recall-response.js +334 -61
  31. package/dist/access-recall-surface.js +334 -61
  32. package/dist/access-schema.js +2 -2
  33. package/dist/access-service.d.ts +1 -0
  34. package/dist/access-service.d.ts.map +1 -1
  35. package/dist/access-service.js +337 -64
  36. package/dist/access-surface-catalog.js +6 -283
  37. package/dist/access-surface-catalog.js.map +1 -1
  38. package/dist/active-context-transform.js +1 -1
  39. package/dist/active-memory-bridge.js +3 -2
  40. package/dist/active-recall.js +36 -37
  41. package/dist/active-recall.js.map +1 -1
  42. package/dist/activity/journal-extract.d.ts +13 -1
  43. package/dist/activity/journal-extract.d.ts.map +1 -1
  44. package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
  45. package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
  46. package/dist/briefing.js +20 -20
  47. package/dist/buffer.js +16 -16
  48. package/dist/calibration.js +10 -10
  49. package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
  50. package/dist/causal-consolidation.js +23 -23
  51. package/dist/{chunk-QL7RHYK6.js → chunk-2LMA22UU.js} +5 -5
  52. package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
  53. package/dist/{chunk-RSK2WIEM.js → chunk-2YODOG2O.js} +2 -2
  54. package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
  55. package/dist/{chunk-OFVCFKDH.js → chunk-3CYOCE6U.js} +3 -3
  56. package/dist/{chunk-6HM6HARE.js → chunk-3FBAEPCU.js} +2 -2
  57. package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
  58. package/dist/{chunk-V4XE263I.js → chunk-43AA5FVD.js} +1 -1
  59. package/dist/chunk-43AA5FVD.js.map +1 -0
  60. package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
  61. package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
  62. package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
  63. package/dist/{chunk-PUEO37QG.js → chunk-5YT2IIFX.js} +729 -17
  64. package/dist/chunk-5YT2IIFX.js.map +1 -0
  65. package/dist/{chunk-P5VZSSYL.js → chunk-622V2AZA.js} +233 -40
  66. package/dist/chunk-622V2AZA.js.map +1 -0
  67. package/dist/{chunk-3UMDRGFS.js → chunk-65HWAQ4F.js} +3 -3
  68. package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
  69. package/dist/chunk-A2WFUMUN.js +86 -0
  70. package/dist/chunk-A2WFUMUN.js.map +1 -0
  71. package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
  72. package/dist/chunk-AHA5VQGP.js +117 -0
  73. package/dist/chunk-AHA5VQGP.js.map +1 -0
  74. package/dist/{chunk-AOFEMUHG.js → chunk-ATG2PQ4V.js} +56 -5
  75. package/dist/chunk-ATG2PQ4V.js.map +1 -0
  76. package/dist/{chunk-63O7LVMN.js → chunk-B33YWUJV.js} +3 -3
  77. package/dist/{chunk-QVMOCJRV.js → chunk-B377OKUP.js} +2 -2
  78. package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
  79. package/dist/chunk-B3DR2JBZ.js.map +1 -0
  80. package/dist/{chunk-ZT6WPSII.js → chunk-BGHCSLE5.js} +2 -2
  81. package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
  82. package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
  83. package/dist/{chunk-EV76IZ37.js → chunk-BTI4C4MA.js} +2 -2
  84. package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
  85. package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
  86. package/dist/{chunk-3XIWW4TB.js → chunk-CGJYL633.js} +2 -2
  87. package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
  88. package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
  89. package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
  90. package/dist/{chunk-RO7RUBLD.js → chunk-EFL24KOI.js} +2 -2
  91. package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
  92. package/dist/{chunk-Z4Q6USKJ.js → chunk-ETBVCRVL.js} +4 -4
  93. package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
  94. package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
  95. package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
  96. package/dist/chunk-FA7SLQ5X.js.map +1 -0
  97. package/dist/{chunk-SHUBTAY2.js → chunk-FMKILENU.js} +14 -14
  98. package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
  99. package/dist/{chunk-J4MMPQ7Q.js → chunk-GQXOBKPL.js} +7 -9
  100. package/dist/{chunk-J4MMPQ7Q.js.map → chunk-GQXOBKPL.js.map} +1 -1
  101. package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
  102. package/dist/chunk-HLUHEMN4.js.map +1 -0
  103. package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
  104. package/dist/chunk-JTSSSR4L.js +359 -0
  105. package/dist/chunk-JTSSSR4L.js.map +1 -0
  106. package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
  107. package/dist/chunk-JVBAPAWZ.js.map +1 -0
  108. package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
  109. package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
  110. package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
  111. package/dist/chunk-LLPMMMWG.js.map +1 -0
  112. package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
  113. package/dist/{chunk-JC36FPQ5.js → chunk-LPV72KRQ.js} +2 -2
  114. package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
  115. package/dist/{chunk-GGRRF4PZ.js → chunk-MDPT6QFK.js} +1 -1
  116. package/dist/{chunk-GGRRF4PZ.js.map → chunk-MDPT6QFK.js.map} +1 -1
  117. package/dist/{chunk-VPQFK63H.js → chunk-MH632KPO.js} +2 -2
  118. package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
  119. package/dist/chunk-MLAMXQAW.js.map +1 -0
  120. package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
  121. package/dist/{chunk-ZZ67UTQX.js → chunk-MUX7IL77.js} +2 -2
  122. package/dist/{chunk-RYWROSL5.js → chunk-NAQ4A5IX.js} +7 -7
  123. package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
  124. package/dist/{chunk-ADYP7HXE.js → chunk-NHVAX7MH.js} +3 -3
  125. package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
  126. package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
  127. package/dist/chunk-NS7U4ZYU.js.map +1 -0
  128. package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
  129. package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
  130. package/dist/{chunk-7L3TODI7.js → chunk-OJXJWNFY.js} +2 -2
  131. package/dist/{chunk-66L5ZRJC.js → chunk-P3WRNWWM.js} +157 -4
  132. package/dist/chunk-P3WRNWWM.js.map +1 -0
  133. package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
  134. package/dist/chunk-PC5DRBGD.js.map +1 -0
  135. package/dist/{chunk-EURPSF4Y.js → chunk-PH5ZQM35.js} +2 -2
  136. package/dist/{chunk-BMWMTAQF.js → chunk-PRAUN6PN.js} +3 -3
  137. package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
  138. package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
  139. package/dist/{chunk-AH637HZ5.js → chunk-QDESY76F.js} +3 -3
  140. package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
  141. package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
  142. package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
  143. package/dist/{chunk-EWJZZDRG.js → chunk-R4BBGPI2.js} +219 -219
  144. package/dist/chunk-R4BBGPI2.js.map +1 -0
  145. package/dist/{chunk-LJQFJF4J.js → chunk-RM2YUFH6.js} +6 -6
  146. package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
  147. package/dist/chunk-RQFXZYTF.js.map +1 -0
  148. package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
  149. package/dist/chunk-S3S72HRM.js.map +1 -0
  150. package/dist/{chunk-6QMWECO5.js → chunk-SGQPR5SX.js} +7 -7
  151. package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
  152. package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
  153. package/dist/{chunk-5UCMYPVF.js → chunk-SVT5IA2H.js} +2 -2
  154. package/dist/{chunk-HVKUCT3W.js → chunk-T4KY6UYF.js} +5 -3
  155. package/dist/{chunk-HVKUCT3W.js.map → chunk-T4KY6UYF.js.map} +1 -1
  156. package/dist/{chunk-FRSHF6FU.js → chunk-TJ4F6H4H.js} +3 -3
  157. package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
  158. package/dist/{chunk-NJJMVS5Y.js → chunk-TRQE3RYR.js} +5 -5
  159. package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
  160. package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
  161. package/dist/{chunk-YS2GHSWN.js → chunk-UBEKET2V.js} +389 -39
  162. package/dist/chunk-UBEKET2V.js.map +1 -0
  163. package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
  164. package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
  165. package/dist/{chunk-KZSGBWOJ.js → chunk-V2KGDESV.js} +2 -2
  166. package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
  167. package/dist/{chunk-BCCG7VWM.js → chunk-V65H7MI6.js} +396 -13
  168. package/dist/chunk-V65H7MI6.js.map +1 -0
  169. package/dist/chunk-W54Z2MOP.js +4500 -0
  170. package/dist/chunk-W54Z2MOP.js.map +1 -0
  171. package/dist/{chunk-N74SGESS.js → chunk-WKSQTMU7.js} +2 -2
  172. package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
  173. package/dist/{chunk-WWK6J6SE.js → chunk-WQURALGV.js} +4996 -2748
  174. package/dist/chunk-WQURALGV.js.map +1 -0
  175. package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
  176. package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
  177. package/dist/{chunk-GNXGJBCA.js → chunk-XIYLWEXC.js} +2 -2
  178. package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
  179. package/dist/{chunk-7O2MC42R.js → chunk-XNRLRNQF.js} +2 -2
  180. package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
  181. package/dist/{chunk-JBCCWVU2.js → chunk-XRDYRWZI.js} +2 -2
  182. package/dist/chunk-XRWWCW4H.js +102 -0
  183. package/dist/chunk-XRWWCW4H.js.map +1 -0
  184. package/dist/chunk-YKJBSMMP.js +67137 -0
  185. package/dist/chunk-YKJBSMMP.js.map +1 -0
  186. package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
  187. package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
  188. package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
  189. package/dist/{chunk-KEPXC4KN.js → chunk-ZSE774RV.js} +2 -2
  190. package/dist/cli.d.ts.map +1 -1
  191. package/dist/cli.js +305 -187
  192. package/dist/coding/export-okf-codegraph.js +29 -29
  193. package/dist/compounding/engine.js +23 -24
  194. package/dist/config.d.ts.map +1 -1
  195. package/dist/config.js +36 -37
  196. package/dist/connectors/codex-materialize-runner.js +20 -20
  197. package/dist/connectors/index.js +21 -21
  198. package/dist/connectors/live/framework.d.ts +7 -0
  199. package/dist/connectors/live/framework.d.ts.map +1 -1
  200. package/dist/connectors/live/google-drive.d.ts +17 -3
  201. package/dist/connectors/live/google-drive.d.ts.map +1 -1
  202. package/dist/consolidation-provenance-check.js +4 -4
  203. package/dist/consolidation-undo.js +5 -5
  204. package/dist/contradiction/index.js +7 -6
  205. package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
  206. package/dist/convergence-refresh.js +22 -22
  207. package/dist/corpus-watermark.js +23 -23
  208. package/dist/deep-recall-http-glue.js +2 -2
  209. package/dist/deep-recall-policy-llm.js +4 -4
  210. package/dist/direct-answer-wiring.js +2 -2
  211. package/dist/embedding-fallback.js +1 -1
  212. package/dist/entity-retrieval.js +21 -21
  213. package/dist/eval-trajectory-store.js +2 -2
  214. package/dist/eval-trajectory.js +2 -2
  215. package/dist/event-order-recall.js +2 -2
  216. package/dist/explicit-capture.d.ts +3 -2
  217. package/dist/explicit-capture.d.ts.map +1 -1
  218. package/dist/explicit-capture.js +530 -6
  219. package/dist/explicit-cue-recall.js +2 -2
  220. package/dist/external-wiki-access.js +2 -283
  221. package/dist/extraction-faithfulness.js +4 -4
  222. package/dist/extraction-judge.js +5 -5
  223. package/dist/extraction-normalization.js +1 -1
  224. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  225. package/dist/extraction-quote-grounding.js +1 -1
  226. package/dist/extraction-source-grounding-rules.js +2 -2
  227. package/dist/extraction-source-grounding.js +3 -3
  228. package/dist/extraction.js +43 -44
  229. package/dist/fallback-llm-codex-subscription.js +2 -2
  230. package/dist/fallback-llm.js +3 -3
  231. package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
  232. package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
  233. package/dist/focused-list-recall.js +2 -2
  234. package/dist/harmonic-retrieval.js +20 -20
  235. package/dist/index.js +776 -797
  236. package/dist/lcm/index.js +2 -2
  237. package/dist/live-connectors-runner.d.ts +2 -0
  238. package/dist/live-connectors-runner.d.ts.map +1 -1
  239. package/dist/live-connectors-runner.js +3 -4
  240. package/dist/local-llm-config.js +2 -2
  241. package/dist/local-llm-helpers.d.ts +4 -0
  242. package/dist/local-llm-helpers.d.ts.map +1 -1
  243. package/dist/local-llm-helpers.js +3 -1
  244. package/dist/local-llm.d.ts +4 -1
  245. package/dist/local-llm.d.ts.map +1 -1
  246. package/dist/local-llm.js +4 -4
  247. package/dist/location/index.js +27 -29
  248. package/dist/maintenance/memory-governance.js +20 -20
  249. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
  250. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
  251. package/dist/maintenance/rebuild-memory-projection.js +21 -21
  252. package/dist/memory-browse-operations.js +2 -283
  253. package/dist/memory-browse.js +8 -7
  254. package/dist/memory-controller.js +2 -2
  255. package/dist/memory-subject.js +2 -2
  256. package/dist/namespaces/migrate.js +21 -21
  257. package/dist/namespaces/search.js +5 -5
  258. package/dist/namespaces/storage.js +20 -20
  259. package/dist/offline-sync-changeset-generations.js +1 -1
  260. package/dist/offline-sync-embedding-generation.js +1 -1
  261. package/dist/offline-sync-impression-drain.js +6 -6
  262. package/dist/offline-sync-snapshot-stream.js +1 -1
  263. package/dist/offline-sync.js +1 -1
  264. package/dist/operator-doctor-corpus.js +24 -24
  265. package/dist/operator-toolkit.js +58 -61
  266. package/dist/orchestration/backlog-scheduler.js +279 -295
  267. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  268. package/dist/orchestration/buffer-drain-runner.js +279 -295
  269. package/dist/orchestration/extraction-run.d.ts +4 -0
  270. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  271. package/dist/orchestration/fact-dedup-window.d.ts +1 -1
  272. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  273. package/dist/orchestration/maintenance.js +39 -42
  274. package/dist/orchestrator.d.ts +11 -11
  275. package/dist/orchestrator.d.ts.map +1 -1
  276. package/dist/orchestrator.js +291 -308
  277. package/dist/page-versioning.js +3 -3
  278. package/dist/qmd-daemon-session.js +2 -2
  279. package/dist/qmd.js +3 -3
  280. package/dist/recall-explain-renderer.js +3 -3
  281. package/dist/recall-navigate-access.js +2 -2
  282. package/dist/recall-navigate-disclosure.js +2 -2
  283. package/dist/recall-navigate.js +2 -2
  284. package/dist/recall-navigation-operations.js +2 -283
  285. package/dist/recall-navigation.js +334 -61
  286. package/dist/recall-planner-llm.js +3 -3
  287. package/dist/recall-state-view-admission.js +6 -6
  288. package/dist/recall-why-service.js +10 -9
  289. package/dist/recall-why.js +4 -3
  290. package/dist/recall-xray-cli.js +4 -4
  291. package/dist/recall-xray-renderer.js +3 -3
  292. package/dist/recall-xray.js +2 -2
  293. package/dist/reconcile/manifest.js +3 -3
  294. package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
  295. package/dist/response-guidance-recall.js +2 -2
  296. package/dist/resume-bundles.js +40 -41
  297. package/dist/salvage-envelope.js +4 -4
  298. package/dist/search/factory.js +4 -4
  299. package/dist/search/index.js +4 -4
  300. package/dist/semantic-consolidation.js +23 -23
  301. package/dist/semantic-rule-promotion.js +20 -20
  302. package/dist/semantic-rule-verifier.js +20 -20
  303. package/dist/shared-context/manager.js +2 -3
  304. package/dist/source-agent-qualifier.js +32 -33
  305. package/dist/standup/build.js +20 -20
  306. package/dist/{state-store-I5HLZTZT.js → state-store-EEAX2MBS.js} +2 -2
  307. package/dist/storage/content-hash-index.d.ts +10 -0
  308. package/dist/storage/content-hash-index.d.ts.map +1 -1
  309. package/dist/storage.js +19 -19
  310. package/dist/subject-config.js +3 -3
  311. package/dist/summarizer.d.ts +4 -1
  312. package/dist/summarizer.d.ts.map +1 -1
  313. package/dist/summarizer.js +6 -6
  314. package/dist/support-passport/index.js +53 -297
  315. package/dist/targeted-fact-recall.js +2 -2
  316. package/dist/tier-routing.js +3 -2
  317. package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
  318. package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
  319. package/dist/transcript.js +2 -2
  320. package/dist/transfer/capsule-import.js +4 -4
  321. package/dist/transfer/export-okf.js +20 -20
  322. package/dist/types.d.ts +10 -10
  323. package/dist/types.d.ts.map +1 -1
  324. package/dist/types.js +1 -1
  325. package/dist/verified-recall.js +20 -20
  326. package/dist/wearables/memory-gen.d.ts +5 -1
  327. package/dist/wearables/memory-gen.d.ts.map +1 -1
  328. package/dist/wearables/service.d.ts.map +1 -1
  329. package/dist/who-knows-cli.js +21 -21
  330. package/dist/who-knows.js +20 -20
  331. package/dist/write-envelope.js +3 -3
  332. package/package.json +2 -2
  333. package/src/access-boundary.ts +5 -1
  334. package/src/access-category-alias.test.ts +5 -0
  335. package/src/access-mcp-frames.test.ts +60 -5
  336. package/src/access-mcp-frames.ts +72 -18
  337. package/src/access-mcp.test.ts +66 -2
  338. package/src/access-mcp.ts +19 -13
  339. package/src/access-service-coding-write.test.ts +7 -0
  340. package/src/access-service.ts +3 -0
  341. package/src/activity/journal-extract-storage.test.ts +31 -0
  342. package/src/activity/journal-extract.test.ts +1 -0
  343. package/src/activity/journal-extract.ts +24 -3
  344. package/src/cli.ts +1 -2
  345. package/src/config.test.ts +17 -0
  346. package/src/config.ts +25 -7
  347. package/src/connectors/live/framework.ts +8 -0
  348. package/src/connectors/live/google-drive.test.ts +74 -0
  349. package/src/connectors/live/google-drive.ts +138 -11
  350. package/src/explicit-capture.test.ts +109 -17
  351. package/src/explicit-capture.ts +90 -37
  352. package/src/extraction-quote-grounding.test.ts +20 -0
  353. package/src/extraction-quote-grounding.ts +21 -11
  354. package/src/fact-dedup-window.test.ts +37 -0
  355. package/src/live-connectors-runner.ts +3 -0
  356. package/src/live-connectors-secretref.test.ts +126 -0
  357. package/src/local-llm-helpers.ts +12 -0
  358. package/src/local-llm-shutdown.test.ts +194 -0
  359. package/src/local-llm.ts +32 -30
  360. package/src/orchestration/extraction-run.ts +12 -3
  361. package/src/orchestration/fact-dedup-window.ts +5 -1
  362. package/src/orchestrator-connector-dedup.test.ts +9 -5
  363. package/src/orchestrator.ts +52 -55
  364. package/src/storage/content-hash-index.ts +27 -2
  365. package/src/storage-fact-hash-publish-failure.test.ts +87 -0
  366. package/src/summarizer-shutdown.test.ts +195 -0
  367. package/src/summarizer.ts +15 -2
  368. package/src/types.ts +10 -10
  369. package/src/wearables/memory-gen.test.ts +20 -0
  370. package/src/wearables/memory-gen.ts +14 -7
  371. package/src/wearables/service.ts +14 -1
  372. package/src/wearables/storage-io.test.ts +25 -0
  373. package/dist/chunk-27XYV2XL.js +0 -43
  374. package/dist/chunk-27XYV2XL.js.map +0 -1
  375. package/dist/chunk-5KBIOI4G.js.map +0 -1
  376. package/dist/chunk-66L5ZRJC.js.map +0 -1
  377. package/dist/chunk-7HTUYFR3.js.map +0 -1
  378. package/dist/chunk-AOFEMUHG.js.map +0 -1
  379. package/dist/chunk-AP6NCP3V.js.map +0 -1
  380. package/dist/chunk-APYY5CX3.js +0 -335
  381. package/dist/chunk-APYY5CX3.js.map +0 -1
  382. package/dist/chunk-ATPWMT2E.js +0 -223
  383. package/dist/chunk-ATPWMT2E.js.map +0 -1
  384. package/dist/chunk-BCCG7VWM.js.map +0 -1
  385. package/dist/chunk-E6M225BJ.js.map +0 -1
  386. package/dist/chunk-EQ4U73UH.js.map +0 -1
  387. package/dist/chunk-ETSJD5IV.js +0 -361
  388. package/dist/chunk-ETSJD5IV.js.map +0 -1
  389. package/dist/chunk-EWJZZDRG.js.map +0 -1
  390. package/dist/chunk-FHGWKODN.js +0 -2300
  391. package/dist/chunk-FHGWKODN.js.map +0 -1
  392. package/dist/chunk-GRTKLWYX.js +0 -621
  393. package/dist/chunk-GRTKLWYX.js.map +0 -1
  394. package/dist/chunk-IGLJNHOX.js +0 -62
  395. package/dist/chunk-IGLJNHOX.js.map +0 -1
  396. package/dist/chunk-IPT2OY4B.js.map +0 -1
  397. package/dist/chunk-IUKYFTGW.js.map +0 -1
  398. package/dist/chunk-KUIRDD74.js +0 -3991
  399. package/dist/chunk-KUIRDD74.js.map +0 -1
  400. package/dist/chunk-MVMRUGBH.js +0 -1919
  401. package/dist/chunk-MVMRUGBH.js.map +0 -1
  402. package/dist/chunk-ODL4ZTNI.js +0 -3644
  403. package/dist/chunk-ODL4ZTNI.js.map +0 -1
  404. package/dist/chunk-OGXXGHW6.js +0 -223
  405. package/dist/chunk-OGXXGHW6.js.map +0 -1
  406. package/dist/chunk-P5VZSSYL.js.map +0 -1
  407. package/dist/chunk-PPCLZ4JU.js +0 -28
  408. package/dist/chunk-PPCLZ4JU.js.map +0 -1
  409. package/dist/chunk-PUEO37QG.js.map +0 -1
  410. package/dist/chunk-RRTVGD3J.js +0 -792
  411. package/dist/chunk-RRTVGD3J.js.map +0 -1
  412. package/dist/chunk-STSLB4HT.js +0 -18793
  413. package/dist/chunk-STSLB4HT.js.map +0 -1
  414. package/dist/chunk-T6OZW2WR.js.map +0 -1
  415. package/dist/chunk-TRBGGJKZ.js +0 -32218
  416. package/dist/chunk-TRBGGJKZ.js.map +0 -1
  417. package/dist/chunk-UDKWFDV2.js.map +0 -1
  418. package/dist/chunk-V4XE263I.js.map +0 -1
  419. package/dist/chunk-V7IZKJI3.js +0 -22
  420. package/dist/chunk-V7IZKJI3.js.map +0 -1
  421. package/dist/chunk-VMVVSZTD.js +0 -61
  422. package/dist/chunk-VMVVSZTD.js.map +0 -1
  423. package/dist/chunk-VNUOAXWP.js +0 -203
  424. package/dist/chunk-VNUOAXWP.js.map +0 -1
  425. package/dist/chunk-WLBEZZK5.js +0 -729
  426. package/dist/chunk-WLBEZZK5.js.map +0 -1
  427. package/dist/chunk-WUUES6CU.js +0 -25
  428. package/dist/chunk-WUUES6CU.js.map +0 -1
  429. package/dist/chunk-WWK6J6SE.js.map +0 -1
  430. package/dist/chunk-XICOH5HT.js +0 -346
  431. package/dist/chunk-XICOH5HT.js.map +0 -1
  432. package/dist/chunk-YCNIGRRF.js.map +0 -1
  433. package/dist/chunk-YS2GHSWN.js.map +0 -1
  434. package/dist/chunk-ZCLEYJPB.js +0 -10143
  435. package/dist/chunk-ZCLEYJPB.js.map +0 -1
  436. /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
  437. /package/dist/{chunk-QL7RHYK6.js.map → chunk-2LMA22UU.js.map} +0 -0
  438. /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
  439. /package/dist/{chunk-RSK2WIEM.js.map → chunk-2YODOG2O.js.map} +0 -0
  440. /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
  441. /package/dist/{chunk-OFVCFKDH.js.map → chunk-3CYOCE6U.js.map} +0 -0
  442. /package/dist/{chunk-6HM6HARE.js.map → chunk-3FBAEPCU.js.map} +0 -0
  443. /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
  444. /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
  445. /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
  446. /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
  447. /package/dist/{chunk-3UMDRGFS.js.map → chunk-65HWAQ4F.js.map} +0 -0
  448. /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
  449. /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
  450. /package/dist/{chunk-63O7LVMN.js.map → chunk-B33YWUJV.js.map} +0 -0
  451. /package/dist/{chunk-QVMOCJRV.js.map → chunk-B377OKUP.js.map} +0 -0
  452. /package/dist/{chunk-ZT6WPSII.js.map → chunk-BGHCSLE5.js.map} +0 -0
  453. /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
  454. /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
  455. /package/dist/{chunk-EV76IZ37.js.map → chunk-BTI4C4MA.js.map} +0 -0
  456. /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
  457. /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
  458. /package/dist/{chunk-3XIWW4TB.js.map → chunk-CGJYL633.js.map} +0 -0
  459. /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
  460. /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
  461. /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
  462. /package/dist/{chunk-RO7RUBLD.js.map → chunk-EFL24KOI.js.map} +0 -0
  463. /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
  464. /package/dist/{chunk-Z4Q6USKJ.js.map → chunk-ETBVCRVL.js.map} +0 -0
  465. /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
  466. /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
  467. /package/dist/{chunk-SHUBTAY2.js.map → chunk-FMKILENU.js.map} +0 -0
  468. /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
  469. /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
  470. /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
  471. /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
  472. /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
  473. /package/dist/{chunk-JC36FPQ5.js.map → chunk-LPV72KRQ.js.map} +0 -0
  474. /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
  475. /package/dist/{chunk-VPQFK63H.js.map → chunk-MH632KPO.js.map} +0 -0
  476. /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
  477. /package/dist/{chunk-ZZ67UTQX.js.map → chunk-MUX7IL77.js.map} +0 -0
  478. /package/dist/{chunk-RYWROSL5.js.map → chunk-NAQ4A5IX.js.map} +0 -0
  479. /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
  480. /package/dist/{chunk-ADYP7HXE.js.map → chunk-NHVAX7MH.js.map} +0 -0
  481. /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
  482. /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
  483. /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
  484. /package/dist/{chunk-7L3TODI7.js.map → chunk-OJXJWNFY.js.map} +0 -0
  485. /package/dist/{chunk-EURPSF4Y.js.map → chunk-PH5ZQM35.js.map} +0 -0
  486. /package/dist/{chunk-BMWMTAQF.js.map → chunk-PRAUN6PN.js.map} +0 -0
  487. /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
  488. /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
  489. /package/dist/{chunk-AH637HZ5.js.map → chunk-QDESY76F.js.map} +0 -0
  490. /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
  491. /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
  492. /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
  493. /package/dist/{chunk-LJQFJF4J.js.map → chunk-RM2YUFH6.js.map} +0 -0
  494. /package/dist/{chunk-6QMWECO5.js.map → chunk-SGQPR5SX.js.map} +0 -0
  495. /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
  496. /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
  497. /package/dist/{chunk-5UCMYPVF.js.map → chunk-SVT5IA2H.js.map} +0 -0
  498. /package/dist/{chunk-FRSHF6FU.js.map → chunk-TJ4F6H4H.js.map} +0 -0
  499. /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
  500. /package/dist/{chunk-NJJMVS5Y.js.map → chunk-TRQE3RYR.js.map} +0 -0
  501. /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
  502. /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
  503. /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
  504. /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
  505. /package/dist/{chunk-KZSGBWOJ.js.map → chunk-V2KGDESV.js.map} +0 -0
  506. /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
  507. /package/dist/{chunk-N74SGESS.js.map → chunk-WKSQTMU7.js.map} +0 -0
  508. /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
  509. /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
  510. /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
  511. /package/dist/{chunk-GNXGJBCA.js.map → chunk-XIYLWEXC.js.map} +0 -0
  512. /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
  513. /package/dist/{chunk-7O2MC42R.js.map → chunk-XNRLRNQF.js.map} +0 -0
  514. /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
  515. /package/dist/{chunk-JBCCWVU2.js.map → chunk-XRDYRWZI.js.map} +0 -0
  516. /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
  517. /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
  518. /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
  519. /package/dist/{chunk-KEPXC4KN.js.map → chunk-ZSE774RV.js.map} +0 -0
  520. /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
  521. /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
  522. /package/dist/{state-store-I5HLZTZT.js.map → state-store-EEAX2MBS.js.map} +0 -0
@@ -1,10 +1,13 @@
1
1
  import assert from "node:assert/strict";
2
+ import os from "node:os";
3
+ import path from "node:path";
2
4
  import test from "node:test";
3
5
 
4
6
  import type { Orchestrator } from "./orchestrator.js";
5
7
  import type { StorageManager } from "./storage.js";
6
8
  import type { PluginConfig } from "./types.js";
7
9
  import type { SealedMemoryEnvelope } from "./write-envelope.js";
10
+ import { withHeldFileLock } from "./utils/serialize-mutations.js";
8
11
  import {
9
12
  InlineExplicitCaptureProcessor,
10
13
  parseInlineExplicitCaptureNotes,
@@ -67,6 +70,7 @@ function createInlineCaptureProcessorProbe(
67
70
  failPrimaryWrites?: boolean;
68
71
  duplicateReadDelayMs?: number;
69
72
  lockBusyAttempts?: number;
73
+ peerFactCommitAfterFirstReadAll?: boolean;
70
74
  } = {}
71
75
  ) {
72
76
  const envelopes: SealedMemoryEnvelope[] = [];
@@ -86,19 +90,38 @@ function createInlineCaptureProcessorProbe(
86
90
  let nextId = 1;
87
91
  let writeTail = Promise.resolve();
88
92
  let lockBusyAttemptsRemaining = options.lockBusyAttempts ?? 0;
93
+ let peerCommitted = false;
94
+ const peerFactContent = "A concurrent writer committed this fact before the capture could.";
95
+ // #3354: the dedup window takes a real per-dir file lock, so the fake needs
96
+ // a writable per-process dir even though its reads/writes stay in memory.
97
+ const storageDir = path.join(os.tmpdir(), `remnic-explicit-capture-probe-${process.pid}`);
89
98
  const storage = {
99
+ dir: storageDir,
90
100
  readAllMemories: async () => {
91
101
  readAllCalls += 1;
92
102
  activeReadAllCalls += 1;
93
103
  maxConcurrentReadAllCalls = Math.max(maxConcurrentReadAllCalls, activeReadAllCalls);
104
+ // A read reflects the corpus as it was when the read began, so a write
105
+ // that lands during the delay is invisible to this caller.
106
+ const snapshot = memories.map((memory) => ({
107
+ ...memory,
108
+ frontmatter: { ...memory.frontmatter },
109
+ }));
94
110
  if (options.duplicateReadDelayMs !== undefined) {
95
111
  await new Promise((resolve) => setTimeout(resolve, options.duplicateReadDelayMs));
96
112
  }
97
113
  activeReadAllCalls -= 1;
98
- return memories.map((memory) => ({
99
- ...memory,
100
- frontmatter: { ...memory.frontmatter },
101
- }));
114
+ // Simulate a concurrent extraction writer committing between this read
115
+ // and a later one: the caller sees the snapshot above without the peer
116
+ // row, while subsequent reads include it.
117
+ if (options.peerFactCommitAfterFirstReadAll && !peerCommitted) {
118
+ peerCommitted = true;
119
+ memories.push({
120
+ frontmatter: { id: `peer-fact-${nextId++}`, category: "fact", tags: [] },
121
+ content: peerFactContent,
122
+ });
123
+ }
124
+ return snapshot;
102
125
  },
103
126
  readAllColdMemories: async () =>
104
127
  coldMemories.map((memory) => ({
@@ -140,12 +163,9 @@ function createInlineCaptureProcessorProbe(
140
163
  release();
141
164
  }
142
165
  },
143
- ...(options.authoritativeFactHashMiss
144
- ? {
145
- hasFactContentHash: async () => false,
146
- isFactContentHashAuthoritative: () => true,
147
- }
148
- : {}),
166
+ lookupContentHash: options.authoritativeFactHashMiss
167
+ ? async () => ({ present: false, authoritative: true })
168
+ : undefined,
149
169
  ...(options.tombstoneBlockedCaptureIndexHit !== undefined
150
170
  ? {
151
171
  checkTombstoneBlockedExplicitCapture: async (content: string, category: string, sourceConnector?: string) => {
@@ -279,6 +299,43 @@ test("persistExplicitCapture serializes duplicate checks across concurrent calls
279
299
  assert.equal(probe.lifecycleEvents.length, 1);
280
300
  });
281
301
 
302
+ test("persistExplicitCapture keeps the duplicate gate and the write atomic for non-fact categories (#3354)", async () => {
303
+ const probe = createInlineCaptureProcessorProbe({ duplicateReadDelayMs: 15 });
304
+ const candidate = {
305
+ content: "Concurrent preference captures must produce one durable capture.",
306
+ category: "preference" as const,
307
+ confidence: 0.9,
308
+ tags: [],
309
+ };
310
+ const [first, second] = await Promise.all([
311
+ persistExplicitCapture(probe.orchestrator, candidate, "memory_store"),
312
+ persistExplicitCapture(probe.orchestrator, candidate, "memory_store"),
313
+ ]);
314
+ assert.equal(Number(first.duplicateOf !== undefined) + Number(second.duplicateOf !== undefined), 1);
315
+ assert.equal(probe.envelopes.length, 1);
316
+ assert.equal(probe.lifecycleEvents.length, 1);
317
+ });
318
+
319
+ test("explicit capture suppresses a fact a concurrent writer commits between the gate and the write", async () => {
320
+ const probe = createInlineCaptureProcessorProbe({ peerFactCommitAfterFirstReadAll: true });
321
+ const candidate = validateExplicitCaptureInput({
322
+ content: "A concurrent writer committed this fact before the capture could.",
323
+ category: "fact",
324
+ });
325
+
326
+ const result = await persistExplicitCapture(probe.orchestrator, candidate, "memory_store");
327
+
328
+ // The peer row committed between the persist-time gate and the dedup
329
+ // window, so the capture must report the raced copy instead of writing a
330
+ // second active fact (#3354).
331
+ assert.equal(result.id, "peer-fact-1");
332
+ assert.equal(result.duplicateOf, "peer-fact-1");
333
+ assert.equal(probe.envelopes.length, 0, "no second sealed write of the raced fact");
334
+ assert.equal(probe.lifecycleEvents.length, 0);
335
+ const active = probe.memories.filter((memory) => (memory.frontmatter.status ?? "active") === "active");
336
+ assert.equal(active.length, 1);
337
+ });
338
+
282
339
  test("inline capture processor hashes the effective authorized input for replay dedupe", async () => {
283
340
  const probe = createInlineCaptureProcessorProbe({ tombstoneBlocked: true });
284
341
  const request = {
@@ -403,6 +460,40 @@ test("inline capture processor queues invalid complete markup and never leaves i
403
460
  assert.equal(probe.envelopes.length, 1);
404
461
  });
405
462
 
463
+ test("an inline fact capture that cannot take the dedup window fails closed and stays retryable (#3354)", async () => {
464
+ const probe = createInlineCaptureProcessorProbe();
465
+ const request = {
466
+ captureMode: "hybrid" as const,
467
+ content: [
468
+ "<memory_note>",
469
+ "content: A contended fact capture must stay retryable.",
470
+ "category: fact",
471
+ "</memory_note>",
472
+ ].join("\n"),
473
+ dedupeKeys: ["window-contention-1"],
474
+ namespace: "principal-project",
475
+ namespacePreResolved: true,
476
+ };
477
+
478
+ // A second process holding fact-dedup.lock: the window waits its bounded
479
+ // 2s, then throws instead of writing unlocked.
480
+ const contended = await withHeldFileLock(
481
+ path.join(probe.storage.dir, "fact-dedup.lock"),
482
+ { staleMs: 60_000, maxWaitMs: 1_000 },
483
+ async (acquired) => {
484
+ assert.ok(acquired, "the test must hold the window lock");
485
+ return await probe.processor.process(request);
486
+ }
487
+ );
488
+ assert.equal(contended.failed, 1);
489
+ assert.equal(probe.envelopes.length, 0);
490
+
491
+ // The failed delivery left its dedupe key unremembered, so the replay commits.
492
+ const replay = await probe.processor.process(request);
493
+ assert.equal(replay.accepted, 1);
494
+ assert.equal(probe.envelopes.length, 1);
495
+ });
496
+
406
497
  test("inline capture review fallback serializes duplicate checks across processor instances", async () => {
407
498
  const probe = createInlineCaptureProcessorProbe({ duplicateReadDelayMs: 15 });
408
499
  const secondProcessor = new InlineExplicitCaptureProcessor(probe.orchestrator, {
@@ -1117,13 +1208,14 @@ test("explicit capture keeps the authoritative fact-hash miss fast path", async
1117
1208
  const result = await persistExplicitCapture(probe.orchestrator, candidate, "memory_store");
1118
1209
 
1119
1210
  assert.equal(result.duplicateOf, undefined);
1120
- assert.deepEqual(probe.blockedIndexArguments(), [
1121
- {
1122
- content: candidate.content,
1123
- category: "fact",
1124
- sourceConnector: undefined,
1125
- },
1126
- ]);
1211
+ // The gate probes once outside the window and the in-window recheck
1212
+ // (#3354) probes again; both take the authoritative-miss fast path.
1213
+ const blockedProbe = {
1214
+ content: candidate.content,
1215
+ category: "fact",
1216
+ sourceConnector: undefined,
1217
+ };
1218
+ assert.deepEqual(probe.blockedIndexArguments(), [blockedProbe, blockedProbe]);
1127
1219
  assert.equal(probe.envelopes.length, 1);
1128
1220
  assert.equal(probe.readAllCalls(), 0);
1129
1221
  });
@@ -1,4 +1,6 @@
1
1
  import { composeMemoryEnvelope, TAG_LIMITS } from "./write-envelope.js";
2
+ import type { SealedMemoryEnvelope } from "./write-envelope.js";
3
+ import { persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
2
4
  import { resolveNamespaceCapabilities } from "./capabilities.js";
3
5
  import { createHash, randomUUID } from "node:crypto";
4
6
  import type { Orchestrator } from "./orchestrator.js";
@@ -419,19 +421,18 @@ async function findDuplicateExplicitCapture(
419
421
  // authoritative miss can therefore skip the corpus scan only after the
420
422
  // targeted blocked-row index reports a miss and remains authoritative.
421
423
  let authoritativeFactHashMiss = false;
422
- if (candidate.category === "fact" && typeof storage.hasFactContentHash === "function") {
424
+ if (candidate.category === "fact") {
423
425
  try {
424
- const hasHash = await storage.hasFactContentHash(candidate.content);
425
- if (!hasHash) {
426
- const authoritative =
427
- typeof storage.isFactContentHashAuthoritative === "function"
428
- ? await storage.isFactContentHashAuthoritative()
429
- : false;
430
- authoritativeFactHashMiss = authoritative;
426
+ // One lookup returns the MISS together with the authority behind it, so
427
+ // a stale MISS is never paired with authority from another moment
428
+ // (#3312, #3354 — each separate call may rebuild the index).
429
+ const lookup = await storage.lookupContentHash(sanitizeMemoryContent(candidate.content).text);
430
+ if (!lookup.present && lookup.authoritative) {
431
+ authoritativeFactHashMiss = true;
431
432
  }
432
433
  } catch (err) {
433
434
  // Fail open: hash indexes are only an optimization, so fall back to the
434
- // full corpus scan when either index cannot answer.
435
+ // full corpus scan when the lookup cannot answer.
435
436
  void err;
436
437
  }
437
438
  }
@@ -503,18 +504,55 @@ async function findDuplicateExplicitCapture(
503
504
  }
504
505
  : null;
505
506
  }
507
+ type PersistedExplicitCapture = { id: string; duplicateOf?: string; tombstoneBlocked?: boolean };
508
+ type RacedDuplicate = { match: { id: string; tombstoneBlocked: boolean } | null };
509
+
510
+ function duplicatePersistResult(duplicate: { id: string; tombstoneBlocked: boolean }): PersistedExplicitCapture {
511
+ return duplicate.tombstoneBlocked
512
+ ? { id: duplicate.id, duplicateOf: duplicate.id, tombstoneBlocked: true }
513
+ : { id: duplicate.id, duplicateOf: duplicate.id };
514
+ }
515
+
516
+ async function persistSealedExplicitCapture(
517
+ storage: StorageManager,
518
+ captureEnvelope: SealedMemoryEnvelope,
519
+ candidate: ValidExplicitCapture,
520
+ source: ExplicitCaptureSource
521
+ ): Promise<PersistedExplicitCapture> {
522
+ const { id, duplicateOf, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
523
+ if (duplicateOf) return { id, duplicateOf, tombstoneBlocked };
524
+ // #1522: catalog touch handled at the storage chokepoint — the StorageManager's
525
+ // post-write hook records the namespace touch automatically.
526
+
527
+ const created = new Date().toISOString();
528
+ const event: MemoryLifecycleEvent = {
529
+ eventId: `mle-${randomUUID()}`,
530
+ memoryId: id,
531
+ eventType: tombstoneBlocked ? "explicit_capture_queued" : "explicit_capture_accepted",
532
+ timestamp: created,
533
+ actor: explicitCaptureActor(source),
534
+ reasonCode: candidate.sourceReason,
535
+ ruleVersion: "explicit-capture.v1",
536
+ };
537
+ await storage.appendMemoryLifecycleEvents([event]);
538
+
539
+ return { id, duplicateOf, tombstoneBlocked };
540
+ }
541
+
506
542
  async function persistExplicitCaptureUnlocked(
507
543
  orchestrator: Orchestrator,
508
544
  candidate: ValidExplicitCapture,
509
545
  source: ExplicitCaptureSource,
510
546
  resolvedNamespace: string | undefined,
511
547
  storage: StorageManager
512
- ): Promise<{ id: string; duplicateOf?: string; tombstoneBlocked?: boolean }> {
513
- const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
514
- if (duplicate) {
515
- return duplicate.tombstoneBlocked
516
- ? { id: duplicate.id, duplicateOf: duplicate.id, tombstoneBlocked: true }
517
- : { id: duplicate.id, duplicateOf: duplicate.id };
548
+ ): Promise<PersistedExplicitCapture> {
549
+ const isFact = candidate.category === "fact";
550
+ // Facts take a cheap early exit here and re-check inside the dedup window.
551
+ // Every other category checks only under the capture lock, so its gate and
552
+ // its write stay one atomic step per dedup key (#3354).
553
+ if (isFact) {
554
+ const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
555
+ if (duplicate) return duplicatePersistResult(duplicate);
518
556
  }
519
557
 
520
558
  // #1645 (review thread yG-): surface the tombstone block so callers
@@ -534,39 +572,54 @@ async function persistExplicitCaptureUnlocked(
534
572
  },
535
573
  { source: source === "inline" ? "explicit-inline" : "explicit" }
536
574
  );
537
- const { id, duplicateOf, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
538
- if (duplicateOf) return { id, duplicateOf, tombstoneBlocked };
539
- // #1522: catalog touch handled at the storage chokepoint — the StorageManager's
540
- // post-write hook records the namespace touch automatically.
541
575
 
542
- const created = new Date().toISOString();
543
- const event: MemoryLifecycleEvent = {
544
- eventId: `mle-${randomUUID()}`,
545
- memoryId: id,
546
- eventType: tombstoneBlocked ? "explicit_capture_queued" : "explicit_capture_accepted",
547
- timestamp: created,
548
- actor: explicitCaptureActor(source),
549
- reasonCode: candidate.sourceReason,
550
- ruleVersion: "explicit-capture.v1",
551
- };
552
- await storage.appendMemoryLifecycleEvents([event]);
553
-
554
- return { id, duplicateOf, tombstoneBlocked };
576
+ // Test fakes may omit the lock; the real StorageManager always has it.
577
+ const withCaptureLock = <T>(run: () => Promise<T>): Promise<T> =>
578
+ storage.withTombstoneBlockedCaptureWriteLock?.(
579
+ run,
580
+ buildExplicitCaptureDedupKey(candidate.content, candidate.category, candidate.sourceConnector)
581
+ ) ?? run();
582
+ const writeSealed = (): Promise<PersistedExplicitCapture> =>
583
+ persistSealedExplicitCapture(storage, captureEnvelope, candidate, source);
584
+
585
+ if (!isFact) {
586
+ return await withCaptureLock(async () => {
587
+ const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
588
+ return duplicate ? duplicatePersistResult(duplicate) : await writeSealed();
589
+ });
590
+ }
591
+ // #3354: fact captures commit inside the per-dir fact-dedup window so a
592
+ // concurrent extraction write of the same fact is caught by the recheck
593
+ // instead of racing the persist-time gate. The tombstone capture write
594
+ // lock stays INSIDE the window: every other window writer (extraction,
595
+ // promotion) acquires the tombstone lock only inside writeMemory, so
596
+ // taking it here first would order tombstone→window against their
597
+ // window→tombstone and stall the capture on the 60s write-lock backoff.
598
+ const raced: RacedDuplicate = { match: null };
599
+ const outcome = await persistUnderFactDedupWindow(
600
+ storage.dir,
601
+ async () => {
602
+ raced.match = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
603
+ return raced.match !== null;
604
+ },
605
+ () => withCaptureLock(writeSealed)
606
+ );
607
+ if (!outcome.suppressed) return outcome.result;
608
+ if (!raced.match) throw new Error("explicit capture suppressed by the dedup window without a matched duplicate");
609
+ log.info(`dedup: raced explicit capture suppressed in fact-dedup window for ${storage.dir}`);
610
+ return duplicatePersistResult(raced.match);
555
611
  }
556
612
 
557
613
  export async function persistExplicitCapture(
558
614
  orchestrator: Orchestrator,
559
615
  candidate: ValidExplicitCapture,
560
616
  source: ExplicitCaptureSource
561
- ): Promise<{ id: string; duplicateOf?: string; tombstoneBlocked?: boolean }> {
617
+ ): Promise<PersistedExplicitCapture> {
562
618
  const resolvedNamespace = candidate.namespacePreResolved
563
619
  ? asTrimmed(candidate.namespace)
564
620
  : resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);
565
621
  const storage = await orchestrator.getStorage(resolvedNamespace);
566
- const persist = () => persistExplicitCaptureUnlocked(orchestrator, candidate, source, resolvedNamespace, storage);
567
- if (typeof storage.withTombstoneBlockedCaptureWriteLock !== "function") return await persist();
568
- const lockIdentity = buildExplicitCaptureDedupKey(candidate.content, candidate.category, candidate.sourceConnector);
569
- return await storage.withTombstoneBlockedCaptureWriteLock(persist, lockIdentity);
622
+ return await persistExplicitCaptureUnlocked(orchestrator, candidate, source, resolvedNamespace, storage);
570
623
  }
571
624
 
572
625
  function buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason: string): string {
@@ -459,6 +459,26 @@ test("a negator in a claim clause that shares only the subject does not keep the
459
459
  assert.equal(grounded.facts.length, 0);
460
460
  });
461
461
 
462
+ test("a negated clause about another subject does not keep the quote's denial (#3356)", () => {
463
+ const source =
464
+ "Alice did not approve the budget. Bob did not approve the budget either. Carol asked about the plan.";
465
+ const quote = "Alice did not approve the budget.";
466
+ const kept = [
467
+ "Alice did not approve the budget.",
468
+ "The budget was not approved by Alice.",
469
+ "The budget was not approved.",
470
+ ];
471
+ const grounded = filterExtractionResultBySource(
472
+ resultOf([
473
+ { content: "Alice approved the budget, but Bob did not approve the budget.", quote },
474
+ ...kept.map((content) => ({ content, quote })),
475
+ ]),
476
+ source,
477
+ source,
478
+ );
479
+ assert.deepEqual(keptContents(grounded), kept);
480
+ });
481
+
462
482
  test("a claim that inverts a denial verb in the quote is rejected", () => {
463
483
  const source = "Notes: Alice denied approving the budget. Bob reviewed it.";
464
484
  const grounded = filterExtractionResultBySource(
@@ -192,17 +192,19 @@ function negatedScope(clause: string): string {
192
192
  return match ? clause.slice(match.index + match[0].length) : clause;
193
193
  }
194
194
 
195
+ /** The words before a clause's first negator, empty when it has none. */
196
+ function textBeforeNegator(clause: string): string {
197
+ const match = NEGATOR_PATTERN.exec(clause.replace(/[\u2018\u2019]/gu, "'"));
198
+ return match ? clause.slice(0, match.index) : "";
199
+ }
200
+
195
201
  /**
196
- * True when the claim asserts what a quote clause denies or does not assert:
197
- * "did not approve the budget" or "denied approving it" restated as "approved
198
- * the budget", "suspects Bob stole funds" or "Is Alice the CEO?" restated as a
199
- * plain statement. The claim must restate the quote clause, and none of its
200
- * clauses that share a word with what the negator governs may keep the
201
- * negator, exclusion word, or reporting marker ("do not X" reworded as "X is
202
- * excluded", or "Alice reviewed it, but she did not approve it"). A negator in
203
- * a clause about something else does not count, so a claim that keeps one
204
- * quote negation can still misstate another quote clause, and a claim clause
205
- * that shares only the subject does not satisfy the check.
202
+ * True when the claim asserts what a quote clause denies or does not assert.
203
+ * A quote denial is kept only by a claim clause that shares what the negator
204
+ * governs and does not name a different subject, so a negator about Bob does
205
+ * not keep a denial about Alice. A clause that names no subject of its own
206
+ * still counts, which keeps a pronoun clause and a subject-less passive. A
207
+ * quote clause with no subject of its own keeps the clause-level check.
206
208
  */
207
209
  function misstatesQuote(content: string, quote: string): boolean {
208
210
  const claimClauses = splitClaimClauses(content);
@@ -217,8 +219,16 @@ function misstatesQuote(content: string, quote: string): boolean {
217
219
  claimClauses.filter((claimClause) => clauseOverlap(tokens, claimClause).shared > 0);
218
220
  if (needsNegation) {
219
221
  const scopeTokens = [...tokenize(negatedScope(quoteClause))];
222
+ const subjectTokens = [...tokenize(textBeforeNegator(quoteClause))];
223
+ const scopeHas = new Set(scopeTokens);
220
224
  const related = clausesSharing(scopeTokens.length > 0 ? scopeTokens : clauseTokens);
221
- if (!related.some((text) => hasNegation(text) || EXCLUSION_PATTERN.test(text))) return true;
225
+ const keepsDenial = related.some((text) => {
226
+ if (!hasNegation(text) && !EXCLUSION_PATTERN.test(text)) return false;
227
+ if (subjectTokens.length === 0 || clauseOverlap(subjectTokens, text).shared > 0) return true;
228
+ const ownSubject = [...tokenize(textBeforeNegator(text))].filter((token) => !scopeHas.has(token));
229
+ return ownSubject.length === 0;
230
+ });
231
+ if (!keepsDenial) return true;
222
232
  }
223
233
  return needsReporting
224
234
  && !clausesSharing(clauseTokens).some((text) => hasNonAssertiveReporting(text) || text.trim().endsWith("?"));
@@ -6,6 +6,8 @@ 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
+ import { persistExplicitCapture } from "./explicit-capture.js";
10
+ import { Orchestrator } from "./orchestrator.js";
9
11
 
10
12
  const SKY_OPTS = {
11
13
  category: "fact",
@@ -185,6 +187,41 @@ test("two managers on one directory commit the same fact through the window exac
185
187
  }
186
188
  });
187
189
 
190
+ test("explicit capture and an extraction write race the same new fact to exactly one active copy (#3354)", async () => {
191
+ const { storage, cleanup } = await makeStorage();
192
+ try {
193
+ const content = "An extraction run and an explicit capture hold this new fact.";
194
+ // SAFETY: persistExplicitCapture reads only getStorage here (namespacePreResolved skips the config-based namespace resolver); the prototype stub exists for the type only.
195
+ const orchestrator = Object.create(Orchestrator.prototype) as Orchestrator;
196
+ orchestrator.getStorage = async () => storage;
197
+ const candidate = { content, category: "fact" as const, confidence: 0.9, tags: [], namespacePreResolved: true };
198
+ const extractionCommit = persistUnderFactDedupWindow(
199
+ storage.dir,
200
+ () => confirmActiveFactDuplicate(storage, { ...SKY_OPTS, contentHashDedupKey: content }),
201
+ () => storage.writeMemory("fact", content),
202
+ );
203
+ const [capture, extraction] = await Promise.all([
204
+ persistExplicitCapture(orchestrator, candidate, "memory_store"),
205
+ extractionCommit,
206
+ ]);
207
+ const stored = await storage.readAllMemories();
208
+ const active = stored.filter(
209
+ (memory) => memory.frontmatter.category === "fact" && (memory.frontmatter.status ?? "active") === "active",
210
+ );
211
+ assert.equal(active.length, 1, `exactly one active copy of the raced fact, got ${active.length}`);
212
+ const winner = active[0];
213
+ assert.ok(winner, "the single active copy must exist");
214
+ assert.ok(winner.content.includes(content));
215
+ if (capture.duplicateOf === undefined) {
216
+ assert.equal(extraction.suppressed, true, "the extraction write is the loser and must be suppressed");
217
+ } else {
218
+ assert.equal(winner.frontmatter.id, capture.duplicateOf, "the capture reports the winning copy");
219
+ }
220
+ } finally {
221
+ await cleanup();
222
+ }
223
+ });
224
+
188
225
  test("confirmActiveFactDuplicate keeps the authoritative MISS fast path", async () => {
189
226
  const { storage, cleanup } = await makeStorage();
190
227
  try {
@@ -31,6 +31,7 @@ import {
31
31
  } from "./connectors/live/index.js";
32
32
  import { abortError } from "./abort-error.js";
33
33
  import type { LiveConnectorsConfig } from "./types.js";
34
+ import type { ResolveSecretRefFn } from "./resolve-auth-token.js";
34
35
 
35
36
  export type LiveConnectorSkipReason =
36
37
  | "disabled"
@@ -138,6 +139,7 @@ export async function runLiveConnectorsOnce(options: {
138
139
  now?: LiveConnectorsNow;
139
140
  abortSignal?: AbortSignal;
140
141
  definitions?: LiveConnectorDefinition[];
142
+ resolveSecretRef?: ResolveSecretRefFn | null;
141
143
  }): Promise<LiveConnectorsRunSummary> {
142
144
  const ranAt = resolveNow(options.now);
143
145
  const force = options.force === true;
@@ -228,6 +230,7 @@ export async function runLiveConnectorsOnce(options: {
228
230
  cursor,
229
231
  config: validatedConfig,
230
232
  abortSignal: pollAbortSignal,
233
+ resolveSecretRef: options.resolveSecretRef,
231
234
  }),
232
235
  ingestFn: async (docs) => {
233
236
  throwIfAborted(pollAbortSignal);
@@ -0,0 +1,126 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
6
+
7
+ import { parseConfig } from "./config.js";
8
+ import {
9
+ builtInLiveConnectorDefinitions,
10
+ runLiveConnectorsOnce,
11
+ } from "./live-connectors-runner.js";
12
+ import {
13
+ GOOGLE_DRIVE_CONNECTOR_ID,
14
+ NOTION_CONNECTOR_ID,
15
+ createGoogleDriveConnector,
16
+ type GoogleDriveClient,
17
+ } from "./connectors/live/index.js";
18
+ import type { LiveConnector } from "./connectors/live/framework.js";
19
+
20
+ const RESOLVED_SECRET = "resolved-drive-secret";
21
+ const SECRET_REF = { source: "keychain", id: "projects/drive-secret" };
22
+
23
+ function driveConfig() {
24
+ return {
25
+ openaiApiKey: "sk-test",
26
+ connectors: {
27
+ googleDrive: {
28
+ enabled: true,
29
+ clientId: "synthetic-client",
30
+ clientSecret: SECRET_REF,
31
+ refreshToken: "synthetic-token",
32
+ },
33
+ notion: { enabled: true, token: "secret_synthetic_notion_token" },
34
+ },
35
+ };
36
+ }
37
+
38
+ function notionStub(): LiveConnector {
39
+ return {
40
+ id: NOTION_CONNECTOR_ID,
41
+ displayName: "Notion",
42
+ validateConfig: () => ({}),
43
+ async syncIncremental() {
44
+ return { newDocs: [], nextCursor: { kind: "notion", value: "cursor-1", updatedAt: "2026-11-11T00:00:00.000Z" } };
45
+ },
46
+ };
47
+ }
48
+
49
+ test("a Drive SecretRef resolves through parseConfig and the runner, and stays out of the state file", async () => {
50
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-drive-secretref-"));
51
+ try {
52
+ const parsed = parseConfig(driveConfig());
53
+ const definitions = builtInLiveConnectorDefinitions(parsed.connectors);
54
+ let seenSecret = "";
55
+ const client: GoogleDriveClient = {
56
+ async getStartPageToken() {
57
+ return { startPageToken: "seed-token" };
58
+ },
59
+ async listChanges() {
60
+ return { changes: [], newStartPageToken: "next-token" };
61
+ },
62
+ async exportFile() {
63
+ return "";
64
+ },
65
+ async getFileMedia() {
66
+ return "";
67
+ },
68
+ };
69
+ for (const definition of definitions) {
70
+ if (definition.id === GOOGLE_DRIVE_CONNECTOR_ID) {
71
+ definition.createConnector = () =>
72
+ createGoogleDriveConnector({
73
+ clientFactory: async (config) => {
74
+ seenSecret = config.clientSecret;
75
+ return client;
76
+ },
77
+ });
78
+ }
79
+ if (definition.id === NOTION_CONNECTOR_ID) {
80
+ definition.createConnector = notionStub;
81
+ }
82
+ }
83
+ const summary = await runLiveConnectorsOnce({
84
+ memoryDir,
85
+ connectors: parsed.connectors,
86
+ definitions,
87
+ force: true,
88
+ resolveSecretRef: async () => RESOLVED_SECRET,
89
+ ingestDocuments: async () => {},
90
+ });
91
+ assert.equal(seenSecret, RESOLVED_SECRET);
92
+ const drive = summary.results.find((item) => item.id === GOOGLE_DRIVE_CONNECTOR_ID);
93
+ assert.equal(drive?.ran, true);
94
+ const statePath = path.join(memoryDir, "state", "connectors", `${GOOGLE_DRIVE_CONNECTOR_ID}.json`);
95
+ const stateText = await readFile(statePath, "utf-8");
96
+ assert.equal(stateText.includes(RESOLVED_SECRET), false);
97
+ assert.equal(stateText.includes(SECRET_REF.id), false);
98
+ } finally {
99
+ await rm(memoryDir, { recursive: true, force: true });
100
+ }
101
+ });
102
+
103
+ test("a Drive SecretRef with no resolver fails that connector and the others still run", async () => {
104
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-drive-secretref-"));
105
+ try {
106
+ const parsed = parseConfig(driveConfig());
107
+ const definitions = builtInLiveConnectorDefinitions(parsed.connectors);
108
+ for (const definition of definitions) {
109
+ if (definition.id === NOTION_CONNECTOR_ID) definition.createConnector = notionStub;
110
+ }
111
+ const summary = await runLiveConnectorsOnce({
112
+ memoryDir,
113
+ connectors: parsed.connectors,
114
+ definitions,
115
+ force: true,
116
+ ingestDocuments: async () => {},
117
+ });
118
+ const drive = summary.results.find((item) => item.id === GOOGLE_DRIVE_CONNECTOR_ID);
119
+ const notion = summary.results.find((item) => item.id === NOTION_CONNECTOR_ID);
120
+ assert.match(drive?.error ?? "", /clientSecret is a secret reference but no resolver is configured/);
121
+ assert.equal((drive?.error ?? "").includes(SECRET_REF.id), false);
122
+ assert.equal(notion?.ran, true);
123
+ } finally {
124
+ await rm(memoryDir, { recursive: true, force: true });
125
+ }
126
+ });
@@ -40,6 +40,18 @@ export function isAbortError(err: unknown): boolean {
40
40
  );
41
41
  }
42
42
 
43
+ /** Caller signal combined with a client-wide shutdown signal, so a daemon
44
+ * stop aborts in-flight requests and retry loops at once — even for
45
+ * callers that pass no signal of their own. */
46
+ export function combineClientAbortSignals(
47
+ caller: AbortSignal | undefined,
48
+ shutdown: AbortSignal | undefined,
49
+ ): AbortSignal | undefined {
50
+ if (shutdown === undefined) return caller;
51
+ if (caller === undefined) return shutdown;
52
+ return AbortSignal.any([caller, shutdown]);
53
+ }
54
+
43
55
  export function waitForRetryBackoff(backoffMs: number, signal?: AbortSignal): Promise<boolean> {
44
56
  return new Promise((resolve) => {
45
57
  let settled = false;