@remnic/core 10.0.49 → 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 (338) hide show
  1. package/dist/access-admin-ops-surface.js +41 -40
  2. package/dist/access-authorization-probe.js +42 -41
  3. package/dist/access-boundary.js +41 -40
  4. package/dist/access-cli.js +107 -106
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +41 -40
  7. package/dist/access-http-briefing.d.ts +2 -0
  8. package/dist/access-http-briefing.d.ts.map +1 -1
  9. package/dist/access-http-briefing.js +42 -41
  10. package/dist/access-http-query.js +43 -42
  11. package/dist/access-http.d.ts.map +1 -1
  12. package/dist/access-http.js +59 -58
  13. package/dist/access-identity-continuity-surface.js +41 -40
  14. package/dist/access-lcm-surface.js +41 -40
  15. package/dist/access-mcp.js +51 -50
  16. package/dist/access-namespace-preflight.js +41 -40
  17. package/dist/access-observe-write-category.d.ts +1 -1
  18. package/dist/access-observe-write-category.d.ts.map +1 -1
  19. package/dist/access-observe-write-category.js +10 -10
  20. package/dist/access-observe-write-surface.d.ts.map +1 -1
  21. package/dist/access-observe-write-surface.js +41 -40
  22. package/dist/access-offline-manifest.js +4 -4
  23. package/dist/access-operations-batch.js +42 -41
  24. package/dist/access-operations.js +49 -48
  25. package/dist/access-recall-concurrency.js +41 -40
  26. package/dist/access-recall-response.js +41 -40
  27. package/dist/access-recall-surface.js +41 -40
  28. package/dist/access-schema.js +6 -6
  29. package/dist/access-service.d.ts +5 -0
  30. package/dist/access-service.d.ts.map +1 -1
  31. package/dist/access-service.js +41 -40
  32. package/dist/access-surface-catalog.js +43 -42
  33. package/dist/access-surface-catalog.js.map +1 -1
  34. package/dist/active-recall.js +14 -14
  35. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  36. package/dist/briefing-deadline.d.ts +7 -0
  37. package/dist/briefing-deadline.d.ts.map +1 -0
  38. package/dist/briefing-deadline.js +13 -0
  39. package/dist/briefing-window.d.ts +12 -4
  40. package/dist/briefing-window.d.ts.map +1 -1
  41. package/dist/briefing-window.js +3 -1
  42. package/dist/briefing.d.ts +2 -0
  43. package/dist/briefing.d.ts.map +1 -1
  44. package/dist/briefing.js +12 -11
  45. package/dist/buffer.js +3 -3
  46. package/dist/calibration.js +2 -2
  47. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  48. package/dist/causal-consolidation.js +11 -11
  49. package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
  50. package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
  51. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  52. package/dist/{chunk-PBWJVRKE.js → chunk-5FUDSK6Z.js} +2 -2
  53. package/dist/{chunk-V4GPFCJ7.js → chunk-5G53JSRO.js} +2 -2
  54. package/dist/{chunk-WJCAAVZX.js → chunk-5X2H3X4J.js} +2 -2
  55. package/dist/{chunk-CPBYKOTC.js → chunk-6ZQX3R66.js} +2 -2
  56. package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
  57. package/dist/{chunk-XSJL62LE.js → chunk-7J6A5MV2.js} +2 -2
  58. package/dist/{chunk-A3WXV7YU.js → chunk-7SAE6T36.js} +2 -2
  59. package/dist/{chunk-BWF3KNSQ.js → chunk-7YVER7ZO.js} +2 -2
  60. package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
  61. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  62. package/dist/chunk-A5L5KGDI.js.map +1 -0
  63. package/dist/{chunk-6TIVVOVU.js → chunk-BNJAVRNJ.js} +9 -9
  64. package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
  65. package/dist/{chunk-7XFQSQYS.js → chunk-CRN3EPE2.js} +4 -3
  66. package/dist/chunk-CRN3EPE2.js.map +1 -0
  67. package/dist/{chunk-3NYIXVCT.js → chunk-DMKTOCC7.js} +2 -2
  68. package/dist/chunk-DMKTOCC7.js.map +1 -0
  69. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  70. package/dist/chunk-EKUI7I2X.js.map +1 -0
  71. package/dist/{chunk-N6EM7ESC.js → chunk-EKYWZYZJ.js} +22 -14
  72. package/dist/chunk-EKYWZYZJ.js.map +1 -0
  73. package/dist/{chunk-FOKTW6GY.js → chunk-EWBXMGXR.js} +2 -2
  74. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  75. package/dist/{chunk-6W3QH3QW.js → chunk-GQIYL7OD.js} +388 -292
  76. package/dist/chunk-GQIYL7OD.js.map +1 -0
  77. package/dist/{chunk-E47UQBYW.js → chunk-GXVXBYRA.js} +3 -3
  78. package/dist/{chunk-RZDDNH63.js → chunk-HKOMHP6D.js} +27 -15
  79. package/dist/chunk-HKOMHP6D.js.map +1 -0
  80. package/dist/{chunk-AGCEJDG2.js → chunk-HPNXS6W6.js} +2 -2
  81. package/dist/{chunk-O4CA53UY.js → chunk-IMKOJVD4.js} +69 -63
  82. package/dist/chunk-IMKOJVD4.js.map +1 -0
  83. package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
  84. package/dist/{chunk-HI53EK3Y.js → chunk-IUYXCTA7.js} +2 -2
  85. package/dist/{chunk-VGRBKEBH.js → chunk-JBWD3J5U.js} +3 -3
  86. package/dist/{chunk-YB3WN6XA.js → chunk-K5QQXVG6.js} +12 -12
  87. package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
  88. package/dist/{chunk-HDT3DMUF.js → chunk-KW5YZELC.js} +2 -2
  89. package/dist/{chunk-DM6SOROF.js → chunk-LIBVANOF.js} +2 -2
  90. package/dist/{chunk-VKUMKVEU.js → chunk-LMMBV7QC.js} +4 -4
  91. package/dist/{chunk-NMOEOQV4.js → chunk-M2DOJBH7.js} +2 -2
  92. package/dist/{chunk-3INLTKTW.js → chunk-M4HRYLOA.js} +2 -2
  93. package/dist/{chunk-YY6PWFUC.js → chunk-M6TP6UYD.js} +3 -3
  94. package/dist/{chunk-BJTFLP3J.js → chunk-MLNXATFG.js} +2 -2
  95. package/dist/{chunk-C7WDWD35.js → chunk-MWURUSRE.js} +5 -5
  96. package/dist/{chunk-WIBWONL4.js → chunk-N74WJ7B2.js} +2 -2
  97. package/dist/{chunk-LCHFINL4.js → chunk-N7FNCE7F.js} +2 -2
  98. package/dist/{chunk-QZK6HG4J.js → chunk-NPLM7AFU.js} +2 -2
  99. package/dist/{chunk-CRJX4OMN.js → chunk-NSP5PIQH.js} +2 -2
  100. package/dist/{chunk-X52SXKVC.js → chunk-P7OVFGMU.js} +2 -2
  101. package/dist/{chunk-2J6BIOVN.js → chunk-PJCVI44H.js} +2 -2
  102. package/dist/{chunk-Q6PYCPHO.js → chunk-Q3PNQYAC.js} +2 -2
  103. package/dist/{chunk-D76AHITY.js → chunk-QS765SBL.js} +2 -2
  104. package/dist/{chunk-KNCR7APE.js → chunk-QTDZ2NYC.js} +7 -7
  105. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  106. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  107. package/dist/{chunk-HCPVL6SF.js → chunk-SJTSWG2Z.js} +3 -3
  108. package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
  109. package/dist/chunk-TEH5MDX5.js.map +1 -0
  110. package/dist/{chunk-XFND63OB.js → chunk-TFWJXWL6.js} +2 -2
  111. package/dist/{chunk-SVJ5JVZO.js → chunk-TXKQBQYW.js} +3 -3
  112. package/dist/{chunk-WQFHHJWK.js → chunk-UALXWWGN.js} +2 -2
  113. package/dist/{chunk-3PVKV3XL.js → chunk-UNOAVHV4.js} +2 -2
  114. package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
  115. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  116. package/dist/chunk-UWMZT2TY.js.map +1 -0
  117. package/dist/{chunk-OD7ZXCUF.js → chunk-UXGXJUZ5.js} +7 -7
  118. package/dist/{chunk-NH37SP6V.js → chunk-UZVQFGAY.js} +81 -78
  119. package/dist/chunk-UZVQFGAY.js.map +1 -0
  120. package/dist/{chunk-2BY56HE6.js → chunk-VCOMACR6.js} +35 -35
  121. package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
  122. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  123. package/dist/{chunk-C3HRYVGD.js → chunk-WV4UYDP6.js} +2 -2
  124. package/dist/{chunk-E3DL3CAL.js → chunk-WXJC2L66.js} +2 -2
  125. package/dist/chunk-X2KO4FK5.js +40 -0
  126. package/dist/chunk-X2KO4FK5.js.map +1 -0
  127. package/dist/{chunk-O7GNSHFF.js → chunk-XC3H7KON.js} +2 -2
  128. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  129. package/dist/{chunk-NUK5XU3P.js → chunk-XTXWR6KQ.js} +25 -10
  130. package/dist/chunk-XTXWR6KQ.js.map +1 -0
  131. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  132. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  133. package/dist/{chunk-QFTNWDJ6.js → chunk-YECEEJUU.js} +2 -2
  134. package/dist/{chunk-BMG3SAXA.js → chunk-Z5277JWF.js} +3 -3
  135. package/dist/{chunk-SW3WHVKN.js → chunk-ZFNXJEUG.js} +2 -2
  136. package/dist/{chunk-VY6BD5KB.js → chunk-ZMO2S5RT.js} +12 -10
  137. package/dist/chunk-ZMO2S5RT.js.map +1 -0
  138. package/dist/{chunk-KMTMMBEB.js → chunk-ZNRTHYYW.js} +6 -6
  139. package/dist/{chunk-VCBS5GRQ.js → chunk-ZSMA67FV.js} +2 -2
  140. package/dist/cli.js +80 -79
  141. package/dist/coding/export-okf-codegraph.js +9 -9
  142. package/dist/compounding/engine.js +11 -11
  143. package/dist/config.js +14 -14
  144. package/dist/connectors/codex-materialize-runner.js +10 -10
  145. package/dist/connectors/index.js +10 -10
  146. package/dist/consolidation-provenance-check.js +4 -4
  147. package/dist/consolidation-undo.js +2 -2
  148. package/dist/convergence-refresh.js +12 -12
  149. package/dist/corpus-watermark.js +12 -12
  150. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  151. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  152. package/dist/dashboard-runtime.js +2 -2
  153. package/dist/deep-recall-http-glue.js +7 -7
  154. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-ZBNRGUNV.js} +3 -3
  155. package/dist/entity-canonical-id-migration-ZBNRGUNV.js.map +1 -0
  156. package/dist/entity-retrieval.js +11 -11
  157. package/dist/explicit-capture.js +2 -2
  158. package/dist/external-wiki-access.js +42 -41
  159. package/dist/extraction.js +16 -16
  160. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  161. package/dist/harmonic-retrieval.js +10 -10
  162. package/dist/index.d.ts +1 -0
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js +130 -122
  165. package/dist/lcm/engine.js +2 -2
  166. package/dist/lcm/index.js +5 -5
  167. package/dist/local-llm.js +2 -2
  168. package/dist/location/index.js +15 -15
  169. package/dist/location/tagging.d.ts +7 -5
  170. package/dist/location/tagging.d.ts.map +1 -1
  171. package/dist/maintenance/memory-governance.js +10 -10
  172. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  173. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
  174. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  175. package/dist/memory-browse-operations.js +42 -41
  176. package/dist/namespaces/migrate.js +11 -11
  177. package/dist/namespaces/storage.js +10 -10
  178. package/dist/offline-sync-file-io.d.ts +15 -0
  179. package/dist/offline-sync-file-io.d.ts.map +1 -1
  180. package/dist/offline-sync-file-io.js +9 -1
  181. package/dist/offline-sync-snapshot-stream.js +4 -4
  182. package/dist/offline-sync-upload-staging.js +2 -2
  183. package/dist/offline-sync.d.ts.map +1 -1
  184. package/dist/offline-sync.js +3 -3
  185. package/dist/operator-doctor-corpus.js +13 -13
  186. package/dist/operator-toolkit.js +24 -24
  187. package/dist/orchestration/maintenance.js +23 -23
  188. package/dist/orchestrator.d.ts +1 -0
  189. package/dist/orchestrator.d.ts.map +1 -1
  190. package/dist/orchestrator.js +109 -108
  191. package/dist/page-versioning.js +2 -2
  192. package/dist/recall-navigation-operations.js +42 -41
  193. package/dist/recall-navigation.js +41 -40
  194. package/dist/resume-bundles.js +15 -15
  195. package/dist/semantic-consolidation.js +15 -15
  196. package/dist/semantic-rule-promotion.js +10 -10
  197. package/dist/semantic-rule-verifier.js +10 -10
  198. package/dist/source-agent-qualifier.js +13 -13
  199. package/dist/standup/build.js +10 -10
  200. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  201. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  202. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  203. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  204. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  205. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  206. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  207. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  208. package/dist/storage/entity-store.d.ts +1 -8
  209. package/dist/storage/entity-store.d.ts.map +1 -1
  210. package/dist/storage/memory-read-store.d.ts +3 -2
  211. package/dist/storage/memory-read-store.d.ts.map +1 -1
  212. package/dist/storage/windowed-memory-read.d.ts +6 -0
  213. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  214. package/dist/storage.d.ts +2 -0
  215. package/dist/storage.d.ts.map +1 -1
  216. package/dist/storage.js +9 -9
  217. package/dist/summarizer.js +3 -3
  218. package/dist/support-passport/index.js +44 -43
  219. package/dist/transfer/capsule-export.js +2 -2
  220. package/dist/transfer/capsule-import.js +4 -4
  221. package/dist/transfer/export-okf.js +10 -10
  222. package/dist/verified-recall.js +10 -10
  223. package/dist/who-knows-cli.js +11 -11
  224. package/dist/who-knows.js +10 -10
  225. package/package.json +2 -2
  226. package/src/access-http-briefing.ts +4 -0
  227. package/src/access-http.test.ts +40 -11
  228. package/src/access-http.ts +6 -0
  229. package/src/access-observe-write-category.ts +1 -1
  230. package/src/access-observe-write-surface.ts +24 -19
  231. package/src/access-operations-batch.ts +18 -2
  232. package/src/access-service.ts +5 -0
  233. package/src/briefing-abort.test.ts +173 -0
  234. package/src/briefing-deadline.test.ts +84 -0
  235. package/src/briefing-deadline.ts +33 -0
  236. package/src/briefing-window-abort.test.ts +47 -0
  237. package/src/briefing-window.ts +33 -4
  238. package/src/briefing.ts +23 -16
  239. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  240. package/src/correction/passive-correction-notifications.ts +4 -0
  241. package/src/entity-lazy-compatibility.test.ts +229 -0
  242. package/src/entity-migration-completion-notification.test.ts +148 -0
  243. package/src/index.ts +1 -1
  244. package/src/location/tagging.ts +14 -12
  245. package/src/offline-sync-file-io.ts +61 -2
  246. package/src/offline-sync.test.ts +313 -20
  247. package/src/offline-sync.ts +43 -21
  248. package/src/orchestrator-init.test.ts +49 -1
  249. package/src/orchestrator.ts +9 -8
  250. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  251. package/src/storage/entity-canonical-id-migration.ts +17 -8
  252. package/src/storage/entity-canonical-id-references.ts +24 -0
  253. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  254. package/src/storage/entity-migration-completion-cache.ts +153 -0
  255. package/src/storage/entity-store.ts +58 -86
  256. package/src/storage/memory-read-store.ts +9 -3
  257. package/src/storage/windowed-memory-read.ts +8 -0
  258. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  259. package/src/storage.ts +7 -2
  260. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  261. package/dist/chunk-3NYIXVCT.js.map +0 -1
  262. package/dist/chunk-6W3QH3QW.js.map +0 -1
  263. package/dist/chunk-7XFQSQYS.js.map +0 -1
  264. package/dist/chunk-FRXS6SC7.js.map +0 -1
  265. package/dist/chunk-LWRIGKFM.js.map +0 -1
  266. package/dist/chunk-N6EM7ESC.js.map +0 -1
  267. package/dist/chunk-NH37SP6V.js.map +0 -1
  268. package/dist/chunk-NUK5XU3P.js.map +0 -1
  269. package/dist/chunk-O4CA53UY.js.map +0 -1
  270. package/dist/chunk-RZDDNH63.js.map +0 -1
  271. package/dist/chunk-SA6P2KS3.js.map +0 -1
  272. package/dist/chunk-SUKPM6DV.js.map +0 -1
  273. package/dist/chunk-VY6BD5KB.js.map +0 -1
  274. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  275. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
  276. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  277. /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
  278. /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
  279. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  280. /package/dist/{chunk-PBWJVRKE.js.map → chunk-5FUDSK6Z.js.map} +0 -0
  281. /package/dist/{chunk-V4GPFCJ7.js.map → chunk-5G53JSRO.js.map} +0 -0
  282. /package/dist/{chunk-WJCAAVZX.js.map → chunk-5X2H3X4J.js.map} +0 -0
  283. /package/dist/{chunk-CPBYKOTC.js.map → chunk-6ZQX3R66.js.map} +0 -0
  284. /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
  285. /package/dist/{chunk-XSJL62LE.js.map → chunk-7J6A5MV2.js.map} +0 -0
  286. /package/dist/{chunk-A3WXV7YU.js.map → chunk-7SAE6T36.js.map} +0 -0
  287. /package/dist/{chunk-BWF3KNSQ.js.map → chunk-7YVER7ZO.js.map} +0 -0
  288. /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
  289. /package/dist/{chunk-6TIVVOVU.js.map → chunk-BNJAVRNJ.js.map} +0 -0
  290. /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
  291. /package/dist/{chunk-FOKTW6GY.js.map → chunk-EWBXMGXR.js.map} +0 -0
  292. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  293. /package/dist/{chunk-E47UQBYW.js.map → chunk-GXVXBYRA.js.map} +0 -0
  294. /package/dist/{chunk-AGCEJDG2.js.map → chunk-HPNXS6W6.js.map} +0 -0
  295. /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
  296. /package/dist/{chunk-HI53EK3Y.js.map → chunk-IUYXCTA7.js.map} +0 -0
  297. /package/dist/{chunk-VGRBKEBH.js.map → chunk-JBWD3J5U.js.map} +0 -0
  298. /package/dist/{chunk-YB3WN6XA.js.map → chunk-K5QQXVG6.js.map} +0 -0
  299. /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
  300. /package/dist/{chunk-HDT3DMUF.js.map → chunk-KW5YZELC.js.map} +0 -0
  301. /package/dist/{chunk-DM6SOROF.js.map → chunk-LIBVANOF.js.map} +0 -0
  302. /package/dist/{chunk-VKUMKVEU.js.map → chunk-LMMBV7QC.js.map} +0 -0
  303. /package/dist/{chunk-NMOEOQV4.js.map → chunk-M2DOJBH7.js.map} +0 -0
  304. /package/dist/{chunk-3INLTKTW.js.map → chunk-M4HRYLOA.js.map} +0 -0
  305. /package/dist/{chunk-YY6PWFUC.js.map → chunk-M6TP6UYD.js.map} +0 -0
  306. /package/dist/{chunk-BJTFLP3J.js.map → chunk-MLNXATFG.js.map} +0 -0
  307. /package/dist/{chunk-C7WDWD35.js.map → chunk-MWURUSRE.js.map} +0 -0
  308. /package/dist/{chunk-WIBWONL4.js.map → chunk-N74WJ7B2.js.map} +0 -0
  309. /package/dist/{chunk-LCHFINL4.js.map → chunk-N7FNCE7F.js.map} +0 -0
  310. /package/dist/{chunk-QZK6HG4J.js.map → chunk-NPLM7AFU.js.map} +0 -0
  311. /package/dist/{chunk-CRJX4OMN.js.map → chunk-NSP5PIQH.js.map} +0 -0
  312. /package/dist/{chunk-X52SXKVC.js.map → chunk-P7OVFGMU.js.map} +0 -0
  313. /package/dist/{chunk-2J6BIOVN.js.map → chunk-PJCVI44H.js.map} +0 -0
  314. /package/dist/{chunk-Q6PYCPHO.js.map → chunk-Q3PNQYAC.js.map} +0 -0
  315. /package/dist/{chunk-D76AHITY.js.map → chunk-QS765SBL.js.map} +0 -0
  316. /package/dist/{chunk-KNCR7APE.js.map → chunk-QTDZ2NYC.js.map} +0 -0
  317. /package/dist/{chunk-HCPVL6SF.js.map → chunk-SJTSWG2Z.js.map} +0 -0
  318. /package/dist/{chunk-XFND63OB.js.map → chunk-TFWJXWL6.js.map} +0 -0
  319. /package/dist/{chunk-SVJ5JVZO.js.map → chunk-TXKQBQYW.js.map} +0 -0
  320. /package/dist/{chunk-WQFHHJWK.js.map → chunk-UALXWWGN.js.map} +0 -0
  321. /package/dist/{chunk-3PVKV3XL.js.map → chunk-UNOAVHV4.js.map} +0 -0
  322. /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
  323. /package/dist/{chunk-OD7ZXCUF.js.map → chunk-UXGXJUZ5.js.map} +0 -0
  324. /package/dist/{chunk-2BY56HE6.js.map → chunk-VCOMACR6.js.map} +0 -0
  325. /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
  326. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  327. /package/dist/{chunk-C3HRYVGD.js.map → chunk-WV4UYDP6.js.map} +0 -0
  328. /package/dist/{chunk-E3DL3CAL.js.map → chunk-WXJC2L66.js.map} +0 -0
  329. /package/dist/{chunk-O7GNSHFF.js.map → chunk-XC3H7KON.js.map} +0 -0
  330. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  331. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  332. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  333. /package/dist/{chunk-QFTNWDJ6.js.map → chunk-YECEEJUU.js.map} +0 -0
  334. /package/dist/{chunk-BMG3SAXA.js.map → chunk-Z5277JWF.js.map} +0 -0
  335. /package/dist/{chunk-SW3WHVKN.js.map → chunk-ZFNXJEUG.js.map} +0 -0
  336. /package/dist/{chunk-KMTMMBEB.js.map → chunk-ZNRTHYYW.js.map} +0 -0
  337. /package/dist/{chunk-VCBS5GRQ.js.map → chunk-ZSMA67FV.js.map} +0 -0
  338. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
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,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();
@@ -0,0 +1,229 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { StorageManager, parseEntityFile } from "./storage.js";
7
+ import { sleep } from "./utils/sleep.js";
8
+
9
+ async function legacyStore() {
10
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-lazy-compat-"));
11
+ const seed = new StorageManager(dir);
12
+ const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
13
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
14
+ const journal = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
15
+ await rm(journal, { force: true });
16
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
17
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
18
+ const storage = new StorageManager(dir);
19
+ await storage.ensureDirectories({ waitForEntityMigration: false });
20
+ return { dir, id, journal, lock, storage };
21
+ }
22
+
23
+ test("canonical reads find a legacy file without a journal or migration", async () => {
24
+ const fixture = await legacyStore();
25
+ try {
26
+ assert.match(await fixture.storage.readEntity(fixture.id), /Keeps a synthetic task/);
27
+ await assert.rejects(() => readFile(fixture.journal), { code: "ENOENT" });
28
+ assert.ok(await readFile(fixture.lock));
29
+ } finally {
30
+ StorageManager.clearAllStaticCaches();
31
+ await rm(fixture.dir, { recursive: true, force: true });
32
+ }
33
+ });
34
+
35
+ test("ambiguous legacy canonical matches are refused rather than picking a claimant", async () => {
36
+ const fixture = await legacyStore();
37
+ try {
38
+ const raw = await readFile(path.join(fixture.dir, "entities", "legacy-person.md"));
39
+ await writeFile(path.join(fixture.dir, "entities", "other-legacy-person.md"), raw);
40
+ await assert.rejects(() => fixture.storage.readEntity(fixture.id), /ambiguous/i);
41
+ } finally {
42
+ StorageManager.clearAllStaticCaches();
43
+ await rm(fixture.dir, { recursive: true, force: true });
44
+ }
45
+ });
46
+
47
+ test("completed migration skips corpus reads on repeated misses in a fresh read-only store", async () => {
48
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-miss-"));
49
+ try {
50
+ const seed = new StorageManager(dir);
51
+ await seed.writeEntity("Existing Person", "person", ["Synthetic fact."]);
52
+ await seed.ensureDirectories();
53
+ const reader = new StorageManager(dir);
54
+ await reader.ensureDirectories({ waitForEntityMigration: false });
55
+ // Bracket access is TypeScript's typed escape hatch for the private reader.
56
+ const originalRead = reader["readStorageSecureFile"].bind(reader);
57
+ const reads: string[] = [];
58
+ reader["readStorageSecureFile"] = async (filePath: string) => {
59
+ reads.push(path.basename(filePath));
60
+ return originalRead(filePath);
61
+ };
62
+ assert.equal(await reader.readEntity("person-missing"), "");
63
+ assert.equal(await reader.readEntity("person-missing"), "");
64
+ assert.deepEqual(reads, ["person-missing.md", "person-missing.md"], "completed-store misses must not scan entity contents");
65
+ } finally {
66
+ StorageManager.clearAllStaticCaches();
67
+ await rm(dir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ test("an external entities change invalidates the amortized completion verdict on the same reader", async () => {
72
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-cache-"));
73
+ try {
74
+ const seed = new StorageManager(dir);
75
+ const id = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
76
+ await seed.ensureDirectories();
77
+ const reader = new StorageManager(dir);
78
+ await reader.ensureDirectories({ waitForEntityMigration: false });
79
+ const canonical = reader.normalizeEntityName("Café", "project");
80
+ // First miss computes and caches the completion verdict.
81
+ assert.equal(await reader.readEntity("project-missing"), "");
82
+ // An external rename changes the entities directory; the cached verdict
83
+ // must not keep hiding the now-legacy file from the same reader.
84
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-cafe.md"));
85
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
86
+ } finally {
87
+ StorageManager.clearAllStaticCaches();
88
+ await rm(dir, { recursive: true, force: true });
89
+ }
90
+ });
91
+
92
+ test("an external aliases change invalidates the amortized completion verdict on the same reader", async () => {
93
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-completed-alias-cache-"));
94
+ try {
95
+ const seed = new StorageManager(dir);
96
+ const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
97
+ await seed.ensureDirectories();
98
+ const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
99
+ const journal = await readFile(journalPath, "utf8");
100
+ assert.equal(JSON.parse(journal).complete, true);
101
+
102
+ const reader = new StorageManager(dir);
103
+ await reader.ensureDirectories({ waitForEntityMigration: false });
104
+ // Warm amortized completion (and any same-reader completion memo) on a miss.
105
+ assert.equal(await reader.readEntity("project-missing"), "");
106
+
107
+ // Block migration repair so fingerprint drift stays visible to reads.
108
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
109
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
110
+
111
+ // External aliases.json is a getFingerprint input. It must invalidate a
112
+ // warm completed verdict without entities membership or mutation-version change.
113
+ // The entity file stays at previousId; only the alias-derived canonical id moves.
114
+ await mkdir(path.join(dir, "config"), { recursive: true });
115
+ await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
116
+ await reader.loadAliases();
117
+ const canonical = reader.normalizeEntityName("Café", "project");
118
+ assert.notEqual(canonical, previousId);
119
+
120
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
121
+ assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
122
+ assert.ok(await readFile(lock));
123
+ } finally {
124
+ StorageManager.clearAllStaticCaches();
125
+ await rm(dir, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+
130
+ for (const change of ["aliases", "raw legacy write"]) {
131
+ test(`stale completed journals do not hide canonical reads after ${change}`, async () => {
132
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-completion-"));
133
+ try {
134
+ const seed = new StorageManager(dir);
135
+ const previousId = await seed.writeEntity("Café", "project", ["Synthetic legacy fact."]);
136
+ await seed.ensureDirectories();
137
+ const journalPath = path.join(dir, "state", "entity-canonical-id-migration-v1.json");
138
+ const journal = await readFile(journalPath, "utf8");
139
+ assert.equal(JSON.parse(journal).complete, true);
140
+ if (change === "aliases") {
141
+ await mkdir(path.join(dir, "config"), { recursive: true });
142
+ await writeFile(path.join(dir, "config", "aliases.json"), JSON.stringify({ café: "coffee" }));
143
+ } else {
144
+ await rename(path.join(dir, "entities", `${previousId}.md`), path.join(dir, "entities", "legacy-cafe.md"));
145
+ await writeFile(path.join(dir, "state", "entity-canonical-id-reconcile.pending"), "synthetic raw writer\n");
146
+ }
147
+ const lock = path.join(dir, "state", "entity-canonical-id-migration.lock");
148
+ await writeFile(lock, `${process.pid} test-holder ${new Date().toISOString()}\n`);
149
+ const reader = new StorageManager(dir);
150
+ await reader.ensureDirectories({ waitForEntityMigration: false });
151
+ const canonical = reader.normalizeEntityName("Café", "project");
152
+ if (change === "aliases") assert.notEqual(canonical, previousId);
153
+ assert.match(await reader.readEntity(canonical), /Synthetic legacy fact/);
154
+ assert.equal(await readFile(journalPath, "utf8"), journal, "read compatibility must not replay migration");
155
+ assert.ok(await readFile(lock));
156
+ } finally {
157
+ StorageManager.clearAllStaticCaches();
158
+ await rm(dir, { recursive: true, force: true });
159
+ }
160
+ });
161
+ }
162
+
163
+ const mutations = [
164
+ {
165
+ name: "relationship",
166
+ run: (s: StorageManager, id: string) => s.addEntityRelationship(id, { target: "person-peer", label: "knows" }),
167
+ check: (raw: string) => assert.ok(parseEntityFile(raw).relationships.some((r) => r.label === "knows")),
168
+ },
169
+ {
170
+ name: "activity",
171
+ run: (s: StorageManager, id: string) => s.addEntityActivity(id, { date: "2026-01-01", note: "Synthetic activity" }, 10),
172
+ check: (raw: string) => assert.ok(parseEntityFile(raw).activity.some((a) => a.note === "Synthetic activity")),
173
+ },
174
+ {
175
+ name: "alias",
176
+ run: (s: StorageManager, id: string) => s.addEntityAlias(id, "synthetic-alias"),
177
+ check: (raw: string) => assert.ok(parseEntityFile(raw).aliases.includes("synthetic-alias")),
178
+ },
179
+ {
180
+ name: "synthesis",
181
+ run: (s: StorageManager, id: string) => s.updateEntitySynthesis(id, "Synthetic synthesis"),
182
+ check: (raw: string) => assert.equal(parseEntityFile(raw).synthesis, "Synthetic synthesis"),
183
+ },
184
+ {
185
+ name: "fragment merge",
186
+ run: (s: StorageManager) => s.mergeFragmentedEntities(),
187
+ check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
188
+ },
189
+ {
190
+ name: "timeline migration",
191
+ run: (s: StorageManager) => s.migrateEntityFilesToCompiledTruthTimeline(),
192
+ check: (raw: string) => assert.match(raw, /Keeps a synthetic task/),
193
+ },
194
+ ];
195
+
196
+ for (const caller of ["canonical", "legacy"]) {
197
+ for (const mutation of mutations) {
198
+ test(`${mutation.name} awaits migration for a ${caller} caller`, async () => {
199
+ const fixture = await legacyStore();
200
+ let settled = false;
201
+ let failure: Error | undefined;
202
+ const operation = mutation.run(fixture.storage, caller === "legacy" ? "legacy-person" : fixture.id).then(
203
+ () => { settled = true; },
204
+ (error: Error) => { failure = error; settled = true; },
205
+ );
206
+ let probeFailure: unknown;
207
+ try {
208
+ await sleep(100);
209
+ assert.equal(settled, false, "a public entity mutation must wait behind the migration lock");
210
+ } catch (error) {
211
+ probeFailure = error;
212
+ } finally {
213
+ await rm(fixture.lock, { force: true });
214
+ }
215
+ try {
216
+ await operation;
217
+ if (probeFailure) throw probeFailure;
218
+ assert.equal(failure, undefined);
219
+ const raw = await readFile(path.join(fixture.dir, "entities", `${fixture.id}.md`), "utf8");
220
+ mutation.check(raw);
221
+ const journal = JSON.parse(await readFile(fixture.journal, "utf8"));
222
+ assert.equal(journal.complete, true);
223
+ } finally {
224
+ StorageManager.clearAllStaticCaches();
225
+ await rm(fixture.dir, { recursive: true, force: true });
226
+ }
227
+ });
228
+ }
229
+ }
@@ -0,0 +1,148 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, rename, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { parseConfig } from "./config.js";
8
+ import { Orchestrator } from "./orchestrator.js";
9
+ import { StorageManager } from "./storage.js";
10
+
11
+ // #3240 follow-up: the host's onEntityMigrationComplete slot must fire once
12
+ // per migration pass that rewrote entities (never on no-op replays or ensure() no-ops), fail open on a
13
+ // throwing hook, reach router-created storages independent of auto-embed, and
14
+ // be inert after Orchestrator.destroy(). No direct QMD calls; the re-arm is
15
+ // observed through a requestQmdMaintenance spy.
16
+
17
+ async function legacyEntityStore(prefix: string): Promise<string> {
18
+ const dir = await mkdtemp(path.join(os.tmpdir(), prefix));
19
+ const seed = new StorageManager(dir);
20
+ const id = await seed.writeEntity("Legacy Person", "person", ["Keeps a synthetic task."]);
21
+ await rename(path.join(dir, "entities", `${id}.md`), path.join(dir, "entities", "legacy-person.md"));
22
+ await rm(path.join(dir, "state", "entity-canonical-id-migration-v1.json"), { force: true });
23
+ return dir;
24
+ }
25
+
26
+ test("real storage fires the completion slot only for a pass that rewrote entities, and fails open on a throwing hook", async () => {
27
+ const dir = await legacyEntityStore("remnic-migration-complete-");
28
+ const emptyDir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-noop-"));
29
+ const throwingDir = await legacyEntityStore("remnic-migration-throwing-");
30
+ try {
31
+ const storage = new StorageManager(dir);
32
+ let completions = 0;
33
+ storage.onEntityMigrationComplete = () => {
34
+ completions += 1;
35
+ };
36
+ await storage.ensureDirectories();
37
+ assert.equal(completions, 1, "a migration pass that renamed a legacy entity must fire the completion slot");
38
+ await storage.ensureDirectories();
39
+ assert.equal(completions, 1, "a second ensureDirectories must not re-fire the slot");
40
+
41
+ const noop = new StorageManager(emptyDir);
42
+ let noopCompletions = 0;
43
+ noop.onEntityMigrationComplete = () => {
44
+ noopCompletions += 1;
45
+ };
46
+ await noop.ensureDirectories();
47
+ assert.equal(noopCompletions, 0, "a no-op replay must not ask search maintenance to run");
48
+
49
+ const throwing = new StorageManager(throwingDir);
50
+ let hookCalls = 0;
51
+ throwing.onEntityMigrationComplete = () => {
52
+ hookCalls += 1;
53
+ throw new Error("consumer hook exploded");
54
+ };
55
+ await throwing.ensureDirectories();
56
+ assert.equal(hookCalls, 1, "the throwing hook must still be invoked");
57
+ } finally {
58
+ StorageManager.clearAllStaticCaches();
59
+ await rm(dir, { recursive: true, force: true });
60
+ await rm(emptyDir, { recursive: true, force: true });
61
+ await rm(throwingDir, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("orchestrator wires completion with auto-embed off and blocks it from teardown admission", async () => {
66
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-migration-complete-orch-"));
67
+ const orchestrator = new Orchestrator(
68
+ parseConfig({
69
+ memoryDir: dir,
70
+ workspaceDir: path.join(dir, "workspace"),
71
+ qmdEnabled: false,
72
+ qmdAutoEmbedEnabled: false,
73
+ namespacesEnabled: true,
74
+ namespaceCatalogEnabled: true,
75
+ defaultNamespace: "default",
76
+ sharedNamespace: "shared",
77
+ namespacePolicies: [],
78
+ embeddingFallbackEnabled: false,
79
+ identityContinuityEnabled: false,
80
+ transcriptEnabled: false,
81
+ hourlySummariesEnabled: false,
82
+ compoundingEnabled: false,
83
+ knowledgeIndexEnabled: false,
84
+ }),
85
+ );
86
+ // Test-only access to the Orchestrator's private wiring fields: bracket access
87
+ // is TypeScript's typed escape hatch for private members, so no assertion.
88
+ const internals = {
89
+ get storage() { return orchestrator["storage"]; },
90
+ get storageRouter() { return orchestrator["storageRouter"]; },
91
+ get maintenanceScheduler() { return orchestrator["maintenanceScheduler"]; },
92
+ set requestQmdMaintenance(fn: () => void) { orchestrator["requestQmdMaintenance"] = fn; },
93
+ };
94
+ try {
95
+ // Wiring must exist independent of auto-embed.
96
+ const onMigrationComplete = internals.storage.onEntityMigrationComplete;
97
+ assert.ok(
98
+ onMigrationComplete instanceof Function,
99
+ "the default storage must carry the migration completion slot without auto-embed",
100
+ );
101
+ const namespaceStorage = await internals.storageRouter.storageFor("proj-a");
102
+ const onNamespaceMigrationComplete = namespaceStorage.onEntityMigrationComplete;
103
+ assert.ok(
104
+ onNamespaceMigrationComplete instanceof Function,
105
+ "a router-created namespace storage must carry the slot without auto-embed",
106
+ );
107
+
108
+ const rearmRequests: string[] = [];
109
+ internals.requestQmdMaintenance = () => {
110
+ rearmRequests.push("rearm");
111
+ };
112
+ assert.ok(onMigrationComplete);
113
+ onMigrationComplete();
114
+ assert.ok(onNamespaceMigrationComplete);
115
+ onNamespaceMigrationComplete();
116
+ assert.deepEqual(rearmRequests, ["rearm", "rearm"], "a completion must re-arm QMD maintenance");
117
+
118
+ const { promise: disposeGate, resolve: releaseDispose } = Promise.withResolvers<void>();
119
+ const { promise: disposing, resolve: signalDispose } = Promise.withResolvers<void>();
120
+ const originalDispose = internals.maintenanceScheduler.dispose.bind(internals.maintenanceScheduler);
121
+ internals.maintenanceScheduler.dispose = async () => {
122
+ signalDispose();
123
+ await disposeGate;
124
+ await originalDispose();
125
+ };
126
+ const teardown = orchestrator.destroy();
127
+ try {
128
+ await disposing;
129
+ onMigrationComplete();
130
+ onNamespaceMigrationComplete();
131
+ assert.deepEqual(rearmRequests, ["rearm", "rearm"], "teardown admission must stop completion re-arms before drains");
132
+ } finally {
133
+ releaseDispose();
134
+ await teardown;
135
+ }
136
+ onMigrationComplete();
137
+ onNamespaceMigrationComplete();
138
+ assert.deepEqual(
139
+ rearmRequests,
140
+ ["rearm", "rearm"],
141
+ "a completion after destroy must not re-arm maintenance",
142
+ );
143
+ } finally {
144
+ await orchestrator.destroy().catch(() => undefined);
145
+ StorageManager.clearAllStaticCaches();
146
+ await rm(dir, { recursive: true, force: true });
147
+ }
148
+ });
package/src/index.ts CHANGED
@@ -657,7 +657,7 @@ export {
657
657
  type ParsedBriefingWindow,
658
658
  type BriefingFormatValue,
659
659
  } from "./briefing.js";
660
-
660
+ export { withBriefingDeadline, BriefingTimeoutError, BRIEFING_DEADLINE_MS } from "./briefing-deadline.js";
661
661
  // ---------------------------------------------------------------------------
662
662
  // Binary lifecycle management (#367)
663
663
  // ---------------------------------------------------------------------------
@@ -21,7 +21,7 @@
21
21
 
22
22
  import type { StorageManager } from "../index.js";
23
23
  import { log } from "../logger.js";
24
- import type { MemoryFile } from "../types.js";
24
+ import type { BriefingResult, MemoryFile } from "../types.js";
25
25
  import type { WearableConversation } from "../wearables/types.js";
26
26
  import { localDayKey, placeDurations } from "./intervals.js";
27
27
  import {
@@ -294,23 +294,25 @@ export async function briefingLocationSection(
294
294
  );
295
295
  }
296
296
 
297
- export async function applyBriefingLocationContext<
298
- T extends { markdown: string; json: Record<string, unknown> },
299
- >(
300
- result: T,
297
+ /**
298
+ * Append the opt-in location section to a briefing. The section is read BEFORE
299
+ * `build` runs: buildBriefing drains correction notifications at most once, so
300
+ * a deadline during this read must not lose them.
301
+ */
302
+ export async function buildWithBriefingLocation<T extends Pick<BriefingResult, "markdown" | "json">>(
301
303
  includeLocation: boolean | undefined,
302
304
  memoryDir: string,
303
305
  windowStartUtc: string,
304
306
  location: LocationConfig,
307
+ build: () => Promise<T>,
305
308
  ): Promise<T> {
306
- if (includeLocation !== true || !locationTaggingEnabled(location)) return result;
307
- const section = await briefingLocationSection(memoryDir, windowStartUtc, location);
309
+ const section =
310
+ includeLocation === true && locationTaggingEnabled(location)
311
+ ? await briefingLocationSection(memoryDir, windowStartUtc, location)
312
+ : null;
313
+ const result = await build();
308
314
  if (!section) return result;
309
- return {
310
- ...result,
311
- markdown: `${result.markdown}\n\n${section}\n`,
312
- json: { ...result.json, locationContext: section },
313
- };
315
+ return { ...result, markdown: `${result.markdown}\n\n${section}\n`, json: { ...result.json, locationContext: section } };
314
316
  }
315
317
 
316
318
  /**