@remnic/core 10.0.49 → 10.0.51

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 (415) hide show
  1. package/dist/access-admin-ops-surface.js +49 -48
  2. package/dist/access-authorization-probe.js +50 -49
  3. package/dist/access-boundary.d.ts +6 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +49 -48
  6. package/dist/access-cli.js +120 -119
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-errors.d.ts.map +1 -1
  9. package/dist/access-errors.js +1 -1
  10. package/dist/access-extraction-force-flush.js +49 -48
  11. package/dist/access-http-briefing.d.ts +2 -0
  12. package/dist/access-http-briefing.d.ts.map +1 -1
  13. package/dist/access-http-briefing.js +50 -49
  14. package/dist/access-http-query.js +51 -50
  15. package/dist/access-http.d.ts.map +1 -1
  16. package/dist/access-http.js +70 -69
  17. package/dist/access-identity-continuity-surface.js +49 -48
  18. package/dist/access-lcm-surface.js +49 -48
  19. package/dist/access-mcp.js +60 -59
  20. package/dist/access-memory-search-fanout.js +2 -2
  21. package/dist/access-namespace-preflight.js +49 -48
  22. package/dist/access-observe-write-category.d.ts +1 -1
  23. package/dist/access-observe-write-category.d.ts.map +1 -1
  24. package/dist/access-observe-write-category.js +10 -10
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +49 -48
  27. package/dist/access-offline-manifest.js +6 -6
  28. package/dist/access-operations-batch.js +50 -49
  29. package/dist/access-operations.js +57 -56
  30. package/dist/access-recall-concurrency.js +49 -48
  31. package/dist/access-recall-diagnostics.js +2 -2
  32. package/dist/access-recall-response.js +49 -48
  33. package/dist/access-recall-surface.js +49 -48
  34. package/dist/access-schema.d.ts +245 -773
  35. package/dist/access-schema.d.ts.map +1 -1
  36. package/dist/access-schema.js +6 -6
  37. package/dist/access-service.d.ts +5 -0
  38. package/dist/access-service.d.ts.map +1 -1
  39. package/dist/access-service.js +49 -48
  40. package/dist/access-surface-catalog.js +51 -50
  41. package/dist/access-surface-catalog.js.map +1 -1
  42. package/dist/access-token-capabilities.js +2 -2
  43. package/dist/active-recall.js +16 -16
  44. package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
  45. package/dist/briefing-deadline.d.ts +7 -0
  46. package/dist/briefing-deadline.d.ts.map +1 -0
  47. package/dist/briefing-deadline.js +13 -0
  48. package/dist/briefing-window.d.ts +12 -4
  49. package/dist/briefing-window.d.ts.map +1 -1
  50. package/dist/briefing-window.js +3 -1
  51. package/dist/briefing.d.ts +2 -0
  52. package/dist/briefing.d.ts.map +1 -1
  53. package/dist/briefing.js +12 -11
  54. package/dist/buffer.js +3 -3
  55. package/dist/calibration.js +2 -2
  56. package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
  57. package/dist/causal-consolidation.js +11 -11
  58. package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
  59. package/dist/{chunk-BWF3KNSQ.js → chunk-2EXF6IIB.js} +2 -2
  60. package/dist/{chunk-AGCEJDG2.js → chunk-2ONKXEC7.js} +3 -3
  61. package/dist/{chunk-3NYIXVCT.js → chunk-2S6JADBB.js} +2 -2
  62. package/dist/chunk-2S6JADBB.js.map +1 -0
  63. package/dist/{chunk-ZSEFGDCI.js → chunk-2TGIKMMZ.js} +2 -2
  64. package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
  65. package/dist/{chunk-Q6PYCPHO.js → chunk-44EUCSG4.js} +2 -2
  66. package/dist/{chunk-2J6BIOVN.js → chunk-4FM35YIA.js} +3 -3
  67. package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
  68. package/dist/{chunk-YY6PWFUC.js → chunk-6ZAU7LAR.js} +3 -3
  69. package/dist/{chunk-HCPVL6SF.js → chunk-72CBJ5VP.js} +3 -3
  70. package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
  71. package/dist/{chunk-N6EM7ESC.js → chunk-75BK4XGV.js} +27 -19
  72. package/dist/chunk-75BK4XGV.js.map +1 -0
  73. package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
  74. package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
  75. package/dist/chunk-A5L5KGDI.js.map +1 -0
  76. package/dist/{chunk-RZDDNH63.js → chunk-A6JDT6QF.js} +27 -15
  77. package/dist/chunk-A6JDT6QF.js.map +1 -0
  78. package/dist/{chunk-XFND63OB.js → chunk-AHGOOZUC.js} +2 -2
  79. package/dist/{chunk-A7AISUJI.js → chunk-AJG2TAMR.js} +2 -2
  80. package/dist/{chunk-2BY56HE6.js → chunk-AZLD4CKG.js} +36 -36
  81. package/dist/{chunk-E47UQBYW.js → chunk-B35EQQ7L.js} +3 -3
  82. package/dist/{chunk-E6UNM67F.js → chunk-B6SPPYGJ.js} +2 -2
  83. package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
  84. package/dist/{chunk-SVJ5JVZO.js → chunk-CK6GCGMX.js} +4 -4
  85. package/dist/{chunk-QZK6HG4J.js → chunk-DF6ZF2BN.js} +3 -3
  86. package/dist/{chunk-CPBYKOTC.js → chunk-DJFULNXK.js} +4 -4
  87. package/dist/{chunk-VGRBKEBH.js → chunk-DQOGZZJA.js} +3 -3
  88. package/dist/{chunk-YB3WN6XA.js → chunk-EC2KVA27.js} +12 -12
  89. package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
  90. package/dist/chunk-EKUI7I2X.js.map +1 -0
  91. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  92. package/dist/{chunk-WIBWONL4.js → chunk-EZS6QJNC.js} +2 -2
  93. package/dist/{chunk-C7WDWD35.js → chunk-FNNGWN3O.js} +5 -5
  94. package/dist/{chunk-PBWJVRKE.js → chunk-FT75AQ74.js} +3 -3
  95. package/dist/{chunk-VY6BD5KB.js → chunk-G6A3SGCE.js} +14 -12
  96. package/dist/chunk-G6A3SGCE.js.map +1 -0
  97. package/dist/{chunk-O4CA53UY.js → chunk-GBGU5OZP.js} +111 -104
  98. package/dist/chunk-GBGU5OZP.js.map +1 -0
  99. package/dist/{chunk-WJCAAVZX.js → chunk-GE3ZX6YK.js} +2 -2
  100. package/dist/{chunk-3PVKV3XL.js → chunk-HCIRR3BA.js} +3 -3
  101. package/dist/{chunk-BJTFLP3J.js → chunk-HEF4BRMT.js} +3 -3
  102. package/dist/{chunk-BMG3SAXA.js → chunk-HQUYENKK.js} +9 -12
  103. package/dist/chunk-HQUYENKK.js.map +1 -0
  104. package/dist/{chunk-6UDLN4IA.js → chunk-I6ORDT54.js} +2 -2
  105. package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
  106. package/dist/{chunk-3INLTKTW.js → chunk-IUQXRRJB.js} +4 -4
  107. package/dist/{chunk-6W3QH3QW.js → chunk-JIJAWTRK.js} +388 -292
  108. package/dist/chunk-JIJAWTRK.js.map +1 -0
  109. package/dist/{chunk-4KS4IHKH.js → chunk-JX3E4XCW.js} +2 -2
  110. package/dist/{chunk-SW3WHVKN.js → chunk-KAJYSO3U.js} +2 -2
  111. package/dist/{chunk-D76AHITY.js → chunk-KETS5A62.js} +2 -2
  112. package/dist/{chunk-LCHFINL4.js → chunk-KIWO4327.js} +2 -2
  113. package/dist/{chunk-FC3ZFEXF.js → chunk-KJ3MDX4V.js} +2 -2
  114. package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
  115. package/dist/{chunk-6TIVVOVU.js → chunk-L3EFYRYK.js} +9 -9
  116. package/dist/{chunk-3MUKQQ7A.js → chunk-LGEH473S.js} +24 -29
  117. package/dist/chunk-LGEH473S.js.map +1 -0
  118. package/dist/{chunk-X52SXKVC.js → chunk-LMB47UM6.js} +2 -2
  119. package/dist/{chunk-A3WXV7YU.js → chunk-LWMLBVGB.js} +2 -2
  120. package/dist/{chunk-KMTMMBEB.js → chunk-MC7PXJDP.js} +7 -7
  121. package/dist/{chunk-NMOEOQV4.js → chunk-MUNYUSBU.js} +2 -2
  122. package/dist/{chunk-VKUMKVEU.js → chunk-OCRLJJOV.js} +5 -5
  123. package/dist/{chunk-XSJL62LE.js → chunk-ON77FQXH.js} +2 -2
  124. package/dist/{chunk-C3HRYVGD.js → chunk-POJRFSJM.js} +3 -3
  125. package/dist/{chunk-LKRXGOQW.js → chunk-QA3Y2O6H.js} +2 -2
  126. package/dist/{chunk-23ZLN5X6.js → chunk-QT5MGENX.js} +14 -6
  127. package/dist/chunk-QT5MGENX.js.map +1 -0
  128. package/dist/{chunk-QFTNWDJ6.js → chunk-RA7YRL6H.js} +2 -2
  129. package/dist/{chunk-WQFHHJWK.js → chunk-RJDYDF5K.js} +4 -4
  130. package/dist/{chunk-E3DL3CAL.js → chunk-ROAKLYOL.js} +2 -2
  131. package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
  132. package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
  133. package/dist/{chunk-NWIT3U2N.js → chunk-S5CQI2KF.js} +2 -2
  134. package/dist/{chunk-P6P35FY4.js → chunk-SDESKFKT.js} +2 -2
  135. package/dist/{chunk-OD7ZXCUF.js → chunk-SFRBUBUZ.js} +10 -10
  136. package/dist/{chunk-7XFQSQYS.js → chunk-SHNM7RIT.js} +5 -4
  137. package/dist/chunk-SHNM7RIT.js.map +1 -0
  138. package/dist/{chunk-NH37SP6V.js → chunk-SJD334AG.js} +81 -78
  139. package/dist/chunk-SJD334AG.js.map +1 -0
  140. package/dist/{chunk-O7GNSHFF.js → chunk-SPTPBKZX.js} +2 -2
  141. package/dist/{chunk-HI53EK3Y.js → chunk-TC55M5PO.js} +2 -2
  142. package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
  143. package/dist/chunk-TEH5MDX5.js.map +1 -0
  144. package/dist/{chunk-FOKTW6GY.js → chunk-UCLJTGP3.js} +2 -2
  145. package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
  146. package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
  147. package/dist/chunk-UWMZT2TY.js.map +1 -0
  148. package/dist/{chunk-754XN6LX.js → chunk-V27FK6D6.js} +2 -2
  149. package/dist/{chunk-DM6SOROF.js → chunk-V6Z4NAJZ.js} +2 -2
  150. package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
  151. package/dist/{chunk-NUK5XU3P.js → chunk-VUMWVYMR.js} +28 -13
  152. package/dist/chunk-VUMWVYMR.js.map +1 -0
  153. package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
  154. package/dist/{chunk-VCBS5GRQ.js → chunk-WY66J4PI.js} +2 -2
  155. package/dist/chunk-X2KO4FK5.js +40 -0
  156. package/dist/chunk-X2KO4FK5.js.map +1 -0
  157. package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
  158. package/dist/{chunk-V4GPFCJ7.js → chunk-XWJUFER3.js} +3 -3
  159. package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
  160. package/dist/{chunk-CRJX4OMN.js → chunk-Y3T5KQCR.js} +2 -2
  161. package/dist/{chunk-KNCR7APE.js → chunk-Y77FXULH.js} +8 -8
  162. package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
  163. package/dist/{chunk-IG747LRN.js → chunk-YO2JEX2Z.js} +2 -2
  164. package/dist/{chunk-HDT3DMUF.js → chunk-ZKK3VMWU.js} +2 -2
  165. package/dist/cli.js +92 -91
  166. package/dist/coding/export-okf-codegraph.js +9 -9
  167. package/dist/compounding/engine.js +11 -11
  168. package/dist/config.js +16 -16
  169. package/dist/connectors/codex-materialize-runner.js +10 -10
  170. package/dist/connectors/index.js +14 -14
  171. package/dist/consolidation-provenance-check.js +4 -4
  172. package/dist/consolidation-undo.js +2 -2
  173. package/dist/convergence-refresh.js +12 -12
  174. package/dist/corpus-watermark.js +14 -14
  175. package/dist/correction/passive-correction-notifications.d.ts +1 -1
  176. package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
  177. package/dist/dashboard-runtime.js +2 -2
  178. package/dist/deep-recall-config.js +2 -2
  179. package/dist/deep-recall-http-glue.js +9 -9
  180. package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-F3JVG6K6.js} +3 -3
  181. package/dist/entity-canonical-id-migration-F3JVG6K6.js.map +1 -0
  182. package/dist/entity-retrieval.js +11 -11
  183. package/dist/explicit-capture.js +2 -2
  184. package/dist/external-wiki-access.js +50 -49
  185. package/dist/extraction-span-wire.d.ts.map +1 -1
  186. package/dist/extraction-span-wire.js +1 -1
  187. package/dist/extraction.js +22 -22
  188. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  189. package/dist/harmonic-retrieval.js +10 -10
  190. package/dist/index.d.ts +1 -0
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +143 -135
  193. package/dist/lcm/engine.js +2 -2
  194. package/dist/lcm/index.js +5 -5
  195. package/dist/local-llm.js +2 -2
  196. package/dist/location/index.js +16 -16
  197. package/dist/location/tagging.d.ts +7 -5
  198. package/dist/location/tagging.d.ts.map +1 -1
  199. package/dist/maintenance/memory-governance.js +10 -10
  200. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  201. package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
  202. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  203. package/dist/memory-browse-http-glue.js +2 -2
  204. package/dist/memory-browse-operations.js +50 -49
  205. package/dist/namespaces/migrate.js +11 -11
  206. package/dist/namespaces/storage.js +10 -10
  207. package/dist/offline-sync-file-io.d.ts +15 -0
  208. package/dist/offline-sync-file-io.d.ts.map +1 -1
  209. package/dist/offline-sync-file-io.js +9 -1
  210. package/dist/offline-sync-impression-drain.js +2 -2
  211. package/dist/offline-sync-snapshot-stream.js +4 -4
  212. package/dist/offline-sync-upload-staging.js +2 -2
  213. package/dist/offline-sync.d.ts.map +1 -1
  214. package/dist/offline-sync.js +3 -3
  215. package/dist/operator-doctor-corpus.js +17 -17
  216. package/dist/operator-doctor-replica.js +4 -4
  217. package/dist/operator-toolkit.js +29 -29
  218. package/dist/orchestration/maintenance.js +25 -25
  219. package/dist/orchestrator.d.ts +1 -0
  220. package/dist/orchestrator.d.ts.map +1 -1
  221. package/dist/orchestrator.js +122 -121
  222. package/dist/page-versioning.js +2 -2
  223. package/dist/recall-navigation-http-glue.js +2 -2
  224. package/dist/recall-navigation-operations.js +50 -49
  225. package/dist/recall-navigation.js +49 -48
  226. package/dist/relay/mission.d.ts +731 -3403
  227. package/dist/relay/mission.d.ts.map +1 -1
  228. package/dist/replica-divergence.js +3 -3
  229. package/dist/resume-bundles.js +17 -17
  230. package/dist/schemas.d.ts +346 -1771
  231. package/dist/schemas.d.ts.map +1 -1
  232. package/dist/semantic-consolidation.js +15 -15
  233. package/dist/semantic-rule-promotion.js +10 -10
  234. package/dist/semantic-rule-verifier.js +10 -10
  235. package/dist/shared-context/manager.d.ts +13 -33
  236. package/dist/shared-context/manager.d.ts.map +1 -1
  237. package/dist/source-agent-qualifier.js +18 -18
  238. package/dist/standup/build.js +11 -11
  239. package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
  240. package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
  241. package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
  242. package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
  243. package/dist/storage/entity-canonical-id-references.d.ts +2 -0
  244. package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
  245. package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
  246. package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
  247. package/dist/storage/entity-store.d.ts +1 -8
  248. package/dist/storage/entity-store.d.ts.map +1 -1
  249. package/dist/storage/memory-read-store.d.ts +3 -2
  250. package/dist/storage/memory-read-store.d.ts.map +1 -1
  251. package/dist/storage/windowed-memory-read.d.ts +6 -0
  252. package/dist/storage/windowed-memory-read.d.ts.map +1 -1
  253. package/dist/storage.d.ts +2 -0
  254. package/dist/storage.d.ts.map +1 -1
  255. package/dist/storage.js +9 -9
  256. package/dist/summarizer.js +3 -3
  257. package/dist/support-passport/access-operations.d.ts +51 -51
  258. package/dist/support-passport/contracts.d.ts +85 -126
  259. package/dist/support-passport/contracts.d.ts.map +1 -1
  260. package/dist/support-passport/generated-batch.d.ts +3 -17
  261. package/dist/support-passport/generated-batch.d.ts.map +1 -1
  262. package/dist/support-passport/grant-contracts.d.ts +75 -381
  263. package/dist/support-passport/grant-contracts.d.ts.map +1 -1
  264. package/dist/support-passport/index.js +52 -51
  265. package/dist/support-passport/model-audit.d.ts +17 -82
  266. package/dist/support-passport/model-audit.d.ts.map +1 -1
  267. package/dist/support-passport/model-bridge.d.ts +12 -51
  268. package/dist/support-passport/model-bridge.d.ts.map +1 -1
  269. package/dist/support-passport/model-contracts.d.ts +53 -192
  270. package/dist/support-passport/model-contracts.d.ts.map +1 -1
  271. package/dist/tokens.js +3 -3
  272. package/dist/transfer/capsule-export.js +2 -2
  273. package/dist/transfer/capsule-import.js +4 -4
  274. package/dist/transfer/export-okf.js +10 -10
  275. package/dist/transfer/types.d.ts +32 -737
  276. package/dist/transfer/types.d.ts.map +1 -1
  277. package/dist/verified-recall.js +10 -10
  278. package/dist/who-knows-cli.js +12 -12
  279. package/dist/who-knows.js +11 -11
  280. package/package.json +3 -4
  281. package/src/access-boundary.test.ts +14 -0
  282. package/src/access-boundary.ts +48 -42
  283. package/src/access-category-alias.test.ts +9 -7
  284. package/src/access-errors.test.ts +21 -9
  285. package/src/access-errors.ts +45 -31
  286. package/src/access-http-briefing.ts +4 -0
  287. package/src/access-http.test.ts +41 -12
  288. package/src/access-http.ts +6 -0
  289. package/src/access-observe-write-category.ts +1 -1
  290. package/src/access-observe-write-surface.ts +24 -19
  291. package/src/access-operations-batch.ts +18 -2
  292. package/src/access-schema.ts +13 -17
  293. package/src/access-service.ts +5 -0
  294. package/src/briefing-abort.test.ts +173 -0
  295. package/src/briefing-deadline.test.ts +84 -0
  296. package/src/briefing-deadline.ts +33 -0
  297. package/src/briefing-window-abort.test.ts +47 -0
  298. package/src/briefing-window.ts +33 -4
  299. package/src/briefing.ts +23 -16
  300. package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
  301. package/src/correction/passive-correction-notifications.ts +4 -0
  302. package/src/entity-lazy-compatibility.test.ts +229 -0
  303. package/src/entity-migration-completion-notification.test.ts +148 -0
  304. package/src/extraction-scope-source-agent.test.ts +6 -6
  305. package/src/extraction-span-wire.test.ts +28 -0
  306. package/src/extraction-span-wire.ts +34 -6
  307. package/src/index.ts +1 -1
  308. package/src/location/tagging.ts +14 -12
  309. package/src/offline-sync-file-io.ts +61 -2
  310. package/src/offline-sync.test.ts +313 -20
  311. package/src/offline-sync.ts +43 -21
  312. package/src/orchestrator-init.test.ts +49 -1
  313. package/src/orchestrator.ts +9 -8
  314. package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
  315. package/src/storage/entity-canonical-id-migration.ts +19 -10
  316. package/src/storage/entity-canonical-id-references.ts +24 -0
  317. package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
  318. package/src/storage/entity-migration-completion-cache.ts +153 -0
  319. package/src/storage/entity-store.ts +58 -86
  320. package/src/storage/memory-read-store.ts +9 -3
  321. package/src/storage/windowed-memory-read.ts +8 -0
  322. package/src/storage-startup-nonblocking-migration.test.ts +183 -0
  323. package/src/storage.ts +7 -2
  324. package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
  325. package/dist/chunk-23ZLN5X6.js.map +0 -1
  326. package/dist/chunk-3MUKQQ7A.js.map +0 -1
  327. package/dist/chunk-3NYIXVCT.js.map +0 -1
  328. package/dist/chunk-6W3QH3QW.js.map +0 -1
  329. package/dist/chunk-7XFQSQYS.js.map +0 -1
  330. package/dist/chunk-BMG3SAXA.js.map +0 -1
  331. package/dist/chunk-FRXS6SC7.js.map +0 -1
  332. package/dist/chunk-LWRIGKFM.js.map +0 -1
  333. package/dist/chunk-N6EM7ESC.js.map +0 -1
  334. package/dist/chunk-NH37SP6V.js.map +0 -1
  335. package/dist/chunk-NUK5XU3P.js.map +0 -1
  336. package/dist/chunk-O4CA53UY.js.map +0 -1
  337. package/dist/chunk-RZDDNH63.js.map +0 -1
  338. package/dist/chunk-SA6P2KS3.js.map +0 -1
  339. package/dist/chunk-SUKPM6DV.js.map +0 -1
  340. package/dist/chunk-VY6BD5KB.js.map +0 -1
  341. /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
  342. /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
  343. /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
  344. /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
  345. /package/dist/{chunk-BWF3KNSQ.js.map → chunk-2EXF6IIB.js.map} +0 -0
  346. /package/dist/{chunk-AGCEJDG2.js.map → chunk-2ONKXEC7.js.map} +0 -0
  347. /package/dist/{chunk-ZSEFGDCI.js.map → chunk-2TGIKMMZ.js.map} +0 -0
  348. /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
  349. /package/dist/{chunk-Q6PYCPHO.js.map → chunk-44EUCSG4.js.map} +0 -0
  350. /package/dist/{chunk-2J6BIOVN.js.map → chunk-4FM35YIA.js.map} +0 -0
  351. /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
  352. /package/dist/{chunk-YY6PWFUC.js.map → chunk-6ZAU7LAR.js.map} +0 -0
  353. /package/dist/{chunk-HCPVL6SF.js.map → chunk-72CBJ5VP.js.map} +0 -0
  354. /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
  355. /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
  356. /package/dist/{chunk-XFND63OB.js.map → chunk-AHGOOZUC.js.map} +0 -0
  357. /package/dist/{chunk-A7AISUJI.js.map → chunk-AJG2TAMR.js.map} +0 -0
  358. /package/dist/{chunk-2BY56HE6.js.map → chunk-AZLD4CKG.js.map} +0 -0
  359. /package/dist/{chunk-E47UQBYW.js.map → chunk-B35EQQ7L.js.map} +0 -0
  360. /package/dist/{chunk-E6UNM67F.js.map → chunk-B6SPPYGJ.js.map} +0 -0
  361. /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
  362. /package/dist/{chunk-SVJ5JVZO.js.map → chunk-CK6GCGMX.js.map} +0 -0
  363. /package/dist/{chunk-QZK6HG4J.js.map → chunk-DF6ZF2BN.js.map} +0 -0
  364. /package/dist/{chunk-CPBYKOTC.js.map → chunk-DJFULNXK.js.map} +0 -0
  365. /package/dist/{chunk-VGRBKEBH.js.map → chunk-DQOGZZJA.js.map} +0 -0
  366. /package/dist/{chunk-YB3WN6XA.js.map → chunk-EC2KVA27.js.map} +0 -0
  367. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  368. /package/dist/{chunk-WIBWONL4.js.map → chunk-EZS6QJNC.js.map} +0 -0
  369. /package/dist/{chunk-C7WDWD35.js.map → chunk-FNNGWN3O.js.map} +0 -0
  370. /package/dist/{chunk-PBWJVRKE.js.map → chunk-FT75AQ74.js.map} +0 -0
  371. /package/dist/{chunk-WJCAAVZX.js.map → chunk-GE3ZX6YK.js.map} +0 -0
  372. /package/dist/{chunk-3PVKV3XL.js.map → chunk-HCIRR3BA.js.map} +0 -0
  373. /package/dist/{chunk-BJTFLP3J.js.map → chunk-HEF4BRMT.js.map} +0 -0
  374. /package/dist/{chunk-6UDLN4IA.js.map → chunk-I6ORDT54.js.map} +0 -0
  375. /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
  376. /package/dist/{chunk-3INLTKTW.js.map → chunk-IUQXRRJB.js.map} +0 -0
  377. /package/dist/{chunk-4KS4IHKH.js.map → chunk-JX3E4XCW.js.map} +0 -0
  378. /package/dist/{chunk-SW3WHVKN.js.map → chunk-KAJYSO3U.js.map} +0 -0
  379. /package/dist/{chunk-D76AHITY.js.map → chunk-KETS5A62.js.map} +0 -0
  380. /package/dist/{chunk-LCHFINL4.js.map → chunk-KIWO4327.js.map} +0 -0
  381. /package/dist/{chunk-FC3ZFEXF.js.map → chunk-KJ3MDX4V.js.map} +0 -0
  382. /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
  383. /package/dist/{chunk-6TIVVOVU.js.map → chunk-L3EFYRYK.js.map} +0 -0
  384. /package/dist/{chunk-X52SXKVC.js.map → chunk-LMB47UM6.js.map} +0 -0
  385. /package/dist/{chunk-A3WXV7YU.js.map → chunk-LWMLBVGB.js.map} +0 -0
  386. /package/dist/{chunk-KMTMMBEB.js.map → chunk-MC7PXJDP.js.map} +0 -0
  387. /package/dist/{chunk-NMOEOQV4.js.map → chunk-MUNYUSBU.js.map} +0 -0
  388. /package/dist/{chunk-VKUMKVEU.js.map → chunk-OCRLJJOV.js.map} +0 -0
  389. /package/dist/{chunk-XSJL62LE.js.map → chunk-ON77FQXH.js.map} +0 -0
  390. /package/dist/{chunk-C3HRYVGD.js.map → chunk-POJRFSJM.js.map} +0 -0
  391. /package/dist/{chunk-LKRXGOQW.js.map → chunk-QA3Y2O6H.js.map} +0 -0
  392. /package/dist/{chunk-QFTNWDJ6.js.map → chunk-RA7YRL6H.js.map} +0 -0
  393. /package/dist/{chunk-WQFHHJWK.js.map → chunk-RJDYDF5K.js.map} +0 -0
  394. /package/dist/{chunk-E3DL3CAL.js.map → chunk-ROAKLYOL.js.map} +0 -0
  395. /package/dist/{chunk-NWIT3U2N.js.map → chunk-S5CQI2KF.js.map} +0 -0
  396. /package/dist/{chunk-P6P35FY4.js.map → chunk-SDESKFKT.js.map} +0 -0
  397. /package/dist/{chunk-OD7ZXCUF.js.map → chunk-SFRBUBUZ.js.map} +0 -0
  398. /package/dist/{chunk-O7GNSHFF.js.map → chunk-SPTPBKZX.js.map} +0 -0
  399. /package/dist/{chunk-HI53EK3Y.js.map → chunk-TC55M5PO.js.map} +0 -0
  400. /package/dist/{chunk-FOKTW6GY.js.map → chunk-UCLJTGP3.js.map} +0 -0
  401. /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
  402. /package/dist/{chunk-754XN6LX.js.map → chunk-V27FK6D6.js.map} +0 -0
  403. /package/dist/{chunk-DM6SOROF.js.map → chunk-V6Z4NAJZ.js.map} +0 -0
  404. /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
  405. /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
  406. /package/dist/{chunk-VCBS5GRQ.js.map → chunk-WY66J4PI.js.map} +0 -0
  407. /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
  408. /package/dist/{chunk-V4GPFCJ7.js.map → chunk-XWJUFER3.js.map} +0 -0
  409. /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
  410. /package/dist/{chunk-CRJX4OMN.js.map → chunk-Y3T5KQCR.js.map} +0 -0
  411. /package/dist/{chunk-KNCR7APE.js.map → chunk-Y77FXULH.js.map} +0 -0
  412. /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
  413. /package/dist/{chunk-IG747LRN.js.map → chunk-YO2JEX2Z.js.map} +0 -0
  414. /package/dist/{chunk-HDT3DMUF.js.map → chunk-ZKK3VMWU.js.map} +0 -0
  415. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -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,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
+ });
@@ -92,13 +92,13 @@ test("scope prompt qualifies tool knowledge as project, requires naming the agen
92
92
  assert.match(captured.system, /"`git status --short` emits compact output" -> "global"/);
93
93
  });
94
94
 
95
- // ExtractedFactSchema is z.object(...).superRefine(...); unwrap the ZodEffects
96
- // to reach the scope field's describe text (named const, not an inline cast).
95
+ // zod 4: superRefine attaches checks to the object itself — the schema stays
96
+ // a ZodObject, so the scope field's describe text is on `.shape` directly.
97
97
  function scopeDescribe(): string {
98
- const unwrapped = ExtractedFactSchema as unknown as {
99
- innerType(): { shape: Record<string, { description?: string }> };
100
- };
101
- return unwrapped.innerType().shape.scope?.description ?? "";
98
+ // SAFETY: ExtractedFactSchema is a ZodObject in zod 4 (superRefine attaches
99
+ // checks in place); its `.shape` maps field names to schema nodes.
100
+ const schema = ExtractedFactSchema as { shape: Record<string, { description?: string }> };
101
+ return schema.shape.scope?.description ?? "";
102
102
  }
103
103
 
104
104
  test("scope field schema describe no longer lists tool configs and qualifies by agent", () => {