@remnic/core 10.0.48 → 10.0.50

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 (449) hide show
  1. package/dist/access-admin-ops-surface.js +73 -70
  2. package/dist/access-audit.js +2 -2
  3. package/dist/access-authorization-probe.js +74 -71
  4. package/dist/access-boundary.js +73 -70
  5. package/dist/access-cli.js +170 -167
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +73 -70
  8. package/dist/access-http-briefing.d.ts +2 -0
  9. package/dist/access-http-briefing.d.ts.map +1 -1
  10. package/dist/access-http-briefing.js +74 -71
  11. package/dist/access-http-query.js +75 -72
  12. package/dist/access-http.d.ts.map +1 -1
  13. package/dist/access-http.js +96 -93
  14. package/dist/access-identity-continuity-surface.js +73 -70
  15. package/dist/access-lcm-surface.js +73 -70
  16. package/dist/access-mcp.js +85 -82
  17. package/dist/access-namespace-preflight.js +73 -70
  18. package/dist/access-observe-write-category.d.ts +1 -1
  19. package/dist/access-observe-write-category.d.ts.map +1 -1
  20. package/dist/access-observe-write-category.js +15 -14
  21. package/dist/access-observe-write-surface.d.ts.map +1 -1
  22. package/dist/access-observe-write-surface.js +73 -70
  23. package/dist/access-offline-manifest.js +9 -8
  24. package/dist/access-operations-batch.js +76 -73
  25. package/dist/access-operations.js +83 -80
  26. package/dist/access-recall-concurrency.js +73 -70
  27. package/dist/access-recall-response.js +73 -70
  28. package/dist/access-recall-surface.js +73 -70
  29. package/dist/access-schema.js +11 -10
  30. package/dist/access-service.d.ts +5 -0
  31. package/dist/access-service.d.ts.map +1 -1
  32. package/dist/access-service.js +73 -70
  33. package/dist/access-surface-catalog.js +75 -72
  34. package/dist/access-surface-catalog.js.map +1 -1
  35. package/dist/active-recall.js +32 -30
  36. package/dist/active-recall.js.map +1 -1
  37. package/dist/adapters/index.js +4 -4
  38. package/dist/adapters/registry.js +2 -2
  39. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  40. package/dist/briefing-deadline.d.ts +7 -0
  41. package/dist/briefing-deadline.d.ts.map +1 -0
  42. package/dist/briefing-deadline.js +13 -0
  43. package/dist/briefing-window.d.ts +12 -4
  44. package/dist/briefing-window.d.ts.map +1 -1
  45. package/dist/briefing-window.js +3 -1
  46. package/dist/briefing.d.ts +2 -0
  47. package/dist/briefing.d.ts.map +1 -1
  48. package/dist/briefing.js +22 -20
  49. package/dist/buffer.js +6 -6
  50. package/dist/calibration.js +2 -2
  51. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  52. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  53. package/dist/capture-write-lock-scope.d.ts +7 -0
  54. package/dist/capture-write-lock-scope.d.ts.map +1 -0
  55. package/dist/capture-write-lock-scope.js +10 -0
  56. package/dist/causal-behavior.js +2 -2
  57. package/dist/causal-chain.js +2 -2
  58. package/dist/causal-consolidation.js +23 -22
  59. package/dist/causal-consolidation.js.map +1 -1
  60. package/dist/causal-retrieval.js +2 -2
  61. package/dist/causal-trajectory-graph.js +3 -3
  62. package/dist/causal-trajectory.js +1 -1
  63. package/dist/{chunk-FAKRTS3Y.js → chunk-2AKIVSHP.js} +2 -2
  64. package/dist/{chunk-EIYJK7OD.js → chunk-2M36PR7R.js} +2 -2
  65. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  66. package/dist/{chunk-6P6MZDBL.js → chunk-3NL4ALUP.js} +2 -2
  67. package/dist/{chunk-6CC2YCKG.js → chunk-43TUWQNR.js} +3 -3
  68. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  69. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  70. package/dist/{chunk-5KGYR5VN.js → chunk-5DVOBBTV.js} +2 -2
  71. package/dist/{chunk-RT2GN6KQ.js → chunk-5FUDSK6Z.js} +2 -2
  72. package/dist/{chunk-B4XSXFKI.js → chunk-5G53JSRO.js} +2 -2
  73. package/dist/{chunk-BGK3SFCC.js → chunk-5X2H3X4J.js} +2 -2
  74. package/dist/{chunk-OXSTKCYK.js → chunk-6AEKSOWV.js} +33 -5
  75. package/dist/chunk-6AEKSOWV.js.map +1 -0
  76. package/dist/{chunk-4J7BNL7B.js → chunk-6ZQX3R66.js} +2 -2
  77. package/dist/{chunk-JNLYHJG7.js → chunk-74AY3S7U.js} +4 -4
  78. package/dist/{chunk-RUAIO5UA.js → chunk-7J6A5MV2.js} +2 -2
  79. package/dist/{chunk-A45QXZMU.js → chunk-7SAE6T36.js} +2 -2
  80. package/dist/chunk-7VLBZKNB.js +26 -0
  81. package/dist/chunk-7VLBZKNB.js.map +1 -0
  82. package/dist/{chunk-QE2XQM3S.js → chunk-7YVER7ZO.js} +2 -2
  83. package/dist/{chunk-IJH6METJ.js → chunk-A3UFV4K3.js} +4 -4
  84. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  85. package/dist/chunk-A5L5KGDI.js.map +1 -0
  86. package/dist/{chunk-LXN737FX.js → chunk-AAANTXCQ.js} +2 -2
  87. package/dist/{chunk-I3IPXGFQ.js → chunk-AJJPXUE6.js} +4 -4
  88. package/dist/{chunk-2DPJFJFH.js → chunk-BNJAVRNJ.js} +11 -11
  89. package/dist/{chunk-YL564QS2.js → chunk-CCL2OXFO.js} +3 -3
  90. package/dist/{chunk-CHSRA76M.js → chunk-CRN3EPE2.js} +4 -3
  91. package/dist/chunk-CRN3EPE2.js.map +1 -0
  92. package/dist/{chunk-ZX5N5D6M.js → chunk-DAOMWGO2.js} +4 -4
  93. package/dist/{chunk-YUE7J2HZ.js → chunk-DMKTOCC7.js} +2 -2
  94. package/dist/chunk-DMKTOCC7.js.map +1 -0
  95. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  96. package/dist/chunk-EKUI7I2X.js.map +1 -0
  97. package/dist/{chunk-PKPOO6X5.js → chunk-EKYWZYZJ.js} +25 -17
  98. package/dist/chunk-EKYWZYZJ.js.map +1 -0
  99. package/dist/{chunk-23ZMQONL.js → chunk-ERMT5WQM.js} +2 -2
  100. package/dist/{chunk-VTCNVGM6.js → chunk-EWBXMGXR.js} +2 -2
  101. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  102. package/dist/{chunk-XTN54NML.js → chunk-G3T6YMBO.js} +4 -4
  103. package/dist/{chunk-2JNBPOZ2.js → chunk-GDYLZ6FR.js} +3 -3
  104. package/dist/{chunk-DZPTR2XQ.js → chunk-GFAEKSFR.js} +2 -2
  105. package/dist/{chunk-L46MG2GE.js → chunk-GQIYL7OD.js} +400 -301
  106. package/dist/chunk-GQIYL7OD.js.map +1 -0
  107. package/dist/{chunk-M7LA4QBY.js → chunk-GXVXBYRA.js} +3 -3
  108. package/dist/{chunk-UXAF4RQM.js → chunk-HKOMHP6D.js} +27 -15
  109. package/dist/chunk-HKOMHP6D.js.map +1 -0
  110. package/dist/{chunk-JGMJZBFV.js → chunk-HPNXS6W6.js} +2 -2
  111. package/dist/{chunk-BFPF5N3H.js → chunk-HXFIILGF.js} +4 -4
  112. package/dist/{chunk-YBKQAVPZ.js → chunk-IMKOJVD4.js} +101 -81
  113. package/dist/chunk-IMKOJVD4.js.map +1 -0
  114. package/dist/{chunk-CMNL3QSL.js → chunk-IN6U2BS5.js} +2 -2
  115. package/dist/{chunk-BWRX322G.js → chunk-IUYXCTA7.js} +2 -2
  116. package/dist/{chunk-LU57EEJW.js → chunk-JBWD3J5U.js} +3 -3
  117. package/dist/chunk-JNA7LFMQ.js +41 -0
  118. package/dist/chunk-JNA7LFMQ.js.map +1 -0
  119. package/dist/{chunk-2V664H6O.js → chunk-JS44PBCM.js} +5 -5
  120. package/dist/{chunk-XKTHJHSQ.js → chunk-JZCBMKR5.js} +3 -3
  121. package/dist/{chunk-MXPVNDGO.js → chunk-K5QQXVG6.js} +12 -12
  122. package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
  123. package/dist/{chunk-WUBGMV3G.js → chunk-KRAMCBIW.js} +9 -9
  124. package/dist/{chunk-NDMPRT3Z.js → chunk-KW5YZELC.js} +2 -2
  125. package/dist/{chunk-2RQR4TXV.js → chunk-LIBVANOF.js} +2 -2
  126. package/dist/{chunk-M7L5A2AT.js → chunk-LMMBV7QC.js} +4 -4
  127. package/dist/{chunk-PJBYVJKO.js → chunk-LXXIV7Y2.js} +2 -2
  128. package/dist/{chunk-MZCTS3ZG.js → chunk-M2DOJBH7.js} +2 -2
  129. package/dist/{chunk-WOPZ2P5Z.js → chunk-M4HRYLOA.js} +2 -2
  130. package/dist/{chunk-JCYEDMOY.js → chunk-M6TP6UYD.js} +3 -3
  131. package/dist/{chunk-KAUCVTAW.js → chunk-MLNXATFG.js} +2 -2
  132. package/dist/{chunk-2NX7JTST.js → chunk-MPWOAIIH.js} +2 -2
  133. package/dist/{chunk-RQLTCHUL.js → chunk-MWURUSRE.js} +13 -13
  134. package/dist/{chunk-3EGL7XZO.js → chunk-N74WJ7B2.js} +2 -2
  135. package/dist/{chunk-DRXTMDAG.js → chunk-N7FNCE7F.js} +2 -2
  136. package/dist/{chunk-6CWAY4R7.js → chunk-NPLM7AFU.js} +2 -2
  137. package/dist/{chunk-VECRSYQG.js → chunk-NRGQ5HJO.js} +1 -1
  138. package/dist/{chunk-PPDNG7WC.js → chunk-NSP5PIQH.js} +2 -2
  139. package/dist/{chunk-6HVLHIAB.js → chunk-P7OVFGMU.js} +2 -2
  140. package/dist/{chunk-EX27VKU6.js → chunk-PDTNQLPC.js} +2 -2
  141. package/dist/{chunk-ME5LVISI.js → chunk-PJCVI44H.js} +2 -2
  142. package/dist/{chunk-LJCHDFLP.js → chunk-Q3PNQYAC.js} +2 -2
  143. package/dist/{chunk-YFJHFEET.js → chunk-QS765SBL.js} +2 -2
  144. package/dist/{chunk-FJ264QBP.js → chunk-QTDZ2NYC.js} +11 -11
  145. package/dist/{chunk-Q6UZREJK.js → chunk-RKRX4C4H.js} +2 -2
  146. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  147. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  148. package/dist/{chunk-GLZWOIU3.js → chunk-SJTSWG2Z.js} +3 -3
  149. package/dist/{chunk-Y57GJDG5.js → chunk-SWRRZNHM.js} +14 -17
  150. package/dist/chunk-SWRRZNHM.js.map +1 -0
  151. package/dist/{chunk-CCTLRRV6.js → chunk-TEH5MDX5.js} +55 -34
  152. package/dist/chunk-TEH5MDX5.js.map +1 -0
  153. package/dist/{chunk-XM747YJF.js → chunk-TFWJXWL6.js} +2 -2
  154. package/dist/{chunk-QFQ4UG3I.js → chunk-TXKQBQYW.js} +4 -4
  155. package/dist/{chunk-BMOJFGVX.js → chunk-UALXWWGN.js} +2 -2
  156. package/dist/{chunk-B44NFHWI.js → chunk-UNOAVHV4.js} +2 -2
  157. package/dist/{chunk-TF5NZGPS.js → chunk-UQBOPCDK.js} +2 -2
  158. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  159. package/dist/chunk-UWMZT2TY.js.map +1 -0
  160. package/dist/{chunk-5R2IF2KI.js → chunk-UXGXJUZ5.js} +7 -7
  161. package/dist/{chunk-FWV2SXSL.js → chunk-UZVQFGAY.js} +101 -98
  162. package/dist/chunk-UZVQFGAY.js.map +1 -0
  163. package/dist/{chunk-J6B6FK2X.js → chunk-VCOMACR6.js} +53 -53
  164. package/dist/{chunk-WCGJK4NQ.js → chunk-VKQI47DJ.js} +7 -7
  165. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  166. package/dist/{chunk-BCDECPVO.js → chunk-WV4UYDP6.js} +2 -2
  167. package/dist/{chunk-VWJ56T63.js → chunk-WXJC2L66.js} +2 -2
  168. package/dist/chunk-X2KO4FK5.js +40 -0
  169. package/dist/chunk-X2KO4FK5.js.map +1 -0
  170. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  171. package/dist/{chunk-FPANDKMQ.js → chunk-XC3H7KON.js} +2 -2
  172. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  173. package/dist/{chunk-3DKKZVXS.js → chunk-XTXWR6KQ.js} +26 -11
  174. package/dist/chunk-XTXWR6KQ.js.map +1 -0
  175. package/dist/{chunk-3SL52JKD.js → chunk-XVZA6VXC.js} +4 -4
  176. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  177. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  178. package/dist/{chunk-XTVH2NAW.js → chunk-YECEEJUU.js} +3 -3
  179. package/dist/{chunk-QR6W3FSW.js → chunk-Z5277JWF.js} +3 -3
  180. package/dist/{chunk-CTHYR4QK.js → chunk-ZFNXJEUG.js} +2 -2
  181. package/dist/{chunk-6Q56WDHG.js → chunk-ZLCBLDJO.js} +2 -2
  182. package/dist/{chunk-VY6BD5KB.js → chunk-ZMO2S5RT.js} +12 -10
  183. package/dist/chunk-ZMO2S5RT.js.map +1 -0
  184. package/dist/{chunk-KRACAE73.js → chunk-ZNRTHYYW.js} +6 -6
  185. package/dist/{chunk-7AIPWNM7.js → chunk-ZSMA67FV.js} +2 -2
  186. package/dist/cli.js +123 -120
  187. package/dist/coding/export-okf-codegraph.js +19 -18
  188. package/dist/coding/export-okf-codegraph.js.map +1 -1
  189. package/dist/coding/pre-action-gate.js +2 -2
  190. package/dist/compounding/engine.js +21 -20
  191. package/dist/config.js +32 -30
  192. package/dist/connectors/codex-materialize-runner.js +20 -19
  193. package/dist/connectors/index.js +20 -19
  194. package/dist/consolidation-provenance-check.js +4 -4
  195. package/dist/consolidation-undo.js +2 -2
  196. package/dist/convergence-refresh.js +22 -21
  197. package/dist/corpus-watermark.js +22 -21
  198. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  199. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  200. package/dist/dashboard-runtime.js +2 -2
  201. package/dist/deep-recall-http-glue.js +12 -11
  202. package/dist/deep-recall.js +2 -2
  203. package/dist/embedding-fallback.js +3 -2
  204. package/dist/embedding-generation-marker.d.ts +16 -0
  205. package/dist/embedding-generation-marker.d.ts.map +1 -0
  206. package/dist/embedding-generation-marker.js +19 -0
  207. package/dist/embedding-generation-marker.js.map +1 -0
  208. package/dist/embedding-index-storage.d.ts +2 -4
  209. package/dist/embedding-index-storage.d.ts.map +1 -1
  210. package/dist/embedding-index-storage.js +2 -1
  211. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-ZBNRGUNV.js} +3 -3
  212. package/dist/entity-canonical-id-migration-ZBNRGUNV.js.map +1 -0
  213. package/dist/entity-retrieval.js +22 -21
  214. package/dist/event-order-recall.js +2 -2
  215. package/dist/explicit-capture.js +3 -3
  216. package/dist/external-wiki-access.js +74 -71
  217. package/dist/extraction-span-materialize.js +2 -2
  218. package/dist/extraction.js +30 -29
  219. package/dist/{graph-edge-decay-3HVLMT6U.js → graph-edge-decay-IU75NV42.js} +6 -6
  220. package/dist/graph-edge-decay-IU75NV42.js.map +1 -0
  221. package/dist/graph-jsonl.js +4 -4
  222. package/dist/graph-snapshot.js +4 -4
  223. package/dist/graph.js +5 -5
  224. package/dist/harmonic-retrieval.js +20 -19
  225. package/dist/index.d.ts +2 -1
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +230 -218
  228. package/dist/lcm/engine.js +3 -3
  229. package/dist/lcm/index.js +5 -5
  230. package/dist/local-llm.js +2 -2
  231. package/dist/location/index.js +25 -24
  232. package/dist/location/tagging.d.ts +7 -5
  233. package/dist/location/tagging.d.ts.map +1 -1
  234. package/dist/maintenance/memory-governance.js +20 -19
  235. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -19
  236. package/dist/maintenance/rebuild-memory-projection-runner.js +22 -21
  237. package/dist/maintenance/rebuild-memory-projection.js +21 -20
  238. package/dist/memory-browse-operations.js +74 -71
  239. package/dist/memory-browse.js +5 -5
  240. package/dist/namespaces/migrate.js +21 -20
  241. package/dist/namespaces/search.js +6 -6
  242. package/dist/namespaces/storage.js +20 -19
  243. package/dist/offline-sync-changeset-generations.js +5 -4
  244. package/dist/offline-sync-embedding-generation.d.ts +12 -2
  245. package/dist/offline-sync-embedding-generation.d.ts.map +1 -1
  246. package/dist/offline-sync-embedding-generation.js +6 -3
  247. package/dist/offline-sync-file-io.d.ts +15 -0
  248. package/dist/offline-sync-file-io.d.ts.map +1 -1
  249. package/dist/offline-sync-file-io.js +9 -1
  250. package/dist/offline-sync-snapshot-stream.js +9 -8
  251. package/dist/offline-sync-upload-staging.js +2 -2
  252. package/dist/offline-sync.d.ts.map +1 -1
  253. package/dist/offline-sync.js +10 -9
  254. package/dist/operator-doctor-corpus.js +23 -22
  255. package/dist/operator-toolkit.js +45 -43
  256. package/dist/orchestration/maintenance.js +33 -32
  257. package/dist/orchestrator.d.ts +1 -0
  258. package/dist/orchestrator.d.ts.map +1 -1
  259. package/dist/orchestrator.js +170 -167
  260. package/dist/page-versioning.js +2 -2
  261. package/dist/qmd.js +2 -2
  262. package/dist/recall-navigation-operations.js +74 -71
  263. package/dist/recall-navigation.js +73 -70
  264. package/dist/recall-state-view-admission.js +5 -5
  265. package/dist/recall-why-service.js +5 -5
  266. package/dist/resume-bundles.js +33 -31
  267. package/dist/search/factory.js +5 -5
  268. package/dist/search/index.js +9 -9
  269. package/dist/semantic-consolidation.js +25 -24
  270. package/dist/semantic-rule-promotion.js +20 -19
  271. package/dist/semantic-rule-verifier.js +20 -19
  272. package/dist/source-agent-qualifier.js +24 -23
  273. package/dist/standup/build.js +20 -19
  274. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  275. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  276. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  277. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  278. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  279. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  280. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  281. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  282. package/dist/storage/entity-store.d.ts +1 -8
  283. package/dist/storage/entity-store.d.ts.map +1 -1
  284. package/dist/storage/memory-read-store.d.ts +3 -2
  285. package/dist/storage/memory-read-store.d.ts.map +1 -1
  286. package/dist/storage/windowed-memory-read.d.ts +6 -0
  287. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  288. package/dist/storage.d.ts +2 -0
  289. package/dist/storage.d.ts.map +1 -1
  290. package/dist/storage.js +19 -18
  291. package/dist/summarizer.js +3 -3
  292. package/dist/support-passport/index.js +76 -73
  293. package/dist/transfer/backup.js +2 -2
  294. package/dist/transfer/capsule-export.js +2 -2
  295. package/dist/transfer/capsule-import.js +4 -4
  296. package/dist/transfer/export-okf.js +20 -19
  297. package/dist/verified-recall.js +20 -19
  298. package/dist/who-knows-cli.js +21 -20
  299. package/dist/who-knows.js +20 -19
  300. package/package.json +2 -2
  301. package/src/access-http-briefing.ts +4 -0
  302. package/src/access-http.test.ts +40 -11
  303. package/src/access-http.ts +6 -0
  304. package/src/access-observe-write-category.ts +1 -1
  305. package/src/access-observe-write-surface.ts +24 -19
  306. package/src/access-operations-batch.ts +18 -2
  307. package/src/access-service-peer-capture-backoff.test.ts +236 -0
  308. package/src/access-service.ts +11 -2
  309. package/src/briefing-abort.test.ts +173 -0
  310. package/src/briefing-deadline.test.ts +84 -0
  311. package/src/briefing-deadline.ts +33 -0
  312. package/src/briefing-window-abort.test.ts +47 -0
  313. package/src/briefing-window.ts +33 -4
  314. package/src/briefing.ts +23 -16
  315. package/src/capture-write-lock-scope.test.ts +32 -0
  316. package/src/capture-write-lock-scope.ts +30 -0
  317. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  318. package/src/correction/passive-correction-notifications.ts +4 -0
  319. package/src/embedding-generation-marker.ts +51 -0
  320. package/src/embedding-index-storage.ts +13 -29
  321. package/src/entity-lazy-compatibility.test.ts +229 -0
  322. package/src/entity-migration-completion-notification.test.ts +148 -0
  323. package/src/index.ts +2 -2
  324. package/src/location/tagging.ts +14 -12
  325. package/src/offline-sync-embedding-delete-digest.test.ts +58 -0
  326. package/src/offline-sync-embedding-generation-marker.test.ts +245 -0
  327. package/src/offline-sync-embedding-generation.ts +47 -2
  328. package/src/offline-sync-embedding-lock-loss.test.ts +42 -0
  329. package/src/offline-sync-file-io.ts +61 -2
  330. package/src/offline-sync.test.ts +313 -20
  331. package/src/offline-sync.ts +47 -22
  332. package/src/orchestrator-init.test.ts +49 -1
  333. package/src/orchestrator.ts +9 -8
  334. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  335. package/src/storage/entity-canonical-id-migration.ts +17 -8
  336. package/src/storage/entity-canonical-id-references.ts +24 -0
  337. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  338. package/src/storage/entity-migration-completion-cache.ts +153 -0
  339. package/src/storage/entity-store.ts +58 -86
  340. package/src/storage/memory-read-store.ts +9 -3
  341. package/src/storage/windowed-memory-read.ts +8 -0
  342. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  343. package/src/storage.ts +9 -3
  344. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  345. package/dist/chunk-3DKKZVXS.js.map +0 -1
  346. package/dist/chunk-CCTLRRV6.js.map +0 -1
  347. package/dist/chunk-CHSRA76M.js.map +0 -1
  348. package/dist/chunk-FRXS6SC7.js.map +0 -1
  349. package/dist/chunk-FWV2SXSL.js.map +0 -1
  350. package/dist/chunk-L46MG2GE.js.map +0 -1
  351. package/dist/chunk-LWRIGKFM.js.map +0 -1
  352. package/dist/chunk-OXSTKCYK.js.map +0 -1
  353. package/dist/chunk-PKPOO6X5.js.map +0 -1
  354. package/dist/chunk-SUKPM6DV.js.map +0 -1
  355. package/dist/chunk-UXAF4RQM.js.map +0 -1
  356. package/dist/chunk-VY6BD5KB.js.map +0 -1
  357. package/dist/chunk-Y57GJDG5.js.map +0 -1
  358. package/dist/chunk-YBKQAVPZ.js.map +0 -1
  359. package/dist/chunk-YUE7J2HZ.js.map +0 -1
  360. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  361. /package/dist/{capsule-crypto-GWVG7LGC.js.map → briefing-deadline.js.map} +0 -0
  362. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  363. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  364. /package/dist/{graph-edge-decay-3HVLMT6U.js.map → capture-write-lock-scope.js.map} +0 -0
  365. /package/dist/{chunk-FAKRTS3Y.js.map → chunk-2AKIVSHP.js.map} +0 -0
  366. /package/dist/{chunk-EIYJK7OD.js.map → chunk-2M36PR7R.js.map} +0 -0
  367. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  368. /package/dist/{chunk-6P6MZDBL.js.map → chunk-3NL4ALUP.js.map} +0 -0
  369. /package/dist/{chunk-6CC2YCKG.js.map → chunk-43TUWQNR.js.map} +0 -0
  370. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  371. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  372. /package/dist/{chunk-5KGYR5VN.js.map → chunk-5DVOBBTV.js.map} +0 -0
  373. /package/dist/{chunk-RT2GN6KQ.js.map → chunk-5FUDSK6Z.js.map} +0 -0
  374. /package/dist/{chunk-B4XSXFKI.js.map → chunk-5G53JSRO.js.map} +0 -0
  375. /package/dist/{chunk-BGK3SFCC.js.map → chunk-5X2H3X4J.js.map} +0 -0
  376. /package/dist/{chunk-4J7BNL7B.js.map → chunk-6ZQX3R66.js.map} +0 -0
  377. /package/dist/{chunk-JNLYHJG7.js.map → chunk-74AY3S7U.js.map} +0 -0
  378. /package/dist/{chunk-RUAIO5UA.js.map → chunk-7J6A5MV2.js.map} +0 -0
  379. /package/dist/{chunk-A45QXZMU.js.map → chunk-7SAE6T36.js.map} +0 -0
  380. /package/dist/{chunk-QE2XQM3S.js.map → chunk-7YVER7ZO.js.map} +0 -0
  381. /package/dist/{chunk-IJH6METJ.js.map → chunk-A3UFV4K3.js.map} +0 -0
  382. /package/dist/{chunk-LXN737FX.js.map → chunk-AAANTXCQ.js.map} +0 -0
  383. /package/dist/{chunk-I3IPXGFQ.js.map → chunk-AJJPXUE6.js.map} +0 -0
  384. /package/dist/{chunk-2DPJFJFH.js.map → chunk-BNJAVRNJ.js.map} +0 -0
  385. /package/dist/{chunk-YL564QS2.js.map → chunk-CCL2OXFO.js.map} +0 -0
  386. /package/dist/{chunk-ZX5N5D6M.js.map → chunk-DAOMWGO2.js.map} +0 -0
  387. /package/dist/{chunk-23ZMQONL.js.map → chunk-ERMT5WQM.js.map} +0 -0
  388. /package/dist/{chunk-VTCNVGM6.js.map → chunk-EWBXMGXR.js.map} +0 -0
  389. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  390. /package/dist/{chunk-XTN54NML.js.map → chunk-G3T6YMBO.js.map} +0 -0
  391. /package/dist/{chunk-2JNBPOZ2.js.map → chunk-GDYLZ6FR.js.map} +0 -0
  392. /package/dist/{chunk-DZPTR2XQ.js.map → chunk-GFAEKSFR.js.map} +0 -0
  393. /package/dist/{chunk-M7LA4QBY.js.map → chunk-GXVXBYRA.js.map} +0 -0
  394. /package/dist/{chunk-JGMJZBFV.js.map → chunk-HPNXS6W6.js.map} +0 -0
  395. /package/dist/{chunk-BFPF5N3H.js.map → chunk-HXFIILGF.js.map} +0 -0
  396. /package/dist/{chunk-CMNL3QSL.js.map → chunk-IN6U2BS5.js.map} +0 -0
  397. /package/dist/{chunk-BWRX322G.js.map → chunk-IUYXCTA7.js.map} +0 -0
  398. /package/dist/{chunk-LU57EEJW.js.map → chunk-JBWD3J5U.js.map} +0 -0
  399. /package/dist/{chunk-2V664H6O.js.map → chunk-JS44PBCM.js.map} +0 -0
  400. /package/dist/{chunk-XKTHJHSQ.js.map → chunk-JZCBMKR5.js.map} +0 -0
  401. /package/dist/{chunk-MXPVNDGO.js.map → chunk-K5QQXVG6.js.map} +0 -0
  402. /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
  403. /package/dist/{chunk-WUBGMV3G.js.map → chunk-KRAMCBIW.js.map} +0 -0
  404. /package/dist/{chunk-NDMPRT3Z.js.map → chunk-KW5YZELC.js.map} +0 -0
  405. /package/dist/{chunk-2RQR4TXV.js.map → chunk-LIBVANOF.js.map} +0 -0
  406. /package/dist/{chunk-M7L5A2AT.js.map → chunk-LMMBV7QC.js.map} +0 -0
  407. /package/dist/{chunk-PJBYVJKO.js.map → chunk-LXXIV7Y2.js.map} +0 -0
  408. /package/dist/{chunk-MZCTS3ZG.js.map → chunk-M2DOJBH7.js.map} +0 -0
  409. /package/dist/{chunk-WOPZ2P5Z.js.map → chunk-M4HRYLOA.js.map} +0 -0
  410. /package/dist/{chunk-JCYEDMOY.js.map → chunk-M6TP6UYD.js.map} +0 -0
  411. /package/dist/{chunk-KAUCVTAW.js.map → chunk-MLNXATFG.js.map} +0 -0
  412. /package/dist/{chunk-2NX7JTST.js.map → chunk-MPWOAIIH.js.map} +0 -0
  413. /package/dist/{chunk-RQLTCHUL.js.map → chunk-MWURUSRE.js.map} +0 -0
  414. /package/dist/{chunk-3EGL7XZO.js.map → chunk-N74WJ7B2.js.map} +0 -0
  415. /package/dist/{chunk-DRXTMDAG.js.map → chunk-N7FNCE7F.js.map} +0 -0
  416. /package/dist/{chunk-6CWAY4R7.js.map → chunk-NPLM7AFU.js.map} +0 -0
  417. /package/dist/{chunk-VECRSYQG.js.map → chunk-NRGQ5HJO.js.map} +0 -0
  418. /package/dist/{chunk-PPDNG7WC.js.map → chunk-NSP5PIQH.js.map} +0 -0
  419. /package/dist/{chunk-6HVLHIAB.js.map → chunk-P7OVFGMU.js.map} +0 -0
  420. /package/dist/{chunk-EX27VKU6.js.map → chunk-PDTNQLPC.js.map} +0 -0
  421. /package/dist/{chunk-ME5LVISI.js.map → chunk-PJCVI44H.js.map} +0 -0
  422. /package/dist/{chunk-LJCHDFLP.js.map → chunk-Q3PNQYAC.js.map} +0 -0
  423. /package/dist/{chunk-YFJHFEET.js.map → chunk-QS765SBL.js.map} +0 -0
  424. /package/dist/{chunk-FJ264QBP.js.map → chunk-QTDZ2NYC.js.map} +0 -0
  425. /package/dist/{chunk-Q6UZREJK.js.map → chunk-RKRX4C4H.js.map} +0 -0
  426. /package/dist/{chunk-GLZWOIU3.js.map → chunk-SJTSWG2Z.js.map} +0 -0
  427. /package/dist/{chunk-XM747YJF.js.map → chunk-TFWJXWL6.js.map} +0 -0
  428. /package/dist/{chunk-QFQ4UG3I.js.map → chunk-TXKQBQYW.js.map} +0 -0
  429. /package/dist/{chunk-BMOJFGVX.js.map → chunk-UALXWWGN.js.map} +0 -0
  430. /package/dist/{chunk-B44NFHWI.js.map → chunk-UNOAVHV4.js.map} +0 -0
  431. /package/dist/{chunk-TF5NZGPS.js.map → chunk-UQBOPCDK.js.map} +0 -0
  432. /package/dist/{chunk-5R2IF2KI.js.map → chunk-UXGXJUZ5.js.map} +0 -0
  433. /package/dist/{chunk-J6B6FK2X.js.map → chunk-VCOMACR6.js.map} +0 -0
  434. /package/dist/{chunk-WCGJK4NQ.js.map → chunk-VKQI47DJ.js.map} +0 -0
  435. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  436. /package/dist/{chunk-BCDECPVO.js.map → chunk-WV4UYDP6.js.map} +0 -0
  437. /package/dist/{chunk-VWJ56T63.js.map → chunk-WXJC2L66.js.map} +0 -0
  438. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  439. /package/dist/{chunk-FPANDKMQ.js.map → chunk-XC3H7KON.js.map} +0 -0
  440. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  441. /package/dist/{chunk-3SL52JKD.js.map → chunk-XVZA6VXC.js.map} +0 -0
  442. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  443. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  444. /package/dist/{chunk-XTVH2NAW.js.map → chunk-YECEEJUU.js.map} +0 -0
  445. /package/dist/{chunk-QR6W3FSW.js.map → chunk-Z5277JWF.js.map} +0 -0
  446. /package/dist/{chunk-CTHYR4QK.js.map → chunk-ZFNXJEUG.js.map} +0 -0
  447. /package/dist/{chunk-6Q56WDHG.js.map → chunk-ZLCBLDJO.js.map} +0 -0
  448. /package/dist/{chunk-KRACAE73.js.map → chunk-ZNRTHYYW.js.map} +0 -0
  449. /package/dist/{chunk-7AIPWNM7.js.map → chunk-ZSMA67FV.js.map} +0 -0
@@ -0,0 +1,173 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test, { type TestContext } from "node:test";
6
+ import { abortError } from "./abort-error.js";
7
+ import fs from "node:fs/promises";
8
+ import { syncBuiltinESMExports } from "node:module";
9
+ import { AccessObserveWriteSurface } from "./access-observe-write-surface.js";
10
+ import { Orchestrator } from "./orchestrator.js";
11
+ import { buildBriefing, type ParsedBriefingWindow } from "./briefing.js";
12
+ import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
13
+ import { StorageManager } from "./storage.js";
14
+ import type { WindowedMemoryReadOptions, WindowedMemoryReadResult } from "./storage/windowed-memory-read.js";
15
+ import type { EntityFile, MemoryFile } from "./types.js";
16
+
17
+ const WINDOW: ParsedBriefingWindow = {
18
+ from: new Date("2026-04-10T00:00:00.000Z"),
19
+ to: new Date("2026-04-11T00:00:00.000Z"),
20
+ label: "test-window",
21
+ };
22
+ const MEMORY: MemoryFile = {
23
+ path: "/synthetic/mem.md",
24
+ frontmatter: {
25
+ id: "synthetic-mem",
26
+ category: "fact",
27
+ created: "2026-04-10T01:00:00.000Z",
28
+ updated: "2026-04-10T02:00:00.000Z",
29
+ source: "test",
30
+ confidence: 0.9,
31
+ confidenceTier: "explicit",
32
+ tags: [],
33
+ },
34
+ content: "synthetic briefing memory",
35
+ };
36
+ const ENTITY: EntityFile = {
37
+ name: "SyntheticEntity",
38
+ type: "project",
39
+ updated: "2026-04-10T03:00:00.000Z",
40
+ facts: ["synthetic fact"],
41
+ timeline: [],
42
+ relationships: [],
43
+ activity: [],
44
+ aliases: [],
45
+ };
46
+
47
+ function makeStorage(t: TestContext): StorageManager {
48
+ const dir = mkdtempSync(path.join(os.tmpdir(), "remnic-briefing-abort-"));
49
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
50
+ const storage = new StorageManager(dir);
51
+ t.mock.method(storage, "readMemoriesWindow", async (_options: WindowedMemoryReadOptions) => ({
52
+ memories: [MEMORY],
53
+ filePaths: [MEMORY.path],
54
+ }));
55
+ t.mock.method(storage, "readAllEntityFiles", async (_options?: CorpusReadOptions) => [ENTITY]);
56
+ return storage;
57
+ }
58
+
59
+ function makeStallingStorage(t: TestContext) {
60
+ const storage = makeStorage(t);
61
+ const fallback = Promise.withResolvers<void>();
62
+ // The fallback releases broken signal plumbing too, so the regression cannot hang.
63
+ t.mock.method(
64
+ storage,
65
+ "readMemoriesWindow",
66
+ (options: WindowedMemoryReadOptions): Promise<WindowedMemoryReadResult> => {
67
+ const signal = options.abortSignal;
68
+ if (!signal) return fallback.promise.then(() => ({ memories: [], filePaths: [] }));
69
+ const read = Promise.withResolvers<WindowedMemoryReadResult>();
70
+ const onAbort = () => read.reject(abortError("corpus read aborted"));
71
+ if (signal.aborted) {
72
+ onAbort();
73
+ return read.promise;
74
+ }
75
+ signal.addEventListener("abort", onAbort, { once: true });
76
+ void fallback.promise.then(() => {
77
+ signal.removeEventListener("abort", onAbort);
78
+ read.resolve({ memories: [], filePaths: [] });
79
+ });
80
+ return read.promise;
81
+ }
82
+ );
83
+ return { storage, releaseFallback: fallback.resolve };
84
+ }
85
+
86
+ test("buildBriefing rejects with an AbortError when the caller's cancel signal fires mid-read", async (t) => {
87
+ const { storage, releaseFallback } = makeStallingStorage(t);
88
+ const controller = new AbortController();
89
+ const call = buildBriefing({ storage, window: WINDOW, allowLlm: false, abortSignal: controller.signal });
90
+ controller.abort();
91
+ releaseFallback();
92
+ await assert.rejects(call, { name: "AbortError", message: /abort|cancel/i });
93
+ });
94
+
95
+ test("buildBriefing rejects when the caller's cancel signal is already aborted", async (t) => {
96
+ const { storage, releaseFallback } = makeStallingStorage(t);
97
+ const controller = new AbortController();
98
+ controller.abort();
99
+ const call = buildBriefing({ storage, window: WINDOW, allowLlm: false, abortSignal: controller.signal });
100
+ releaseFallback();
101
+ await assert.rejects(call, { name: "AbortError" });
102
+ });
103
+
104
+ test("a cancelled briefing does not drain the correction-notification queue", async (t) => {
105
+ const storage = makeStorage(t);
106
+ const queueDir = path.join(storage.dir, "state", "corrections");
107
+ mkdirSync(queueDir, { recursive: true });
108
+ const queueFile = path.join(queueDir, "notifications.jsonl");
109
+ writeFileSync(
110
+ queueFile,
111
+ `${JSON.stringify({ planId: "synthetic-plan", summary: "synthetic correction", undoCommand: "remnic correct plan synthetic-plan", createdAt: "2026-04-10T04:00:00.000Z" })}\n`
112
+ );
113
+ const controller = new AbortController();
114
+ const call = buildBriefing({
115
+ storage,
116
+ window: WINDOW,
117
+ abortSignal: controller.signal,
118
+ // Cancel after reads, just before the queue-draining phase.
119
+ followupGenerator: async () => {
120
+ controller.abort();
121
+ return [];
122
+ },
123
+ });
124
+ await assert.rejects(call, { name: "AbortError" });
125
+ assert.ok(existsSync(queueFile), "a cancelled briefing must not consume correction notifications");
126
+ });
127
+
128
+ test("buildBriefing returns a non-empty briefing when nothing is cancelled", async (t) => {
129
+ const result = await buildBriefing({ storage: makeStorage(t), window: WINDOW, allowLlm: false });
130
+ assert.equal(result.sections.activeThreads.length, 1);
131
+ assert.equal(result.sections.recentEntities.length, 1);
132
+ assert.ok(result.markdown.length > 0);
133
+ assert.deepEqual(result.window, { from: "2026-04-10T00:00:00.000Z", to: "2026-04-11T00:00:00.000Z" });
134
+ });
135
+
136
+ test("an HTTP/MCP briefing cancelled during the location read keeps the notification queue", async (t) => {
137
+ const storage = makeStorage(t);
138
+ const queueDir = path.join(storage.dir, "state", "corrections");
139
+ mkdirSync(queueDir, { recursive: true });
140
+ const queueFile = path.join(queueDir, "notifications.jsonl");
141
+ writeFileSync(
142
+ queueFile,
143
+ `${JSON.stringify({ planId: "synthetic-plan", summary: "synthetic correction", undoCommand: "remnic correct plan synthetic-plan", createdAt: "2026-04-10T04:00:00.000Z" })}\n`
144
+ );
145
+ const controller = new AbortController();
146
+ const read: (file: string, encoding: "utf-8") => Promise<string> = fs.readFile;
147
+ t.mock.method(fs, "readFile", async (file: string) => {
148
+ if (file.endsWith(path.join("state", "locations", "sync.json"))) controller.abort(abortError("deadline"));
149
+ return read(file, "utf-8");
150
+ });
151
+ syncBuiltinESMExports();
152
+ t.after(() => {
153
+ t.mock.restoreAll();
154
+ syncBuiltinESMExports();
155
+ });
156
+ // Object.create yields untyped values, so no assertion is needed: briefing() reads only these members.
157
+ const orchestrator: Orchestrator = Object.assign(Object.create(Orchestrator.prototype), {
158
+ config: {
159
+ briefing: { enabled: true, defaultWindow: "24h", defaultFormat: "markdown", maxFollowups: 0, llmFollowups: false },
160
+ location: { enabled: true, tagging: { enabled: true }, timezone: "UTC" },
161
+ },
162
+ getStorage: async () => storage,
163
+ });
164
+ const surface = new AccessObserveWriteSurface(
165
+ Object.assign(Object.create(null), { orchestrator, resolveReadableNamespace: () => "default" }),
166
+ );
167
+
168
+ await assert.rejects(
169
+ surface.briefing({ includeLocation: true, abortSignal: controller.signal }),
170
+ { name: "AbortError" },
171
+ );
172
+ assert.ok(existsSync(queueFile), "a briefing cancelled during the location read must not consume notifications");
173
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Issue #3139 — the briefing's default deadline.
3
+ *
4
+ * The budget is a server-side limit, not a client cancellation: exhausting it
5
+ * must surface as an explicit TimeoutError, never as an empty result.
6
+ */
7
+
8
+ import assert from "node:assert/strict";
9
+ import test from "node:test";
10
+
11
+ import {
12
+ BRIEFING_DEADLINE_MS,
13
+ BriefingTimeoutError,
14
+ withBriefingDeadline,
15
+ } from "./briefing-deadline.js";
16
+
17
+ test("withBriefingDeadline rejects with a TimeoutError when the default budget is exhausted", async (t) => {
18
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
19
+
20
+ const pending = withBriefingDeadline(
21
+ () => Promise.withResolvers<never>().promise,
22
+ );
23
+
24
+ t.mock.timers.tick(BRIEFING_DEADLINE_MS);
25
+
26
+ await assert.rejects(pending, (err) => {
27
+ assert.ok(
28
+ err instanceof BriefingTimeoutError,
29
+ "budget exhaustion must surface as a timeout",
30
+ );
31
+ assert.equal(err.name, "TimeoutError");
32
+ assert.equal(err.deadlineMs, BRIEFING_DEADLINE_MS);
33
+ return true;
34
+ });
35
+ });
36
+
37
+ test("withBriefingDeadline clears its timer once the run settles", async (t) => {
38
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
39
+ let observed: AbortSignal | undefined;
40
+
41
+ const result = await withBriefingDeadline(async (signal) => {
42
+ observed = signal;
43
+ return "briefing";
44
+ });
45
+
46
+ assert.equal(result, "briefing");
47
+ t.mock.timers.tick(BRIEFING_DEADLINE_MS);
48
+ assert.equal(
49
+ observed?.aborted,
50
+ false,
51
+ "the budget timer must be cleared when the run settles, not left to fire afterwards",
52
+ );
53
+ });
54
+
55
+ test("withBriefingDeadline preserves work failures and clears their timer", async (t) => {
56
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
57
+ const failure = new Error("synthetic work failure");
58
+ const signals: AbortSignal[] = [];
59
+
60
+ const pending = withBriefingDeadline(async (signal) => {
61
+ signals.push(signal);
62
+ throw failure;
63
+ });
64
+
65
+ await assert.rejects(pending, (err) => err === failure);
66
+ assert.equal(signals.length, 1);
67
+ t.mock.timers.tick(BRIEFING_DEADLINE_MS);
68
+ assert.equal(signals[0].aborted, false);
69
+ });
70
+
71
+ test("withBriefingDeadline normalizes arbitrary caller cancellation to AbortError", async () => {
72
+ const controller = new AbortController();
73
+ const pending = withBriefingDeadline(
74
+ () => Promise.withResolvers<never>().promise,
75
+ controller.signal,
76
+ );
77
+ controller.abort(new Error("synthetic caller cancellation"));
78
+
79
+ await assert.rejects(pending, (err) => {
80
+ assert.ok(err instanceof Error);
81
+ assert.equal(err.name, "AbortError");
82
+ return true;
83
+ });
84
+ });
@@ -0,0 +1,33 @@
1
+ import { isAbortError, raceAbort } from "./abort-error.js";
2
+
3
+ export const BRIEFING_DEADLINE_MS = 30_000;
4
+
5
+ export class BriefingTimeoutError extends Error {
6
+ constructor(readonly deadlineMs: number) {
7
+ super(`briefing timed out after ${deadlineMs}ms`);
8
+ this.name = "TimeoutError";
9
+ }
10
+ }
11
+
12
+ export async function withBriefingDeadline<T>(
13
+ run: (signal: AbortSignal) => Promise<T>,
14
+ abortSignal?: AbortSignal
15
+ ): Promise<T> {
16
+ const controller = new AbortController();
17
+ const signal = abortSignal ? AbortSignal.any([abortSignal, controller.signal]) : controller.signal;
18
+ const timer = setTimeout(
19
+ () => controller.abort(new BriefingTimeoutError(BRIEFING_DEADLINE_MS)),
20
+ BRIEFING_DEADLINE_MS
21
+ );
22
+ try {
23
+ signal.throwIfAborted();
24
+ return await raceAbort(run(signal), signal, "briefing aborted");
25
+ } catch (err) {
26
+ // raceAbort normalizes cancellation; retain the distinct deadline reason.
27
+ const reason: unknown = signal.reason;
28
+ if (signal.aborted && (reason instanceof BriefingTimeoutError || isAbortError(reason))) throw reason;
29
+ throw err;
30
+ } finally {
31
+ clearTimeout(timer);
32
+ }
33
+ }
@@ -0,0 +1,47 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { safeReadMemories } from "./briefing-window.js";
4
+ import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
5
+ import type { MemoryFile } from "./types.js";
6
+
7
+ test("caller cancellation reaches the signal-aware legacy briefing scan", async () => {
8
+ const controller = new AbortController();
9
+ const started = Promise.withResolvers<AbortSignal>();
10
+ const release = Promise.withResolvers<void>();
11
+ const reader = {
12
+ supportsAbortSignal: true,
13
+ async readAllMemories(options?: CorpusReadOptions): Promise<MemoryFile[]> {
14
+ assert.ok(options?.abortSignal);
15
+ started.resolve(options.abortSignal);
16
+ await release.promise;
17
+ options.abortSignal.throwIfAborted();
18
+ return [];
19
+ },
20
+ };
21
+ const result = safeReadMemories(
22
+ reader,
23
+ {
24
+ from: new Date("2026-04-10T00:00:00.000Z"),
25
+ to: new Date("2026-04-11T00:00:00.000Z"),
26
+ label: "yesterday",
27
+ },
28
+ { signal: controller.signal }
29
+ ).then(
30
+ () => ({ ok: true as const }),
31
+ (error) => ({ ok: false as const, error })
32
+ );
33
+ try {
34
+ const scanSignal = await started.promise;
35
+ assert.equal(scanSignal.aborted, false);
36
+ controller.abort();
37
+ release.resolve();
38
+ const outcome = await result;
39
+ assert.equal(outcome.ok, false, "a cancelled scan must reject, not return an empty corpus");
40
+ assert.ok(outcome.error instanceof Error);
41
+ assert.equal(outcome.error.name, "AbortError");
42
+ assert.equal(scanSignal.aborted, true, "the underlying scan must stop too");
43
+ } finally {
44
+ release.resolve();
45
+ await result;
46
+ }
47
+ });
@@ -1,11 +1,12 @@
1
1
  import { log } from "./logger.js";
2
- import type { MemoryFile } from "./types.js";
2
+ import type { EntityFile, MemoryFile } from "./types.js";
3
3
  import { isAbortError } from "./abort-error.js";
4
4
  import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
5
5
  import type { ParsedBriefingWindow } from "./briefing.js";
6
+ import type { WindowedMemoryReadOptions } from "./storage/windowed-memory-read.js";
6
7
 
7
8
  type BriefingMemoryReader = {
8
- readMemoriesWindow?: (options: { updatedAfter?: Date }) => Promise<{ memories: MemoryFile[] }>;
9
+ readMemoriesWindow?: (options: WindowedMemoryReadOptions) => Promise<{ memories: MemoryFile[] }>;
9
10
  readAllMemories: ((options?: CorpusReadOptions) => Promise<MemoryFile[]>) & {
10
11
  supportsAbortSignal?: boolean;
11
12
  };
@@ -30,6 +31,12 @@ export const BRIEFING_FULL_READ_FALLBACK_MS = 30_000;
30
31
  export interface SafeReadMemoriesOptions {
31
32
  /** Test seam overriding {@link BRIEFING_FULL_READ_FALLBACK_MS}. */
32
33
  fallbackDeadlineMs?: number;
34
+ /**
35
+ * Caller cancellation. The signal reaches the storage read so it stops at
36
+ * its next boundary, and a cancelled read is rethrown rather than degraded
37
+ * into an empty corpus (issue #3139).
38
+ */
39
+ signal?: AbortSignal;
33
40
  }
34
41
 
35
42
  type BriefingReadMode = "windowed" | "full-read-fallback";
@@ -138,6 +145,12 @@ export async function safeReadMemories(
138
145
  resultCount = memories.length;
139
146
  return memories;
140
147
  } catch (err) {
148
+ if (isAbortError(err) && options.signal?.aborted) {
149
+ // Caller cancellation is not a read failure: rethrow it so the briefing
150
+ // rejects instead of degrading into an empty corpus (issue #3139). The
151
+ // own fallback deadline is reported as BriefingReadTimedOut below.
152
+ throw err;
153
+ }
141
154
  if (err instanceof BriefingReadTimedOut) {
142
155
  outcome = "timeout";
143
156
  bestEffortLog(
@@ -174,7 +187,9 @@ async function readBriefingMemories(
174
187
  // the full corpus on cache misses; keep the full-read fallback for custom
175
188
  // StorageManager-compatible callers that predate readMemoriesWindow().
176
189
  if (mode === "windowed") {
177
- const result = await storage.readMemoriesWindow!({ updatedAfter: window.from });
190
+ const readOptions: WindowedMemoryReadOptions = { updatedAfter: window.from };
191
+ if (options.signal) readOptions.abortSignal = options.signal;
192
+ const result = await storage.readMemoriesWindow!(readOptions);
178
193
  return result.memories;
179
194
  }
180
195
  // Refuse legacy adapters that do not explicitly declare cancellation support (#2827).
@@ -195,12 +210,13 @@ async function readBriefingMemories(
195
210
  deadlineFired = true;
196
211
  controller.abort();
197
212
  }, deadlineMs);
213
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
198
214
  try {
199
215
  // Direct await, no Promise.race: the signal IS the deadline. A
200
216
  // signal-aware read settles on abort (AbortError at its next scan
201
217
  // boundary, #2307), so nothing is left scanning in the background once
202
218
  // this function returns.
203
- const memories = await storage.readAllMemories({ abortSignal: controller.signal });
219
+ const memories = await storage.readAllMemories({ abortSignal: signal });
204
220
  if (deadlineFired) {
205
221
  // A double that resolves partial rows instead of rejecting on abort
206
222
  // must not have them served as a complete corpus.
@@ -214,3 +230,16 @@ async function readBriefingMemories(
214
230
  clearTimeout(timer);
215
231
  }
216
232
  }
233
+
234
+ export async function safeReadEntities(
235
+ storage: { readAllEntityFiles(options?: CorpusReadOptions): Promise<EntityFile[]> },
236
+ signal: AbortSignal,
237
+ ): Promise<EntityFile[]> {
238
+ try {
239
+ return await storage.readAllEntityFiles({ abortSignal: signal });
240
+ } catch (err) {
241
+ if (isAbortError(err) && signal.aborted) throw err;
242
+ log.warn(`briefing: readAllEntityFiles failed: ${err}`);
243
+ return [];
244
+ }
245
+ }
package/src/briefing.ts CHANGED
@@ -19,12 +19,14 @@ import { readFile } from "node:fs/promises";
19
19
  import os from "node:os";
20
20
  import path from "node:path";
21
21
  import { log } from "./logger.js";
22
+ import { isAbortError, throwIfAborted } from "./abort-error.js";
23
+ import { withBriefingDeadline } from "./briefing-deadline.js";
22
24
  import { withBriefingFollowupTimeout } from "./briefing-followup-timeout.js";
23
25
  import { extractJsonCandidates } from "./json-extract.js";
24
26
  import { drainPassiveCorrectionNotifications } from "./correction/passive-correction-notifications.js";
25
27
  import { excludeSupportPassportPrivateMemories } from "./support-passport/card-projection.js";
26
28
  import { normalizeEntityName, StorageManager } from "./storage.js";
27
- import { safeReadMemories } from "./briefing-window.js";
29
+ import { safeReadEntities, safeReadMemories } from "./briefing-window.js";
28
30
  import { readEnvVar, resolveHomeDir } from "./runtime/env.js";
29
31
  import type {
30
32
  BriefingActiveThread,
@@ -817,6 +819,8 @@ export interface BuildBriefingOptions {
817
819
  followupGenerator?: BriefingFollowupGenerator;
818
820
  /** Injected "now" — makes tests deterministic. */
819
821
  now?: Date;
822
+ /** Caller cancellation must not consume correction notifications. */
823
+ abortSignal?: AbortSignal;
820
824
  }
821
825
 
822
826
  const MAX_ACTIVE_THREADS = 8;
@@ -830,15 +834,23 @@ const MAX_OPEN_COMMITMENTS = 8;
830
834
  * omitted and `followupsUnavailableReason` is set.
831
835
  */
832
836
  export async function buildBriefing(options: BuildBriefingOptions): Promise<BriefingResult> {
837
+ return withBriefingDeadline(
838
+ (signal) => buildBriefingWithSignal(options, signal),
839
+ options.abortSignal,
840
+ );
841
+ }
842
+
843
+ async function buildBriefingWithSignal(options: BuildBriefingOptions, signal: AbortSignal): Promise<BriefingResult> {
833
844
  const now = options.now ?? new Date();
834
845
  const window = options.window ?? defaultWindow(now);
835
846
  const maxFollowups = clampFollowups(options.maxFollowups);
836
847
  const focus = options.focus ?? null;
837
848
 
838
849
  const [allMemories, allEntities] = await Promise.all([
839
- safeReadMemories(options.storage, window).then(excludeSupportPassportPrivateMemories),
840
- safeReadEntities(options.storage),
850
+ safeReadMemories(options.storage, window, { signal }).then(excludeSupportPassportPrivateMemories),
851
+ safeReadEntities(options.storage, signal),
841
852
  ]);
853
+ throwIfAborted(signal, "briefing aborted");
842
854
 
843
855
  const memoriesInWindow = filterMemoriesByWindow(allMemories, window);
844
856
  const focusedMemories = focus
@@ -852,6 +864,7 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
852
864
  const calendarLoadResult = options.calendarSource
853
865
  ? await loadTodayCalendar(options.calendarSource, now)
854
866
  : undefined;
867
+ throwIfAborted(signal, "briefing aborted");
855
868
 
856
869
  const calendarSourceErrors: BriefingCalendarSourceError[] =
857
870
  calendarLoadResult?.error ? [calendarLoadResult.error] : [];
@@ -904,18 +917,20 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
904
917
  log.warn(`briefing: ${followupsUnavailableReason}`);
905
918
  }
906
919
  }
920
+ throwIfAborted(signal, "briefing aborted");
907
921
 
908
922
  const sections: BriefingSections = {
909
923
  ...sectionsBase,
910
924
  suggestedFollowups: followups,
911
925
  };
912
926
 
913
- // Drain auto-applied passive-correction notifications (issue #1581). The
914
- // queue is cleared on read, so each notification surfaces in exactly one
915
- // briefing. Fail-open: a drain error never blocks the briefing.
927
+ // Drain auto-applied passive-correction notifications (issue #1581): cleared on read,
928
+ // so each surfaces in exactly one briefing. Fail-open except cancellation, which must
929
+ // not consume notifications nobody receives.
930
+ throwIfAborted(signal, "briefing aborted");
916
931
  let correctionNotifications: BriefingResult["correctionNotifications"];
917
932
  try {
918
- const drained = await drainPassiveCorrectionNotifications(options.storage.dir);
933
+ const drained = await drainPassiveCorrectionNotifications(options.storage.dir, signal);
919
934
  if (drained.length > 0) {
920
935
  correctionNotifications = drained.map((n) => ({
921
936
  planId: n.planId,
@@ -925,6 +940,7 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
925
940
  }));
926
941
  }
927
942
  } catch (err) {
943
+ if (isAbortError(err)) throw err;
928
944
  log.debug(`briefing: correction notification drain failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
929
945
  }
930
946
 
@@ -977,15 +993,6 @@ function defaultWindow(now: Date): ParsedBriefingWindow {
977
993
  return { from: new Date(now.getTime() - DAY_MS), to: now, label: "yesterday" };
978
994
  }
979
995
 
980
- async function safeReadEntities(storage: StorageManager): Promise<EntityFile[]> {
981
- try {
982
- return await storage.readAllEntityFiles();
983
- } catch (err) {
984
- log.warn(`briefing: readAllEntityFiles failed: ${err}`);
985
- return [];
986
- }
987
- }
988
-
989
996
  function memoryTimestamp(memory: MemoryFile): number {
990
997
  const raw = memory.frontmatter.updated || memory.frontmatter.created;
991
998
  if (!raw) return 0;
@@ -0,0 +1,32 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { scopedCaptureWriteLockLimits, withCaptureWriteLockBackoff } from "./capture-write-lock-scope.js";
4
+
5
+ const limits = { initialDelayMs: 20, maxDelayMs: 50, deadlineMs: 8_000 };
6
+ const expected = {
7
+ captureWriteLockInitialDelayMs: 20,
8
+ captureWriteLockMaxDelayMs: 50,
9
+ captureWriteLockDeadlineMs: 8_000,
10
+ };
11
+
12
+ test("capture backoff scope belongs only to its storage and restores nested state after rejection", async () => {
13
+ const storage = { dir: "synthetic-shared-root" };
14
+ const other = { dir: storage.dir };
15
+ const result = await withCaptureWriteLockBackoff(storage, limits, async () => {
16
+ await Promise.resolve();
17
+ assert.deepEqual(scopedCaptureWriteLockLimits(storage), expected);
18
+ assert.equal(scopedCaptureWriteLockLimits(other), undefined);
19
+ await assert.rejects(
20
+ withCaptureWriteLockBackoff(other, { ...limits, deadlineMs: 4_000 }, async () => {
21
+ assert.equal(scopedCaptureWriteLockLimits(other)?.captureWriteLockDeadlineMs, 4_000);
22
+ assert.equal(scopedCaptureWriteLockLimits(storage), undefined);
23
+ throw new Error("peer apply failed");
24
+ }),
25
+ /peer apply failed/
26
+ );
27
+ return scopedCaptureWriteLockLimits(storage);
28
+ });
29
+ assert.deepEqual(result, expected);
30
+ assert.equal(scopedCaptureWriteLockLimits(storage), undefined);
31
+ assert.equal(scopedCaptureWriteLockLimits(other), undefined);
32
+ });
@@ -0,0 +1,30 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { CaptureWriteLockBackoffLimits, CaptureWriteLockLimitSource } from "./capture-write-lock-backoff.js";
3
+ import type { StorageManager } from "./storage.js";
4
+
5
+ type CaptureLockOwner = Pick<StorageManager, "dir">;
6
+
7
+ const captureScope = new AsyncLocalStorage<{ storage: CaptureLockOwner; limits: CaptureWriteLockLimitSource }>();
8
+
9
+ export function withCaptureWriteLockBackoff<T>(
10
+ storage: CaptureLockOwner,
11
+ limits: CaptureWriteLockBackoffLimits,
12
+ run: () => Promise<T>
13
+ ): Promise<T> {
14
+ return captureScope.run(
15
+ {
16
+ storage,
17
+ limits: {
18
+ captureWriteLockInitialDelayMs: limits.initialDelayMs,
19
+ captureWriteLockMaxDelayMs: limits.maxDelayMs,
20
+ captureWriteLockDeadlineMs: limits.deadlineMs,
21
+ },
22
+ },
23
+ run
24
+ );
25
+ }
26
+
27
+ export function scopedCaptureWriteLockLimits(storage: CaptureLockOwner): CaptureWriteLockLimitSource | undefined {
28
+ const scope = captureScope.getStore();
29
+ return scope?.storage === storage ? scope.limits : undefined;
30
+ }
@@ -0,0 +1,42 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import { syncBuiltinESMExports } from "node:module";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import test from "node:test";
7
+ import {
8
+ drainPassiveCorrectionNotifications,
9
+ enqueuePassiveCorrectionNotification,
10
+ } from "./passive-correction-notifications.js";
11
+
12
+ // Issue #3139 review: a briefing cancelled mid-drain must leave the queue
13
+ // intact, because the caller never receives what the drain consumed.
14
+ test("a drain cancelled after reading keeps the notification queue", async (t) => {
15
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "remnic-3139-drain-"));
16
+ t.after(() => fs.rm(dir, { recursive: true, force: true }));
17
+ await enqueuePassiveCorrectionNotification(dir, {
18
+ planId: "plan-1",
19
+ summary: "kept",
20
+ undoCommand: "undo",
21
+ createdAt: new Date(0).toISOString(),
22
+ });
23
+ const controller = new AbortController();
24
+ const read: (file: string, encoding: "utf-8") => Promise<string> = fs.readFile;
25
+ t.mock.method(fs, "readFile", async (file: string) => {
26
+ const content = await read(file, "utf-8");
27
+ controller.abort(new Error("deadline"));
28
+ return content;
29
+ });
30
+ syncBuiltinESMExports();
31
+ t.after(() => {
32
+ t.mock.restoreAll();
33
+ syncBuiltinESMExports();
34
+ });
35
+
36
+ await assert.rejects(drainPassiveCorrectionNotifications(dir, controller.signal), { name: "AbortError" });
37
+
38
+ t.mock.restoreAll();
39
+ syncBuiltinESMExports();
40
+ const kept = await drainPassiveCorrectionNotifications(dir);
41
+ assert.equal(kept.length, 1, "the cancelled drain must not consume the queue");
42
+ });
@@ -14,6 +14,7 @@
14
14
 
15
15
  import { mkdir, readFile, rename, writeFile, unlink } from "node:fs/promises";
16
16
  import path from "node:path";
17
+ import { throwIfAborted } from "../abort-error.js";
17
18
  import { serializeMutations } from "../utils/serialize-mutations.js";
18
19
 
19
20
  // ---------------------------------------------------------------------------
@@ -84,6 +85,7 @@ export async function enqueuePassiveCorrectionNotification(
84
85
  */
85
86
  export async function drainPassiveCorrectionNotifications(
86
87
  storageDir: string,
88
+ signal?: AbortSignal,
87
89
  ): Promise<PassiveCorrectionNotification[]> {
88
90
  const filePath = notificationsPath(storageDir);
89
91
  return serializeMutations(`passive-notification:${filePath}`, async () => {
@@ -96,6 +98,8 @@ export async function drainPassiveCorrectionNotifications(
96
98
  throw err;
97
99
  }
98
100
 
101
+ // Cancellation after the read must keep the queue: the caller never receives these.
102
+ throwIfAborted(signal, "briefing aborted");
99
103
  const notifications: PassiveCorrectionNotification[] = [];
100
104
  for (const line of raw.split("\n")) {
101
105
  const trimmed = line.trim();