@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
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Amortized entity canonical-id migration completion verdict for entity reads.
3
+ *
4
+ * Key covers every getFingerprint input that is cheap to lstat: entity mutation
5
+ * version, entities/ dir, config/aliases.json, the journal, and reconcile
6
+ * markers. Full fingerprint (including entity entry enumeration) runs only on
7
+ * key miss.
8
+ *
9
+ * ponytail: in-place entity *content* edits still skip revalidation.
10
+ */
11
+
12
+ import { lstat, realpath } from "node:fs/promises";
13
+ import path from "node:path";
14
+ import { SecureStoreLockedError } from "../secure-store/secure-fs.js";
15
+ import { isErrnoCode } from "../utils/errno.js";
16
+ import { pathIsInside } from "../utils/path-containment.js";
17
+ import { getFingerprint } from "./entity-canonical-id-migration.js";
18
+ import {
19
+ ENTITY_CANONICAL_ID_MIGRATION_FILE,
20
+ ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER,
21
+ ENTITY_CANONICAL_ID_RECONCILE_MARKER,
22
+ entityCanonicalIdMigrationCompleted,
23
+ resolveHistoricalEntityCanonicalId,
24
+ } from "./entity-canonical-id-references.js";
25
+
26
+ export type EntityMigrationCompletionCacheDeps = {
27
+ readonly baseDir: string;
28
+ readonly entitiesDir: string;
29
+ getEntityMutationVersion(): number;
30
+ };
31
+
32
+ /**
33
+ * True when `entitiesDir` is a real directory inside `baseDir`.
34
+ * lstat first so a symlinked root is never realpath'd into trust.
35
+ */
36
+ export async function isTrustedEntitiesRoot(
37
+ baseDir: string,
38
+ entitiesDir: string,
39
+ ): Promise<boolean> {
40
+ let rootStat;
41
+ try {
42
+ rootStat = await lstat(entitiesDir);
43
+ } catch (err) {
44
+ if (isErrnoCode(err, "ENOENT")) return false;
45
+ throw err;
46
+ }
47
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return false;
48
+ let memoryRoot: string;
49
+ let entitiesReal: string;
50
+ try {
51
+ memoryRoot = await realpath(baseDir);
52
+ entitiesReal = await realpath(entitiesDir);
53
+ } catch (err) {
54
+ if (isErrnoCode(err, "ENOENT")) return false;
55
+ throw err;
56
+ }
57
+ return pathIsInside(memoryRoot, entitiesReal);
58
+ }
59
+
60
+ export class EntityMigrationCompletionCache {
61
+ private cache: { version: number; inputSig: string; completed: boolean } | undefined;
62
+
63
+ /** lstat identity for one fingerprint input path; nofollow; missing → stable token. */
64
+ private async inputPathSig(filePath: string): Promise<string> {
65
+ try {
66
+ const s = await lstat(filePath);
67
+ return `${s.dev}:${s.ino}:${s.mtimeMs}:${s.ctimeMs}:${s.size}:${s.isSymbolicLink() ? "L" : s.isDirectory() ? "D" : "F"}`;
68
+ } catch {
69
+ return "missing";
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Cheap coherence key. entities/ absence is uncacheable (random suffix) so
75
+ * we never pin a false complete.
76
+ */
77
+ private async inputSig(deps: EntityMigrationCompletionCacheDeps): Promise<string> {
78
+ const stateDir = path.join(deps.baseDir, "state");
79
+ const entitiesSig = await this.inputPathSig(deps.entitiesDir);
80
+ if (entitiesSig === "missing") return `entities-missing:${Math.random()}`;
81
+ const [aliasesSig, journalSig, reconcileSig, consumingSig] = await Promise.all([
82
+ this.inputPathSig(path.join(deps.baseDir, "config", "aliases.json")),
83
+ this.inputPathSig(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE)),
84
+ this.inputPathSig(path.join(stateDir, ENTITY_CANONICAL_ID_RECONCILE_MARKER)),
85
+ this.inputPathSig(path.join(stateDir, ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER)),
86
+ ]);
87
+ return [entitiesSig, aliasesSig, journalSig, reconcileSig, consumingSig].join("|");
88
+ }
89
+
90
+ /**
91
+ * True when journal completion attests to live fingerprint inputs.
92
+ * Untrusted entities roots (symlink / outside base) never reach getFingerprint.
93
+ */
94
+ async isCompleted(deps: EntityMigrationCompletionCacheDeps): Promise<boolean> {
95
+ const version = deps.getEntityMutationVersion();
96
+ const inputSig = await this.inputSig(deps);
97
+ const cached = this.cache;
98
+ if (cached && cached.version === version && cached.inputSig === inputSig) return cached.completed;
99
+ if (!(await isTrustedEntitiesRoot(deps.baseDir, deps.entitiesDir))) {
100
+ this.cache = { version, inputSig, completed: false };
101
+ return false;
102
+ }
103
+ const completed = await entityCanonicalIdMigrationCompleted(path.join(deps.baseDir, "state"), () =>
104
+ getFingerprint(deps.baseDir, deps.entitiesDir, () => String(version)));
105
+ this.cache = { version, inputSig, completed };
106
+ return completed;
107
+ }
108
+ }
109
+
110
+ export type ReadEntityRenameDeps = {
111
+ readonly entitiesDir: string;
112
+ currentHistoricalIds(): Readonly<Record<string, string>>;
113
+ readStorageSecureFile(filePath: string): Promise<string>;
114
+ };
115
+
116
+ /** Resolve a physical entity path through historical id rename candidates. */
117
+ export async function readEntityFileResolvingRename(
118
+ deps: ReadEntityRenameDeps,
119
+ firstPath: string,
120
+ entityId: string,
121
+ ): Promise<{ id: string; content: string } | null> {
122
+ try {
123
+ return { id: entityId, content: await deps.readStorageSecureFile(firstPath) };
124
+ } catch (err) {
125
+ if (err instanceof SecureStoreLockedError) throw err;
126
+ // ENOENT: renamed away (retry below). ELOOP: planted symlink — skip.
127
+ if (isErrnoCode(err, "ELOOP")) return null;
128
+ if (!isErrnoCode(err, "ENOENT")) throw err;
129
+ }
130
+ const historicalIds = deps.currentHistoricalIds();
131
+ const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
132
+ const candidates = new Set([canonical]);
133
+ for (const legacy of Object.keys(historicalIds)) {
134
+ if (resolveHistoricalEntityCanonicalId(legacy, historicalIds) === canonical) candidates.add(legacy);
135
+ }
136
+ candidates.delete(entityId);
137
+ for (const candidate of candidates) {
138
+ try {
139
+ return {
140
+ id: candidate,
141
+ content: await deps.readStorageSecureFile(
142
+ path.join(deps.entitiesDir, `${candidate}.md`),
143
+ ),
144
+ };
145
+ } catch (err) {
146
+ if (err instanceof SecureStoreLockedError) throw err;
147
+ // ENOENT/ELOOP: gone or a planted symlink — try the next candidate.
148
+ if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) continue;
149
+ throw err;
150
+ }
151
+ }
152
+ return null;
153
+ }
@@ -10,11 +10,17 @@
10
10
  * - fragmented-entity merge (consolidation)
11
11
  */
12
12
 
13
- import { lstat, readdir, realpath, unlink } from "node:fs/promises";
13
+ import { lstat, readdir, unlink } from "node:fs/promises";
14
14
  import path from "node:path";
15
15
  import { checkCorpusReadAbort, type CorpusReadOptions } from "../corpus-read-cancellation.js";
16
16
  import { normalizeEntityStructuredSection, sortStructuredSectionsBySchema } from "../entity-schema.js";
17
17
  import { withEntityCanonicalMutationLock } from "./entity-canonical-id-lock.js";
18
+ import { assertSafeEntityId } from "./entity-id-safety.js";
19
+ import {
20
+ EntityMigrationCompletionCache,
21
+ isTrustedEntitiesRoot,
22
+ readEntityFileResolvingRename,
23
+ } from "./entity-migration-completion-cache.js";
18
24
  import { isMalformedEntityRecord } from "../utils/malformed-entity-record.js";
19
25
  import { log } from "../logger.js";
20
26
  import {
@@ -33,7 +39,6 @@ import type {
33
39
  ScoredEntity,
34
40
  } from "../types.js";
35
41
  import { isErrnoCode } from "../utils/errno.js";
36
- import { pathIsInside } from "../utils/path-containment.js";
37
42
  import {
38
43
  reconcileIfJournalMovedSync,
39
44
  requestEntityCanonicalIdReconcileSync,
@@ -89,6 +94,7 @@ export interface EntityStoreDeps {
89
94
  export class EntityStore {
90
95
  /** Filename → file identity of the last indexable check. Unchanged files are not reread. */
91
96
  private readonly entityIndexableCache = new Map<string, { identity: string; ok: boolean }>();
97
+ private readonly migrationCompletion = new EntityMigrationCompletionCache();
92
98
 
93
99
  constructor(
94
100
  private readonly deps: EntityStoreDeps,
@@ -111,6 +117,7 @@ export class EntityStore {
111
117
  method: string,
112
118
  historicalIds: Readonly<Record<string, string>>,
113
119
  ): Promise<{ filePath: string; entity: EntityFile } | null> {
120
+ assertSafeEntityId(name);
114
121
  const canonical = resolveHistoricalEntityCanonicalId(name, historicalIds);
115
122
  for (const candidate of canonical === name ? [name] : [canonical, name]) {
116
123
  const filePath = path.join(this.deps.entitiesDir, `${candidate}.md`);
@@ -143,6 +150,7 @@ export class EntityStore {
143
150
  method: string,
144
151
  mutate: (entity: EntityFile, historicalIds: Readonly<Record<string, string>>) => boolean,
145
152
  ): Promise<boolean> {
153
+ await this.deps.ensureDirectories();
146
154
  const stateDir = path.join(this.deps.baseDir, "state");
147
155
  return withEntityCanonicalMutationLock(stateDir, async () => {
148
156
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -375,83 +383,46 @@ export class EntityStore {
375
383
  return normalized;
376
384
  }
377
385
 
378
- private async readEntityFileResolvingRename(firstPath: string, entityId: string) {
379
- try {
380
- return { id: entityId, content: await this.deps.readStorageSecureFile(firstPath) };
381
- } catch (err) {
382
- if (err instanceof SecureStoreLockedError) throw err;
383
- // ENOENT: renamed away (retry below). ELOOP: planted symlink — skip.
384
- if (isErrnoCode(err, "ELOOP")) return null;
385
- if (!isErrnoCode(err, "ENOENT")) throw err;
386
- }
387
- const historicalIds = this.deps.currentHistoricalIds();
388
- const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
389
- const candidates = new Set([canonical]);
390
- for (const legacy of Object.keys(historicalIds)) {
391
- if (resolveHistoricalEntityCanonicalId(legacy, historicalIds) === canonical) candidates.add(legacy);
392
- }
393
- candidates.delete(entityId);
394
- for (const candidate of candidates) {
386
+ async readEntity(name: string): Promise<string> {
387
+ const filePath = this.deps.resolveEntityFilePath(name);
388
+ if (filePath === null) return "";
389
+ // Physical hit wins. Miss skips rename/body-scan only when amortized
390
+ // completion attests live fingerprint inputs (not bare journal.complete).
391
+ const readDirect = async (): Promise<string | undefined> => {
395
392
  try {
396
- return { id: candidate, content: await this.deps.readStorageSecureFile(
397
- path.join(this.deps.entitiesDir, `${candidate}.md`),
398
- ) };
393
+ return await this.deps.readStorageSecureFile(filePath);
399
394
  } catch (err) {
400
395
  if (err instanceof SecureStoreLockedError) throw err;
401
- // ENOENT/ELOOP: gone or a planted symlink — try the next candidate.
402
- if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) continue;
396
+ if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) return undefined;
403
397
  throw err;
404
398
  }
405
- }
406
- return null;
407
- }
408
-
409
- async readEntity(name: string): Promise<string> {
410
- const filePath = this.deps.resolveEntityFilePath(name);
411
- if (filePath === null) {
412
- return "";
413
- }
414
- // Batch reads retry renames unconditionally (a listed file that ENOENTs
415
- // is definitionally a race), but a DIRECT lookup of a legacy id must stop
416
- // resolving once the journal says migration completed and its file is gone.
417
- if (this.deps.entityCanonicalMigrationComplete()) {
399
+ };
400
+ const direct = await readDirect();
401
+ if (direct !== undefined) return direct;
402
+ if (await this.migrationCompletion.isCompleted(this.deps)) return "";
403
+ const renamed = await readEntityFileResolvingRename(this.deps, filePath, name);
404
+ if (renamed !== null) return renamed.content;
405
+ // Deferred / fingerprint-stale: match legacy filename by body id.
406
+ let legacy: string | undefined;
407
+ for (const file of await this.readEntityDirectoryNames()) {
408
+ if (!file.endsWith(".md") || !(await this.isContainedRegularEntityFile(file))) continue;
409
+ let content: string;
418
410
  try {
419
- return await this.deps.readStorageSecureFile(filePath);
411
+ content = await this.deps.readStorageSecureFile(path.join(this.deps.entitiesDir, file));
420
412
  } catch (err) {
421
413
  if (err instanceof SecureStoreLockedError) throw err;
422
- // ENOENT: gone. ELOOP: a planted symlink the open refused to follow.
423
- if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) return "";
414
+ if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) continue;
424
415
  throw err;
425
416
  }
417
+ if (isMalformedEntityRecord(content)) continue;
418
+ const entity = parseEntityFile(content, this.deps.entitySchemas);
419
+ if (!entity.name.trim() || !entity.type.trim()) continue;
420
+ if (this.deps.normalizeEntityName(entity.name, entity.type) !== name) continue;
421
+ if (legacy !== undefined) throw new Error(`Ambiguous legacy entity id: ${name}`);
422
+ legacy = content;
426
423
  }
427
- return (await this.readEntityFileResolvingRename(filePath, name))?.content ?? "";
428
- }
429
-
430
- /**
431
- * True when `entities/` is a real directory inside this memory root.
432
- * `realpath` on a symlinked root would treat the link target as trusted,
433
- * so the check uses `lstat` first and refuses the scan when the root
434
- * itself is a symlink.
435
- */
436
- private async isTrustedEntitiesRoot(): Promise<boolean> {
437
- let rootStat;
438
- try {
439
- rootStat = await lstat(this.deps.entitiesDir);
440
- } catch (err) {
441
- if (isErrnoCode(err, "ENOENT")) return false;
442
- throw err;
443
- }
444
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return false;
445
- let memoryRoot: string;
446
- let entitiesReal: string;
447
- try {
448
- memoryRoot = await realpath(this.deps.baseDir);
449
- entitiesReal = await realpath(this.deps.entitiesDir);
450
- } catch (err) {
451
- if (isErrnoCode(err, "ENOENT")) return false;
452
- throw err;
453
- }
454
- return pathIsInside(memoryRoot, entitiesReal);
424
+ // A peer may have renamed the legacy entry after the directory listing.
425
+ return (await readDirect()) ?? legacy ?? "";
455
426
  }
456
427
 
457
428
  /**
@@ -460,7 +431,7 @@ export class EntityStore {
460
431
  * A symlinked entities root yields an empty list and is not read.
461
432
  */
462
433
  private async readEntityDirectoryNames(): Promise<string[]> {
463
- if (!(await this.isTrustedEntitiesRoot())) return [];
434
+ if (!(await isTrustedEntitiesRoot(this.deps.baseDir, this.deps.entitiesDir))) return [];
464
435
  const entries = await readdir(this.deps.entitiesDir);
465
436
  const { kept, ignored } = withoutMacOsReplicationJunk(entries, (name) => name);
466
437
  warnIgnoredMacOsReplicationJunk(this.deps.entitiesDir, ignored, (message) => {
@@ -595,6 +566,7 @@ export class EntityStore {
595
566
 
596
567
  /** Re-serialize every entity file into the compiled-truth timeline shape. */
597
568
  async migrateEntityFilesToCompiledTruthTimeline(): Promise<{ total: number; migrated: number }> {
569
+ await this.deps.ensureDirectories();
598
570
  const entityNames = await this.listEntityNames();
599
571
  const stateDir = path.join(this.deps.baseDir, "state");
600
572
  let migrated = 0;
@@ -605,14 +577,20 @@ export class EntityStore {
605
577
  // path bumps only the status version, which never re-triggers the
606
578
  // completed migration's reference scan.
607
579
  const wrote = await withEntityCanonicalMutationLock(stateDir, async () => {
608
- const raw = await this.readEntity(entityName);
580
+ const filePath = this.deps.resolveEntityFilePath(entityName);
581
+ if (filePath === null) return false;
582
+ let raw: string;
583
+ try {
584
+ raw = await this.deps.readStorageSecureFile(filePath);
585
+ } catch (err) {
586
+ if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
587
+ return false;
588
+ }
609
589
  if (!raw || isMalformedEntityRecord(raw)) return false;
610
590
  const parsed = parseEntityFile(raw, this.deps.entitySchemas);
611
591
  if (!parsed.name.trim() || !parsed.type.trim()) return false;
612
592
  const serialized = serializeEntityFile(parsed, this.deps.entitySchemas, this.deps.okfConformanceEnabled());
613
593
  if (raw.trimEnd() === serialized.trimEnd()) return false;
614
- const filePath = this.deps.resolveEntityFilePath(entityName);
615
- if (filePath === null) return false;
616
594
  await this.deps.writeStorageSecureFile(filePath, serialized);
617
595
  this.deps.bumpEntityMutationVersion();
618
596
  return true;
@@ -641,6 +619,7 @@ export class EntityStore {
641
619
  incrementVersion?: boolean;
642
620
  } = {},
643
621
  ): Promise<void> {
622
+ await this.deps.ensureDirectories();
644
623
  return withEntityCanonicalMutationLock(
645
624
  path.join(this.deps.baseDir, "state"),
646
625
  () => this.updateEntitySynthesisUnlocked(name, synthesis, options),
@@ -659,17 +638,9 @@ export class EntityStore {
659
638
  incrementVersion?: boolean;
660
639
  } = {},
661
640
  ): Promise<void> {
662
- const filePath = path.join(this.deps.entitiesDir, `${name}.md`);
663
- let entity: EntityFile;
664
- try {
665
- const content = await this.deps.readStorageSecureFile(filePath);
666
- entity = parseEntityFile(content, this.deps.entitySchemas);
667
- } catch (err) {
668
- if (err instanceof SecureStoreLockedError) throw err;
669
- if (!isErrnoCode(err, "ENOENT")) throw err;
670
- log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
671
- return;
672
- }
641
+ const located = await this.readEntityForMutation(name, "updateEntitySynthesis", this.deps.currentHistoricalIds());
642
+ if (!located) return;
643
+ const { filePath, entity } = located;
673
644
 
674
645
  const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || undefined;
675
646
  const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || new Date().toISOString();
@@ -721,7 +692,7 @@ export class EntityStore {
721
692
  // #3207 semantics): resolve empty rather than following it. Within a
722
693
  // trusted root, readdir directly so planted entries reach the secure
723
694
  // read instead of being swallowed by the listing guards.
724
- if (!(await this.isTrustedEntitiesRoot())) return [];
695
+ if (!(await isTrustedEntitiesRoot(this.deps.baseDir, this.deps.entitiesDir))) return [];
725
696
  const listed = await readdir(this.deps.entitiesDir);
726
697
  const { kept: entries, ignored } = withoutMacOsReplicationJunk(listed, (name) => name);
727
698
  warnIgnoredMacOsReplicationJunk(this.deps.entitiesDir, ignored, (message) => {
@@ -753,11 +724,11 @@ export class EntityStore {
753
724
  // rename: resolve through historical ids like a direct read.
754
725
  // Still-present symlinks/dirs stay skipped.
755
726
  if (await this.isMissingEntry(entry)) {
756
- return await this.readEntityFileResolvingRename(entryPath, entityId);
727
+ return await readEntityFileResolvingRename(this.deps, entryPath, entityId);
757
728
  }
758
729
  return null;
759
730
  }
760
- return await this.readEntityFileResolvingRename(entryPath, entityId);
731
+ return await readEntityFileResolvingRename(this.deps, entryPath, entityId);
761
732
  } catch (err) {
762
733
  // Lock and decrypt failures abort the corpus read (the store
763
734
  // is unusable or tampered); anything else isolates to this entry.
@@ -873,6 +844,7 @@ export class EntityStore {
873
844
  * Returns count of files merged.
874
845
  */
875
846
  async mergeFragmentedEntities(): Promise<number> {
847
+ await this.deps.ensureDirectories();
876
848
  return withEntityCanonicalMutationLock(
877
849
  path.join(this.deps.baseDir, "state"),
878
850
  () => this.mergeFragmentedEntitiesUnlocked(),
@@ -41,7 +41,7 @@ export interface MemoryReadStoreDeps {
41
41
  readonly artifactsDir: string;
42
42
  readonly baseDir: string;
43
43
  readonly bufferSurpriseLedgerPath: string;
44
- collectActiveMemoryPaths(): Promise<string[]>;
44
+ collectActiveMemoryPaths(options?: CorpusReadOptions): Promise<string[]>;
45
45
  readonly correctionsDir: string;
46
46
  ensureDirectories(): Promise<void>;
47
47
  readonly entitySchemas: PluginConfig["entitySchemas"] | undefined;
@@ -79,6 +79,7 @@ export interface MemoryReadStoreDeps {
79
79
  remainingSlots: number,
80
80
  remainingInspectionBudget: number,
81
81
  readBatchSize: number,
82
+ options?: CorpusReadOptions,
82
83
  ): Promise<{ memories: MemoryFile[]; filePaths: string[] }>;
83
84
  readonly reasoningTracesDir: string;
84
85
  resolveTierRootDir(tier: "hot" | "cold"): string;
@@ -408,8 +409,10 @@ export class MemoryReadStore {
408
409
  maxMemories?: number;
409
410
  batchSize?: number;
410
411
  updatedAfter?: Date;
412
+ abortSignal?: AbortSignal;
411
413
  } = {}): Promise<{ memories: MemoryFile[]; filePaths: string[] }> {
412
- const allPaths = await this.deps.collectActiveMemoryPaths();
414
+ const readOptions: CorpusReadOptions = { abortSignal: options.abortSignal };
415
+ const allPaths = await this.deps.collectActiveMemoryPaths(readOptions);
413
416
  const sortedPaths = this.deps.orderWindowPaths(allPaths);
414
417
  const maxMemories =
415
418
  typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories)
@@ -422,6 +425,8 @@ export class MemoryReadStore {
422
425
  const selectedPaths: string[] = [];
423
426
 
424
427
  for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
428
+ // Per batch: an abandoned caller must not keep the scan running (#3139).
429
+ checkCorpusReadAbort(readOptions);
425
430
  if (
426
431
  maxMemories !== undefined
427
432
  && (memories.length >= maxMemories || (maxCandidatePaths !== undefined && selectedPaths.length >= maxCandidatePaths))
@@ -436,7 +441,7 @@ export class MemoryReadStore {
436
441
  const remainingInspectionBudget = maxCandidatePaths === undefined ? undefined : Math.max(0, maxCandidatePaths - selectedPaths.length);
437
442
  const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === undefined
438
443
  ? {
439
- memories: await this.deps.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
444
+ memories: await this.deps.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize, readOptions),
440
445
  filePaths: candidateBatchPaths,
441
446
  }
442
447
  : await this.deps.readWindowBoundedBatch(
@@ -444,6 +449,7 @@ export class MemoryReadStore {
444
449
  remainingSlots,
445
450
  remainingInspectionBudget ?? remainingSlots,
446
451
  normalizedBatchSize,
452
+ readOptions,
447
453
  );
448
454
  selectedPaths.push(...parsedCandidatePaths);
449
455
  for (const memory of batchMemories) {
@@ -1,5 +1,6 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { throwIfAborted } from "../abort-error.js";
3
4
  import { getCachedMemories } from "../memory-cache.js";
4
5
  import { readRawMemoryDocument } from "./memory-frontmatter-metadata.js";
5
6
  import type { MemoryFile } from "../types.js";
@@ -11,6 +12,12 @@ export interface WindowedMemoryReadOptions {
11
12
  updatedAfter?: Date;
12
13
  /** Require candidate paths so malformed files can be reported by governance. */
13
14
  includeMalformedPaths?: boolean;
15
+ /**
16
+ * Caller cancellation. The windowed scan stops at its next batch boundary
17
+ * when this aborts, so a cancelled read leaves no work running behind it
18
+ * (issue #3139).
19
+ */
20
+ abortSignal?: AbortSignal;
14
21
  }
15
22
 
16
23
  export interface WindowedMemoryReadResult {
@@ -62,6 +69,7 @@ export async function readWindowedMemories(
62
69
  })
63
70
  )
64
71
  ).filter((memory): memory is MemoryFile => memory !== null);
72
+ throwIfAborted(options.abortSignal, "windowed memory read aborted");
65
73
  const memories = orderWindowMemories(selected, hotCache.baseDir);
66
74
  return { memories: rememberMemorySnapshots(memories), filePaths: memories.map((memory) => memory.path) };
67
75
  }