@remnic/core 10.0.63 → 10.0.65

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 (326) hide show
  1. package/dist/access-admin-ops-surface.js +46 -46
  2. package/dist/access-authorization-probe.js +47 -47
  3. package/dist/access-boundary.js +46 -46
  4. package/dist/access-cli.js +96 -96
  5. package/dist/access-extraction-force-flush.js +46 -46
  6. package/dist/access-http-briefing.js +47 -47
  7. package/dist/access-http-query.js +48 -48
  8. package/dist/access-http.js +66 -66
  9. package/dist/access-identity-continuity-surface.js +46 -46
  10. package/dist/access-lcm-surface.js +46 -46
  11. package/dist/access-mcp.js +58 -58
  12. package/dist/access-namespace-preflight.js +46 -46
  13. package/dist/access-observe-write-category.js +11 -11
  14. package/dist/access-observe-write-surface.js +46 -46
  15. package/dist/access-offline-manifest.js +10 -10
  16. package/dist/access-operations-batch.js +50 -50
  17. package/dist/access-operations.js +57 -57
  18. package/dist/access-recall-concurrency.js +46 -46
  19. package/dist/access-recall-response.js +46 -46
  20. package/dist/access-recall-surface.js +46 -46
  21. package/dist/access-schema.js +6 -6
  22. package/dist/access-service.js +46 -46
  23. package/dist/access-surface-catalog.js +48 -48
  24. package/dist/active-recall.js +21 -21
  25. package/dist/{auto-sync-UQQ6YJSB.js → auto-sync-ANOJDQD4.js} +7 -7
  26. package/dist/briefing.js +14 -14
  27. package/dist/buffer.js +7 -7
  28. package/dist/{capsule-merge-D4BTIJGK.js → capsule-merge-XVTQJJ6R.js} +4 -4
  29. package/dist/capture-write-lock.js +2 -2
  30. package/dist/causal-behavior.js +2 -2
  31. package/dist/causal-chain.js +2 -2
  32. package/dist/causal-consolidation.js +17 -17
  33. package/dist/causal-retrieval.js +2 -2
  34. package/dist/causal-trajectory-graph.js +3 -3
  35. package/dist/causal-trajectory.js +1 -1
  36. package/dist/{chunk-S4OZKPBS.js → chunk-2D7BTSDW.js} +2 -2
  37. package/dist/{chunk-WB3OFPM2.js → chunk-2DNFRLLL.js} +3 -3
  38. package/dist/{chunk-EKPRLTAE.js → chunk-2RUTN7PR.js} +2 -2
  39. package/dist/{chunk-EAALT5NB.js → chunk-2ZSRACNK.js} +42 -42
  40. package/dist/{chunk-X4PAATLA.js → chunk-3LBDEGQA.js} +4 -4
  41. package/dist/{chunk-2U7ZDC2E.js → chunk-3LSTL6EY.js} +2 -2
  42. package/dist/{chunk-QCAJMIAE.js → chunk-434E4BTO.js} +3 -3
  43. package/dist/{chunk-TPKB7T6Z.js → chunk-4XINO77L.js} +2 -2
  44. package/dist/{chunk-EADUOZWH.js → chunk-5JABRT5G.js} +5 -5
  45. package/dist/{chunk-6I6DFULH.js → chunk-5KDDAQ6K.js} +2 -2
  46. package/dist/{chunk-YDIOMDBW.js → chunk-5QV6CE6K.js} +3 -3
  47. package/dist/{chunk-QI7LLSWX.js → chunk-5UEGL5BF.js} +3 -3
  48. package/dist/{chunk-TMWSCE2L.js → chunk-5ZHFPUBC.js} +11 -11
  49. package/dist/{chunk-TUCEP2LS.js → chunk-63A2BU2B.js} +2 -2
  50. package/dist/{chunk-264B57GV.js → chunk-6D3IWYNY.js} +71 -88
  51. package/dist/chunk-6D3IWYNY.js.map +1 -0
  52. package/dist/{chunk-HJ3UTGIW.js → chunk-6P7U3HWG.js} +4 -4
  53. package/dist/{chunk-UEDC2VGP.js → chunk-7JYK7NNY.js} +2 -2
  54. package/dist/{chunk-IWAB7JVH.js → chunk-7ZTGMEXF.js} +2 -2
  55. package/dist/{chunk-QYAXMXOB.js → chunk-A7BM2JTH.js} +2 -2
  56. package/dist/{chunk-HSHLDSPX.js → chunk-AUFPPBC3.js} +3 -3
  57. package/dist/{chunk-QJ6IR35B.js → chunk-B7VD36TI.js} +4 -4
  58. package/dist/{chunk-ECT5ME6X.js → chunk-BBHKN2CN.js} +2 -2
  59. package/dist/{chunk-WA6XRNWE.js → chunk-BH7NVSEC.js} +4 -4
  60. package/dist/{chunk-QIMGULGN.js → chunk-BNZAR6PO.js} +2 -2
  61. package/dist/{chunk-C5OW2FFB.js → chunk-BOZQAUAJ.js} +2 -2
  62. package/dist/{chunk-WJCXUE2J.js → chunk-BV2BRHD4.js} +5 -5
  63. package/dist/{chunk-Z2T5BPOZ.js → chunk-CIEEYVUB.js} +2 -2
  64. package/dist/{chunk-PA6ZNFB7.js → chunk-CLSKGTAT.js} +2 -2
  65. package/dist/{chunk-YRL4PGPP.js → chunk-DRTSPRWL.js} +2 -2
  66. package/dist/{chunk-ZRSXXWIX.js → chunk-E4QIX2OB.js} +3 -3
  67. package/dist/{chunk-EBRG7ZIK.js → chunk-EBV6VXKF.js} +24 -2
  68. package/dist/{chunk-EBRG7ZIK.js.map → chunk-EBV6VXKF.js.map} +1 -1
  69. package/dist/{chunk-X2ZLHUEO.js → chunk-ECF627KC.js} +2 -2
  70. package/dist/{chunk-XICJL56Z.js → chunk-EI6M5NVK.js} +2 -2
  71. package/dist/{chunk-564YP3DW.js → chunk-ER7XANP3.js} +3 -3
  72. package/dist/{chunk-ILMB3X4X.js → chunk-FKGLJJVS.js} +3 -3
  73. package/dist/{chunk-L74VVEJA.js → chunk-FR7PK7ED.js} +3 -3
  74. package/dist/{chunk-UXRCORZX.js → chunk-FUDKGJDN.js} +2 -2
  75. package/dist/{chunk-GNEB3WXR.js → chunk-G6L3REL3.js} +2 -2
  76. package/dist/{chunk-4P4VLPGB.js → chunk-GKL6QYZO.js} +1 -1
  77. package/dist/{chunk-IMNJJKHW.js → chunk-GRAUQXQB.js} +28 -28
  78. package/dist/{chunk-JVFYP2LD.js → chunk-GROHOWR5.js} +2 -2
  79. package/dist/{chunk-LIUI46BX.js → chunk-H6VJSITJ.js} +2 -2
  80. package/dist/{chunk-YGM47S7M.js → chunk-HFEGRMFG.js} +8 -8
  81. package/dist/{chunk-A6JV3WHI.js → chunk-HG3CG557.js} +2 -2
  82. package/dist/{chunk-7JFXXA57.js → chunk-HHL4MX3W.js} +2 -2
  83. package/dist/{chunk-53OWLEAD.js → chunk-HNOSWPKG.js} +2 -2
  84. package/dist/{chunk-IJKBIOWL.js → chunk-HOR4RMUM.js} +25 -25
  85. package/dist/{chunk-WZEDFDJ6.js → chunk-HRRAOTOQ.js} +15 -15
  86. package/dist/{chunk-A5L5KGDI.js → chunk-IAYUTGF2.js} +2 -2
  87. package/dist/{chunk-WVEEVYMH.js → chunk-IGOW4TJE.js} +3 -3
  88. package/dist/{chunk-MKE5DP3S.js → chunk-IHCXMYSE.js} +2 -2
  89. package/dist/{chunk-SDXBR5QO.js → chunk-IZJQNDGI.js} +2 -2
  90. package/dist/{chunk-BQOOUKEN.js → chunk-JL5HXL25.js} +6 -6
  91. package/dist/{chunk-ZFU2B2GM.js → chunk-JPY3RMG2.js} +5 -5
  92. package/dist/{chunk-TWRPWJJT.js → chunk-JRVMCZE3.js} +2 -2
  93. package/dist/{chunk-FYPKSR6Z.js → chunk-KISOMGY2.js} +2 -2
  94. package/dist/{chunk-J62O2UW5.js → chunk-KNMSD2MQ.js} +2 -2
  95. package/dist/{chunk-MTVC5S5N.js → chunk-KO224R36.js} +2 -2
  96. package/dist/{chunk-UFQB5VQ5.js → chunk-KQJUEQ2E.js} +7 -7
  97. package/dist/{chunk-QNIYL5SK.js → chunk-KQLBXUUQ.js} +2 -2
  98. package/dist/{chunk-W5EO4USJ.js → chunk-KQUSP6LB.js} +2 -2
  99. package/dist/{chunk-BDNPW3KM.js → chunk-KYSSUY66.js} +4 -4
  100. package/dist/{chunk-R25JMO5O.js → chunk-LDHR465K.js} +2 -2
  101. package/dist/{chunk-CYEEVWQ6.js → chunk-LIGZO7XN.js} +2 -2
  102. package/dist/{chunk-QKXPOLQD.js → chunk-M4IEZGCK.js} +2 -2
  103. package/dist/{chunk-Q3DAWUAV.js → chunk-MRF5V5CD.js} +2 -2
  104. package/dist/{chunk-HTTZ3GJ6.js → chunk-MUHGYV2V.js} +2 -2
  105. package/dist/{chunk-HNZH57JK.js → chunk-N4T5FW6U.js} +2 -2
  106. package/dist/{chunk-YLB7TW2B.js → chunk-NM2II57L.js} +4 -4
  107. package/dist/{chunk-3VNRDRBM.js → chunk-OVL5AD3N.js} +2 -2
  108. package/dist/{chunk-3HLWLYRD.js → chunk-P2EM5LKH.js} +2 -2
  109. package/dist/{chunk-XL2EE5W3.js → chunk-PLCYKAWH.js} +2 -2
  110. package/dist/{chunk-JIYJIH5N.js → chunk-QDZWQBLU.js} +3 -3
  111. package/dist/{chunk-YXW4KXCV.js → chunk-QGJSBU4R.js} +2 -2
  112. package/dist/{chunk-DM5QZ3YU.js → chunk-QGVCFVX6.js} +2 -2
  113. package/dist/{chunk-VSHWPOUB.js → chunk-RJWL3RHB.js} +2 -2
  114. package/dist/{chunk-WU5WTCH5.js → chunk-RZH5RVJC.js} +3 -3
  115. package/dist/{chunk-76LKZ42K.js → chunk-SHHZWSSE.js} +2 -2
  116. package/dist/{chunk-N7CATBYH.js → chunk-SNS3DWG6.js} +2 -2
  117. package/dist/{chunk-IVJI5FDA.js → chunk-T22FBDG3.js} +2 -2
  118. package/dist/{chunk-YH7HJNWV.js → chunk-TGQW3AMK.js} +4 -4
  119. package/dist/{chunk-KQXRVELF.js → chunk-TIGYOZBU.js} +2 -2
  120. package/dist/{chunk-Z45353ML.js → chunk-UH6ROXFJ.js} +2 -2
  121. package/dist/{chunk-C2AYUXEF.js → chunk-UYUFDTIX.js} +2 -2
  122. package/dist/{chunk-2AKIVSHP.js → chunk-W26D2USW.js} +2 -2
  123. package/dist/{chunk-FZVZYU5C.js → chunk-W7K5BP3M.js} +3 -3
  124. package/dist/{chunk-ZARHK7PT.js → chunk-WWH7GJ42.js} +2 -2
  125. package/dist/{chunk-4NLHEE2Z.js → chunk-XLCEFUAU.js} +2 -2
  126. package/dist/{chunk-PWEIZCPS.js → chunk-XOFIJGB2.js} +2 -2
  127. package/dist/{chunk-6VBKU3FY.js → chunk-XVQVF2TK.js} +18 -12
  128. package/dist/chunk-XVQVF2TK.js.map +1 -0
  129. package/dist/{chunk-TRSKATZQ.js → chunk-XZ66U5M3.js} +2 -2
  130. package/dist/{chunk-UUZ7KZAO.js → chunk-Y33KJ556.js} +2 -2
  131. package/dist/{chunk-BU4W2ISH.js → chunk-YJQJRXUI.js} +2 -2
  132. package/dist/{chunk-QBK3IM6W.js → chunk-YSATDEBY.js} +2 -2
  133. package/dist/{chunk-WR5POHQ3.js → chunk-YUTT4ZTI.js} +2 -2
  134. package/dist/{chunk-JCL7RO2G.js → chunk-ZOBYTRBU.js} +2 -2
  135. package/dist/{chunk-FMOUUWQN.js → chunk-ZPLQ27MY.js} +3 -3
  136. package/dist/cli.js +81 -81
  137. package/dist/coding/export-okf-codegraph.js +14 -14
  138. package/dist/coding/pre-action-gate.js +2 -2
  139. package/dist/compounding/engine.js +16 -16
  140. package/dist/config.js +21 -21
  141. package/dist/connectors/codex-materialize-runner.js +14 -14
  142. package/dist/connectors/index.js +14 -14
  143. package/dist/consolidation-provenance-check.js +4 -4
  144. package/dist/consolidation-undo.js +3 -3
  145. package/dist/contradiction/index.js +5 -5
  146. package/dist/convergence-refresh.js +16 -16
  147. package/dist/corpus-watermark.js +16 -16
  148. package/dist/deep-recall-http-glue.js +7 -7
  149. package/dist/embedding-fallback.js +3 -3
  150. package/dist/embedding-generation-lock.js +2 -2
  151. package/dist/{entity-canonical-id-migration-CUDCKWPF.js → entity-canonical-id-migration-JJSTZF73.js} +4 -4
  152. package/dist/entity-retrieval.js +14 -14
  153. package/dist/explicit-capture.js +8 -8
  154. package/dist/external-wiki-access.js +47 -47
  155. package/dist/extraction.js +18 -18
  156. package/dist/{forget-E6ZTUKEC.js → forget-DE56ICCL.js} +3 -3
  157. package/dist/{graph-edge-decay-2BZTY2IM.js → graph-edge-decay-OEX5OGMI.js} +6 -6
  158. package/dist/graph-jsonl.js +4 -4
  159. package/dist/graph-snapshot.js +4 -4
  160. package/dist/graph-write-lock.js +2 -2
  161. package/dist/graph.js +3 -3
  162. package/dist/harmonic-retrieval.js +14 -14
  163. package/dist/index.js +96 -96
  164. package/dist/location/index.js +17 -17
  165. package/dist/maintenance/memory-governance.js +14 -14
  166. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -14
  167. package/dist/maintenance/rebuild-memory-projection-runner.js +16 -16
  168. package/dist/maintenance/rebuild-memory-projection.js +15 -15
  169. package/dist/memory-browse-operations.js +47 -47
  170. package/dist/memory-subject.js +3 -3
  171. package/dist/namespaces/migrate.js +15 -15
  172. package/dist/namespaces/storage.js +14 -14
  173. package/dist/offline-sync-changeset-generations.js +4 -4
  174. package/dist/offline-sync-embedding-generation.js +3 -3
  175. package/dist/offline-sync-impression-drain.js +3 -3
  176. package/dist/offline-sync-snapshot-stream.js +6 -6
  177. package/dist/offline-sync.js +5 -5
  178. package/dist/operator-doctor-corpus.js +17 -17
  179. package/dist/operator-toolkit.js +33 -33
  180. package/dist/orchestration/buffer-drain-runner.js +96 -96
  181. package/dist/orchestration/extraction-run-helpers.d.ts +0 -3
  182. package/dist/orchestration/extraction-run-helpers.d.ts.map +1 -1
  183. package/dist/orchestration/extraction-run.d.ts +4 -2
  184. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  185. package/dist/orchestration/maintenance.js +26 -26
  186. package/dist/orchestrator.js +96 -96
  187. package/dist/page-versioning.js +3 -3
  188. package/dist/recall-navigation-operations.js +47 -47
  189. package/dist/recall-navigation.js +46 -46
  190. package/dist/recall-state.js +2 -2
  191. package/dist/reconcile/manifest.js +3 -3
  192. package/dist/{resolution-NNYG37YB.js → resolution-BNWE5Y25.js} +6 -6
  193. package/dist/resume-bundles.js +22 -22
  194. package/dist/salvage-envelope.js +5 -5
  195. package/dist/semantic-consolidation.js +15 -15
  196. package/dist/semantic-rule-promotion.js +14 -14
  197. package/dist/semantic-rule-verifier.js +14 -14
  198. package/dist/source-agent-qualifier.js +17 -17
  199. package/dist/standup/build.js +14 -14
  200. package/dist/storage/buffer-drain-claims.js +2 -2
  201. package/dist/storage/buffer-session-files.js +2 -2
  202. package/dist/storage.d.ts +1 -0
  203. package/dist/storage.d.ts.map +1 -1
  204. package/dist/storage.js +13 -13
  205. package/dist/subject-config.js +4 -4
  206. package/dist/summarizer.js +3 -3
  207. package/dist/summary-snapshot.js +2 -2
  208. package/dist/support-passport/index.js +49 -49
  209. package/dist/transfer/capsule-import.js +4 -4
  210. package/dist/transfer/export-okf.js +14 -14
  211. package/dist/utils/serialize-mutations.d.ts +13 -0
  212. package/dist/utils/serialize-mutations.d.ts.map +1 -1
  213. package/dist/utils/serialize-mutations.js +3 -1
  214. package/dist/verified-recall.js +14 -14
  215. package/dist/who-knows-cli.js +15 -15
  216. package/dist/who-knows.js +14 -14
  217. package/dist/write-envelope.js +4 -4
  218. package/package.json +2 -2
  219. package/src/orchestration/extraction-run-helpers.ts +33 -52
  220. package/src/orchestration/extraction-run.ts +54 -54
  221. package/src/orchestrator-flush.test.ts +103 -79
  222. package/src/storage-update-meta.test.ts +114 -0
  223. package/src/storage.ts +7 -6
  224. package/src/utils/serialize-mutations.ts +53 -0
  225. package/dist/chunk-264B57GV.js.map +0 -1
  226. package/dist/chunk-6VBKU3FY.js.map +0 -1
  227. /package/dist/{auto-sync-UQQ6YJSB.js.map → auto-sync-ANOJDQD4.js.map} +0 -0
  228. /package/dist/{capsule-merge-D4BTIJGK.js.map → capsule-merge-XVTQJJ6R.js.map} +0 -0
  229. /package/dist/{chunk-S4OZKPBS.js.map → chunk-2D7BTSDW.js.map} +0 -0
  230. /package/dist/{chunk-WB3OFPM2.js.map → chunk-2DNFRLLL.js.map} +0 -0
  231. /package/dist/{chunk-EKPRLTAE.js.map → chunk-2RUTN7PR.js.map} +0 -0
  232. /package/dist/{chunk-EAALT5NB.js.map → chunk-2ZSRACNK.js.map} +0 -0
  233. /package/dist/{chunk-X4PAATLA.js.map → chunk-3LBDEGQA.js.map} +0 -0
  234. /package/dist/{chunk-2U7ZDC2E.js.map → chunk-3LSTL6EY.js.map} +0 -0
  235. /package/dist/{chunk-QCAJMIAE.js.map → chunk-434E4BTO.js.map} +0 -0
  236. /package/dist/{chunk-TPKB7T6Z.js.map → chunk-4XINO77L.js.map} +0 -0
  237. /package/dist/{chunk-EADUOZWH.js.map → chunk-5JABRT5G.js.map} +0 -0
  238. /package/dist/{chunk-6I6DFULH.js.map → chunk-5KDDAQ6K.js.map} +0 -0
  239. /package/dist/{chunk-YDIOMDBW.js.map → chunk-5QV6CE6K.js.map} +0 -0
  240. /package/dist/{chunk-QI7LLSWX.js.map → chunk-5UEGL5BF.js.map} +0 -0
  241. /package/dist/{chunk-TMWSCE2L.js.map → chunk-5ZHFPUBC.js.map} +0 -0
  242. /package/dist/{chunk-TUCEP2LS.js.map → chunk-63A2BU2B.js.map} +0 -0
  243. /package/dist/{chunk-HJ3UTGIW.js.map → chunk-6P7U3HWG.js.map} +0 -0
  244. /package/dist/{chunk-UEDC2VGP.js.map → chunk-7JYK7NNY.js.map} +0 -0
  245. /package/dist/{chunk-IWAB7JVH.js.map → chunk-7ZTGMEXF.js.map} +0 -0
  246. /package/dist/{chunk-QYAXMXOB.js.map → chunk-A7BM2JTH.js.map} +0 -0
  247. /package/dist/{chunk-HSHLDSPX.js.map → chunk-AUFPPBC3.js.map} +0 -0
  248. /package/dist/{chunk-QJ6IR35B.js.map → chunk-B7VD36TI.js.map} +0 -0
  249. /package/dist/{chunk-ECT5ME6X.js.map → chunk-BBHKN2CN.js.map} +0 -0
  250. /package/dist/{chunk-WA6XRNWE.js.map → chunk-BH7NVSEC.js.map} +0 -0
  251. /package/dist/{chunk-QIMGULGN.js.map → chunk-BNZAR6PO.js.map} +0 -0
  252. /package/dist/{chunk-C5OW2FFB.js.map → chunk-BOZQAUAJ.js.map} +0 -0
  253. /package/dist/{chunk-WJCXUE2J.js.map → chunk-BV2BRHD4.js.map} +0 -0
  254. /package/dist/{chunk-Z2T5BPOZ.js.map → chunk-CIEEYVUB.js.map} +0 -0
  255. /package/dist/{chunk-PA6ZNFB7.js.map → chunk-CLSKGTAT.js.map} +0 -0
  256. /package/dist/{chunk-YRL4PGPP.js.map → chunk-DRTSPRWL.js.map} +0 -0
  257. /package/dist/{chunk-ZRSXXWIX.js.map → chunk-E4QIX2OB.js.map} +0 -0
  258. /package/dist/{chunk-X2ZLHUEO.js.map → chunk-ECF627KC.js.map} +0 -0
  259. /package/dist/{chunk-XICJL56Z.js.map → chunk-EI6M5NVK.js.map} +0 -0
  260. /package/dist/{chunk-564YP3DW.js.map → chunk-ER7XANP3.js.map} +0 -0
  261. /package/dist/{chunk-ILMB3X4X.js.map → chunk-FKGLJJVS.js.map} +0 -0
  262. /package/dist/{chunk-L74VVEJA.js.map → chunk-FR7PK7ED.js.map} +0 -0
  263. /package/dist/{chunk-UXRCORZX.js.map → chunk-FUDKGJDN.js.map} +0 -0
  264. /package/dist/{chunk-GNEB3WXR.js.map → chunk-G6L3REL3.js.map} +0 -0
  265. /package/dist/{chunk-4P4VLPGB.js.map → chunk-GKL6QYZO.js.map} +0 -0
  266. /package/dist/{chunk-IMNJJKHW.js.map → chunk-GRAUQXQB.js.map} +0 -0
  267. /package/dist/{chunk-JVFYP2LD.js.map → chunk-GROHOWR5.js.map} +0 -0
  268. /package/dist/{chunk-LIUI46BX.js.map → chunk-H6VJSITJ.js.map} +0 -0
  269. /package/dist/{chunk-YGM47S7M.js.map → chunk-HFEGRMFG.js.map} +0 -0
  270. /package/dist/{chunk-A6JV3WHI.js.map → chunk-HG3CG557.js.map} +0 -0
  271. /package/dist/{chunk-7JFXXA57.js.map → chunk-HHL4MX3W.js.map} +0 -0
  272. /package/dist/{chunk-53OWLEAD.js.map → chunk-HNOSWPKG.js.map} +0 -0
  273. /package/dist/{chunk-IJKBIOWL.js.map → chunk-HOR4RMUM.js.map} +0 -0
  274. /package/dist/{chunk-WZEDFDJ6.js.map → chunk-HRRAOTOQ.js.map} +0 -0
  275. /package/dist/{chunk-A5L5KGDI.js.map → chunk-IAYUTGF2.js.map} +0 -0
  276. /package/dist/{chunk-WVEEVYMH.js.map → chunk-IGOW4TJE.js.map} +0 -0
  277. /package/dist/{chunk-MKE5DP3S.js.map → chunk-IHCXMYSE.js.map} +0 -0
  278. /package/dist/{chunk-SDXBR5QO.js.map → chunk-IZJQNDGI.js.map} +0 -0
  279. /package/dist/{chunk-BQOOUKEN.js.map → chunk-JL5HXL25.js.map} +0 -0
  280. /package/dist/{chunk-ZFU2B2GM.js.map → chunk-JPY3RMG2.js.map} +0 -0
  281. /package/dist/{chunk-TWRPWJJT.js.map → chunk-JRVMCZE3.js.map} +0 -0
  282. /package/dist/{chunk-FYPKSR6Z.js.map → chunk-KISOMGY2.js.map} +0 -0
  283. /package/dist/{chunk-J62O2UW5.js.map → chunk-KNMSD2MQ.js.map} +0 -0
  284. /package/dist/{chunk-MTVC5S5N.js.map → chunk-KO224R36.js.map} +0 -0
  285. /package/dist/{chunk-UFQB5VQ5.js.map → chunk-KQJUEQ2E.js.map} +0 -0
  286. /package/dist/{chunk-QNIYL5SK.js.map → chunk-KQLBXUUQ.js.map} +0 -0
  287. /package/dist/{chunk-W5EO4USJ.js.map → chunk-KQUSP6LB.js.map} +0 -0
  288. /package/dist/{chunk-BDNPW3KM.js.map → chunk-KYSSUY66.js.map} +0 -0
  289. /package/dist/{chunk-R25JMO5O.js.map → chunk-LDHR465K.js.map} +0 -0
  290. /package/dist/{chunk-CYEEVWQ6.js.map → chunk-LIGZO7XN.js.map} +0 -0
  291. /package/dist/{chunk-QKXPOLQD.js.map → chunk-M4IEZGCK.js.map} +0 -0
  292. /package/dist/{chunk-Q3DAWUAV.js.map → chunk-MRF5V5CD.js.map} +0 -0
  293. /package/dist/{chunk-HTTZ3GJ6.js.map → chunk-MUHGYV2V.js.map} +0 -0
  294. /package/dist/{chunk-HNZH57JK.js.map → chunk-N4T5FW6U.js.map} +0 -0
  295. /package/dist/{chunk-YLB7TW2B.js.map → chunk-NM2II57L.js.map} +0 -0
  296. /package/dist/{chunk-3VNRDRBM.js.map → chunk-OVL5AD3N.js.map} +0 -0
  297. /package/dist/{chunk-3HLWLYRD.js.map → chunk-P2EM5LKH.js.map} +0 -0
  298. /package/dist/{chunk-XL2EE5W3.js.map → chunk-PLCYKAWH.js.map} +0 -0
  299. /package/dist/{chunk-JIYJIH5N.js.map → chunk-QDZWQBLU.js.map} +0 -0
  300. /package/dist/{chunk-YXW4KXCV.js.map → chunk-QGJSBU4R.js.map} +0 -0
  301. /package/dist/{chunk-DM5QZ3YU.js.map → chunk-QGVCFVX6.js.map} +0 -0
  302. /package/dist/{chunk-VSHWPOUB.js.map → chunk-RJWL3RHB.js.map} +0 -0
  303. /package/dist/{chunk-WU5WTCH5.js.map → chunk-RZH5RVJC.js.map} +0 -0
  304. /package/dist/{chunk-76LKZ42K.js.map → chunk-SHHZWSSE.js.map} +0 -0
  305. /package/dist/{chunk-N7CATBYH.js.map → chunk-SNS3DWG6.js.map} +0 -0
  306. /package/dist/{chunk-IVJI5FDA.js.map → chunk-T22FBDG3.js.map} +0 -0
  307. /package/dist/{chunk-YH7HJNWV.js.map → chunk-TGQW3AMK.js.map} +0 -0
  308. /package/dist/{chunk-KQXRVELF.js.map → chunk-TIGYOZBU.js.map} +0 -0
  309. /package/dist/{chunk-Z45353ML.js.map → chunk-UH6ROXFJ.js.map} +0 -0
  310. /package/dist/{chunk-C2AYUXEF.js.map → chunk-UYUFDTIX.js.map} +0 -0
  311. /package/dist/{chunk-2AKIVSHP.js.map → chunk-W26D2USW.js.map} +0 -0
  312. /package/dist/{chunk-FZVZYU5C.js.map → chunk-W7K5BP3M.js.map} +0 -0
  313. /package/dist/{chunk-ZARHK7PT.js.map → chunk-WWH7GJ42.js.map} +0 -0
  314. /package/dist/{chunk-4NLHEE2Z.js.map → chunk-XLCEFUAU.js.map} +0 -0
  315. /package/dist/{chunk-PWEIZCPS.js.map → chunk-XOFIJGB2.js.map} +0 -0
  316. /package/dist/{chunk-TRSKATZQ.js.map → chunk-XZ66U5M3.js.map} +0 -0
  317. /package/dist/{chunk-UUZ7KZAO.js.map → chunk-Y33KJ556.js.map} +0 -0
  318. /package/dist/{chunk-BU4W2ISH.js.map → chunk-YJQJRXUI.js.map} +0 -0
  319. /package/dist/{chunk-QBK3IM6W.js.map → chunk-YSATDEBY.js.map} +0 -0
  320. /package/dist/{chunk-WR5POHQ3.js.map → chunk-YUTT4ZTI.js.map} +0 -0
  321. /package/dist/{chunk-JCL7RO2G.js.map → chunk-ZOBYTRBU.js.map} +0 -0
  322. /package/dist/{chunk-FMOUUWQN.js.map → chunk-ZPLQ27MY.js.map} +0 -0
  323. /package/dist/{entity-canonical-id-migration-CUDCKWPF.js.map → entity-canonical-id-migration-JJSTZF73.js.map} +0 -0
  324. /package/dist/{forget-E6ZTUKEC.js.map → forget-DE56ICCL.js.map} +0 -0
  325. /package/dist/{graph-edge-decay-2BZTY2IM.js.map → graph-edge-decay-OEX5OGMI.js.map} +0 -0
  326. /package/dist/{resolution-NNYG37YB.js.map → resolution-BNWE5Y25.js.map} +0 -0
@@ -13,6 +13,7 @@ import { SmartBuffer } from "./buffer.js";
13
13
  import { parseConfig } from "./config.js";
14
14
  import { stableHash } from "./coding/git-context.js";
15
15
  import type { BufferState, BufferTurn, PluginConfig } from "./types.js";
16
+ import type { MetaState } from "./types.js";
16
17
  import type { ImportTurn } from "./bulk-import/types.js";
17
18
  import type { LcmEngine } from "./lcm/index.js";
18
19
  import type { ExtractionRunResult } from "./orchestration/extraction-run.js";
@@ -20,6 +21,24 @@ import { namespaceIdentityToken } from "./namespaces/identity.js";
20
21
  import { readNamespaceMaintenanceStatuses } from "./maintenance/namespace-planner.js";
21
22
  import { stubPersistExtraction } from "./testing/orchestrator-lite.js";
22
23
 
24
+ /** Adds the `updateMeta` seam to a StorageManager stub that only has loadMeta/saveMeta. */
25
+ function withMetaSeam<S extends { loadMeta: () => Promise<MetaState>; saveMeta: (meta: MetaState) => Promise<void> }>(
26
+ storage: S,
27
+ ): S & { updateMeta: (mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>) => Promise<void> } {
28
+ // SAFETY: the constraint above declares exactly the loadMeta/saveMeta shape every
29
+ // stub here provides; re-typing to `S & { updateMeta }` adds one member and keeps
30
+ // all other members of S unchanged.
31
+ const updatable = storage as S & {
32
+ updateMeta: (mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>) => Promise<void>;
33
+ };
34
+ updatable.updateMeta = async (mutate) => {
35
+ const meta = await storage.loadMeta();
36
+ if ((await mutate(meta)) === false) return;
37
+ await storage.saveMeta(meta);
38
+ };
39
+ return updatable;
40
+ }
41
+
23
42
  function makeTurn(sessionKey: string, content: string, sessionOwnerPrincipal?: string): BufferTurn {
24
43
  return {
25
44
  role: "user",
@@ -1525,7 +1544,7 @@ test("runExtraction writes buffered turns to active scope profile write layer",
1525
1544
  orchestrator.storageRouter = {
1526
1545
  storageFor: async (namespace: string) => {
1527
1546
  requestedNamespace = namespace;
1528
- return {
1547
+ return withMetaSeam({
1529
1548
  listEntityNames: async () => [],
1530
1549
  loadMeta: async () => ({
1531
1550
  extractionCount: 0,
@@ -1541,7 +1560,7 @@ test("runExtraction writes buffered turns to active scope profile write layer",
1541
1560
  ],
1542
1561
  }),
1543
1562
  saveMeta: async () => undefined,
1544
- };
1563
+ });
1545
1564
  },
1546
1565
  };
1547
1566
  orchestrator.extraction = {
@@ -1570,7 +1589,7 @@ test("runExtraction skips batches whose persisted fingerprint already exists in
1570
1589
  },
1571
1590
  };
1572
1591
  orchestrator.storageRouter = {
1573
- storageFor: async () => ({
1592
+ storageFor: async () => withMetaSeam({
1574
1593
  listEntityNames: async () => [],
1575
1594
  loadMeta: async () => ({
1576
1595
  extractionCount: 0,
@@ -1651,7 +1670,7 @@ test("runExtraction fails closed on invalid extraction results when required", a
1651
1670
  },
1652
1671
  };
1653
1672
  orchestrator.storageRouter = {
1654
- storageFor: async () => ({
1673
+ storageFor: async () => withMetaSeam({
1655
1674
  listEntityNames: async () => [],
1656
1675
  loadMeta: async () => ({
1657
1676
  extractionCount: 0,
@@ -1689,18 +1708,7 @@ test("runExtraction persists processed fingerprints for empty extraction results
1689
1708
 
1690
1709
  let clearCalls = 0;
1691
1710
  let saveMetaCalls = 0;
1692
- let savedMeta:
1693
- | {
1694
- extractionCount: number;
1695
- lastExtractionAt: string | null;
1696
- totalMemories: number;
1697
- totalEntities: number;
1698
- processedExtractionFingerprints: Array<{
1699
- fingerprint: string;
1700
- observedAt: string;
1701
- }>;
1702
- }
1703
- | undefined;
1711
+ let savedMeta: MetaState | undefined;
1704
1712
 
1705
1713
  const meta = {
1706
1714
  extractionCount: 0,
@@ -1722,10 +1730,10 @@ test("runExtraction persists processed fingerprints for empty extraction results
1722
1730
  },
1723
1731
  };
1724
1732
  orchestrator.storageRouter = {
1725
- storageFor: async () => ({
1733
+ storageFor: async () => withMetaSeam({
1726
1734
  listEntityNames: async () => [],
1727
1735
  loadMeta: async () => meta,
1728
- saveMeta: async (nextMeta: typeof meta) => {
1736
+ saveMeta: async (nextMeta: MetaState) => {
1729
1737
  saveMetaCalls += 1;
1730
1738
  savedMeta = structuredClone(nextMeta);
1731
1739
  },
@@ -1762,9 +1770,9 @@ test("runExtraction persists processed fingerprints for empty extraction results
1762
1770
  assert.equal(savedMeta?.extractionCount, 1);
1763
1771
  assert.equal(savedMeta?.totalMemories, 0);
1764
1772
  assert.equal(savedMeta?.totalEntities, 0);
1765
- assert.equal(savedMeta?.processedExtractionFingerprints.length, 1);
1773
+ assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
1766
1774
  assert.equal(
1767
- savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
1775
+ savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
1768
1776
  orchestrator.buildExtractionFingerprint(turns, "logical-thread:empty"),
1769
1777
  );
1770
1778
  });
@@ -1797,7 +1805,7 @@ test("runExtraction does not persist processed fingerprints for failed empty ext
1797
1805
  },
1798
1806
  };
1799
1807
  orchestrator.storageRouter = {
1800
- storageFor: async () => ({
1808
+ storageFor: async () => withMetaSeam({
1801
1809
  listEntityNames: async () => [],
1802
1810
  loadMeta: async () => meta,
1803
1811
  saveMeta: async () => {
@@ -1853,7 +1861,7 @@ test("runExtraction clears the buffer on a failed extraction when extractionRetr
1853
1861
  orchestrator.config = config;
1854
1862
  orchestrator.buffer = { clearAfterExtraction: async () => { clearCalls += 1; } };
1855
1863
  orchestrator.storageRouter = {
1856
- storageFor: async () => ({
1864
+ storageFor: async () => withMetaSeam({
1857
1865
  listEntityNames: async () => [],
1858
1866
  loadMeta: async () => ({ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null, totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [] }),
1859
1867
  saveMeta: async () => {},
@@ -1870,6 +1878,45 @@ test("runExtraction clears the buffer on a failed extraction when extractionRetr
1870
1878
  assert.equal(clearCalls, 1, "retry disabled → buffer cleared (pre-#1908 behavior)");
1871
1879
  });
1872
1880
 
1881
+ test("runExtraction retains buffers and keeps in-memory backoff when the meta lock is unavailable", async () => {
1882
+ // Lock contention makes updateMeta fail closed without running the mutator.
1883
+ // The retry gate reads the in-memory mirror, so the failure must still park
1884
+ // backoff there and retain the buffer — a cleared buffer with no retry state
1885
+ // silently drops the turns (base behavior retained them).
1886
+ const config = parseConfig({});
1887
+ config.extractionMinChars = 0;
1888
+ config.extractionMinUserTurns = 1;
1889
+
1890
+ let clearCalls = 0;
1891
+ let updateMetaCalls = 0;
1892
+ // SAFETY: runExtraction is driven only through the stub members assigned below;
1893
+ // the prototype harness leaves the rest of Orchestrator unreachable in this test.
1894
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
1895
+ orchestrator.config = config;
1896
+ orchestrator.buffer = { clearAfterExtraction: async () => { clearCalls += 1; } };
1897
+ orchestrator.storageRouter = {
1898
+ storageFor: async () => ({
1899
+ listEntityNames: async () => [],
1900
+ loadMeta: async () => ({ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null, totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [] }),
1901
+ saveMeta: async () => {},
1902
+ updateMeta: async () => {
1903
+ updateMetaCalls += 1;
1904
+ throw new Error("meta update skipped: meta.json.lock not acquired (test)");
1905
+ },
1906
+ }),
1907
+ };
1908
+ orchestrator.extraction = {
1909
+ extract: async () => ({ facts: [], entities: [], questions: [], profileUpdates: [], extractionFailure: "gateway_unavailable" }),
1910
+ };
1911
+ stubPersistExtraction(orchestrator);
1912
+ const turns = [{ ...makeTurn("session-lock-fail", "failed gateway under lock contention"), logicalSessionKey: "logical-thread:lock-fail", turnFingerprint: "fp-lock-fail", persistProcessedFingerprint: true }];
1913
+ const result = await orchestrator.runExtraction(turns, { bufferKey: "logical-thread:lock-fail" });
1914
+ assert.equal(result.status, "skipped");
1915
+ assert.equal(result.reason, "empty_extraction_result");
1916
+ assert.equal(updateMetaCalls, 1);
1917
+ assert.equal(clearCalls, 0, "buffer must be retained when retry state could not persist");
1918
+ });
1919
+
1873
1920
  test("runExtraction preserves empty-result buffers when fingerprint persistence fails", async () => {
1874
1921
  const config = parseConfig({});
1875
1922
  config.extractionMinChars = 0;
@@ -1897,7 +1944,7 @@ test("runExtraction preserves empty-result buffers when fingerprint persistence
1897
1944
  },
1898
1945
  };
1899
1946
  orchestrator.storageRouter = {
1900
- storageFor: async () => ({
1947
+ storageFor: async () => withMetaSeam({
1901
1948
  listEntityNames: async () => [],
1902
1949
  loadMeta: async () => meta,
1903
1950
  saveMeta: async () => {
@@ -1961,7 +2008,7 @@ test("runExtraction keeps the empty live path fail-open when liveness metadata w
1961
2008
  },
1962
2009
  };
1963
2010
  orchestrator.storageRouter = {
1964
- storageFor: async () => ({
2011
+ storageFor: async () => withMetaSeam({
1965
2012
  listEntityNames: async () => [],
1966
2013
  loadMeta: async () => meta,
1967
2014
  saveMeta: async () => {
@@ -2003,7 +2050,7 @@ test("runExtraction preserves deduped buffers when the caller aborts during meta
2003
2050
  },
2004
2051
  };
2005
2052
  orchestrator.storageRouter = {
2006
- storageFor: async () => ({
2053
+ storageFor: async () => withMetaSeam({
2007
2054
  listEntityNames: async () => [],
2008
2055
  loadMeta: async () => {
2009
2056
  abortController.abort();
@@ -2075,7 +2122,7 @@ test("runExtraction still clears the buffer when fingerprint persistence fails a
2075
2122
  },
2076
2123
  };
2077
2124
  orchestrator.storageRouter = {
2078
- storageFor: async () => ({
2125
+ storageFor: async () => withMetaSeam({
2079
2126
  listEntityNames: async () => [],
2080
2127
  loadMeta: async () => ({
2081
2128
  extractionCount: 0,
@@ -2138,18 +2185,7 @@ test("runExtraction persists fingerprint and extraction counters through one coh
2138
2185
  let clearCalls = 0;
2139
2186
  let loadMetaCalls = 0;
2140
2187
  let saveMetaCalls = 0;
2141
- let savedMeta:
2142
- | {
2143
- extractionCount: number;
2144
- lastExtractionAt: string | null;
2145
- totalMemories: number;
2146
- totalEntities: number;
2147
- processedExtractionFingerprints: Array<{
2148
- fingerprint: string;
2149
- observedAt: string;
2150
- }>;
2151
- }
2152
- | undefined;
2188
+ let savedMeta: MetaState | undefined;
2153
2189
 
2154
2190
  const meta = {
2155
2191
  extractionCount: 0,
@@ -2171,13 +2207,13 @@ test("runExtraction persists fingerprint and extraction counters through one coh
2171
2207
  },
2172
2208
  };
2173
2209
  orchestrator.storageRouter = {
2174
- storageFor: async () => ({
2210
+ storageFor: async () => withMetaSeam({
2175
2211
  listEntityNames: async () => [],
2176
2212
  loadMeta: async () => {
2177
2213
  loadMetaCalls += 1;
2178
2214
  return meta;
2179
2215
  },
2180
- saveMeta: async (nextMeta: typeof meta) => {
2216
+ saveMeta: async (nextMeta: MetaState) => {
2181
2217
  saveMetaCalls += 1;
2182
2218
  savedMeta = structuredClone(nextMeta);
2183
2219
  },
@@ -2216,14 +2252,17 @@ test("runExtraction persists fingerprint and extraction counters through one coh
2216
2252
  },
2217
2253
  );
2218
2254
 
2219
- assert.equal(loadMetaCalls, 1);
2255
+ // The counters and fingerprint land on a FRESHLY loaded meta (the serialized
2256
+ // updateMeta re-read), so more than one load is expected; the single save is
2257
+ // the coherent-write contract under test.
2258
+ assert.ok(loadMetaCalls >= 2, 'expected a fresh meta read before the save');
2220
2259
  assert.equal(saveMetaCalls, 1);
2221
2260
  assert.equal(clearCalls, 1);
2222
2261
  assert.equal(savedMeta?.extractionCount, 1);
2223
2262
  assert.equal(savedMeta?.totalMemories, 1);
2224
- assert.equal(savedMeta?.processedExtractionFingerprints.length, 1);
2263
+ assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
2225
2264
  assert.equal(
2226
- savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
2265
+ savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
2227
2266
  orchestrator.buildExtractionFingerprint(
2228
2267
  [
2229
2268
  {
@@ -2246,18 +2285,7 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
2246
2285
  let clearCalls = 0;
2247
2286
  let loadMetaCalls = 0;
2248
2287
  let saveMetaCalls = 0;
2249
- let savedMeta:
2250
- | {
2251
- extractionCount: number;
2252
- lastExtractionAt: string | null;
2253
- totalMemories: number;
2254
- totalEntities: number;
2255
- processedExtractionFingerprints: Array<{
2256
- fingerprint: string;
2257
- observedAt: string;
2258
- }>;
2259
- }
2260
- | undefined;
2288
+ let savedMeta: MetaState | undefined;
2261
2289
 
2262
2290
  const meta = {
2263
2291
  extractionCount: 0,
@@ -2279,13 +2307,13 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
2279
2307
  },
2280
2308
  };
2281
2309
  orchestrator.storageRouter = {
2282
- storageFor: async () => ({
2310
+ storageFor: async () => withMetaSeam({
2283
2311
  listEntityNames: async () => [],
2284
2312
  loadMeta: async () => {
2285
2313
  loadMetaCalls += 1;
2286
2314
  return meta;
2287
2315
  },
2288
- saveMeta: async (nextMeta: typeof meta) => {
2316
+ saveMeta: async (nextMeta: MetaState) => {
2289
2317
  saveMetaCalls += 1;
2290
2318
  savedMeta = structuredClone(nextMeta);
2291
2319
  },
@@ -2314,12 +2342,15 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
2314
2342
  bufferKey: "logical-thread:thread-15",
2315
2343
  });
2316
2344
 
2317
- assert.equal(loadMetaCalls, 1);
2345
+ // The counters and fingerprint land on a FRESHLY loaded meta (the serialized
2346
+ // updateMeta re-read), so more than one load is expected; the single save is
2347
+ // the coherent-write contract under test.
2348
+ assert.ok(loadMetaCalls >= 2, 'expected a fresh meta read before the save');
2318
2349
  assert.equal(saveMetaCalls, 1);
2319
2350
  assert.equal(clearCalls, 1);
2320
2351
  assert.equal(savedMeta?.extractionCount, 1);
2321
2352
  assert.equal(savedMeta?.totalMemories, 1);
2322
- assert.equal(savedMeta?.processedExtractionFingerprints.length, 0);
2353
+ assert.equal(savedMeta?.processedExtractionFingerprints?.length, 0);
2323
2354
  });
2324
2355
 
2325
2356
  test("runExtraction completes after late threading failures and saves the processed fingerprint", async () => {
@@ -2330,18 +2361,7 @@ test("runExtraction completes after late threading failures and saves the proces
2330
2361
 
2331
2362
  let clearCalls = 0;
2332
2363
  let saveMetaCalls = 0;
2333
- let savedMeta:
2334
- | {
2335
- extractionCount: number;
2336
- lastExtractionAt: string | null;
2337
- totalMemories: number;
2338
- totalEntities: number;
2339
- processedExtractionFingerprints: Array<{
2340
- fingerprint: string;
2341
- observedAt: string;
2342
- }>;
2343
- }
2344
- | undefined;
2364
+ let savedMeta: MetaState | undefined;
2345
2365
 
2346
2366
  const meta = {
2347
2367
  extractionCount: 0,
@@ -2363,10 +2383,10 @@ test("runExtraction completes after late threading failures and saves the proces
2363
2383
  },
2364
2384
  };
2365
2385
  orchestrator.storageRouter = {
2366
- storageFor: async () => ({
2386
+ storageFor: async () => withMetaSeam({
2367
2387
  listEntityNames: async () => [],
2368
2388
  loadMeta: async () => meta,
2369
- saveMeta: async (nextMeta: typeof meta) => {
2389
+ saveMeta: async (nextMeta: MetaState) => {
2370
2390
  saveMetaCalls += 1;
2371
2391
  savedMeta = structuredClone(nextMeta);
2372
2392
  },
@@ -2420,9 +2440,9 @@ test("runExtraction completes after late threading failures and saves the proces
2420
2440
  assert.equal(result.postPersistMetadataFailed, false);
2421
2441
  assert.equal(saveMetaCalls, 1);
2422
2442
  assert.equal(clearCalls, 1);
2423
- assert.equal(savedMeta?.processedExtractionFingerprints.length, 1);
2443
+ assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
2424
2444
  assert.equal(
2425
- savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
2445
+ savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
2426
2446
  orchestrator.buildExtractionFingerprint(
2427
2447
  [
2428
2448
  {
@@ -2465,7 +2485,7 @@ test("runExtraction completes and clears the buffer when the post-persist meta s
2465
2485
  },
2466
2486
  };
2467
2487
  orchestrator.storageRouter = {
2468
- storageFor: async () => ({
2488
+ storageFor: async () => withMetaSeam({
2469
2489
  listEntityNames: async () => [],
2470
2490
  loadMeta: async () => meta,
2471
2491
  saveMeta: async () => {
@@ -2549,7 +2569,7 @@ test("runExtraction still runs follow-on extraction helpers when the post-persis
2549
2569
  },
2550
2570
  };
2551
2571
  orchestrator.storageRouter = {
2552
- storageFor: async () => ({
2572
+ storageFor: async () => withMetaSeam({
2553
2573
  listEntityNames: async () => [],
2554
2574
  loadMeta: async () => meta,
2555
2575
  saveMeta: async () => {
@@ -2654,13 +2674,15 @@ test("runExtraction aborts before late buffer clearing when the caller cancels",
2654
2674
  },
2655
2675
  };
2656
2676
  orchestrator.storageRouter = {
2657
- storageFor: async () => ({
2677
+ storageFor: async () => withMetaSeam({
2658
2678
  listEntityNames: async () => [],
2659
2679
  loadMeta: async () => ({
2660
2680
  extractionCount: 0,
2661
2681
  lastExtractionAt: null,
2682
+ lastConsolidationAt: null,
2662
2683
  totalMemories: 0,
2663
2684
  totalEntities: 0,
2685
+ processedExtractionFingerprints: [],
2664
2686
  }),
2665
2687
  saveMeta: async () => undefined,
2666
2688
  }),
@@ -2716,13 +2738,15 @@ test("runExtraction still clears the session buffer after persistence even if re
2716
2738
  },
2717
2739
  };
2718
2740
  orchestrator.storageRouter = {
2719
- storageFor: async () => ({
2741
+ storageFor: async () => withMetaSeam({
2720
2742
  listEntityNames: async () => [],
2721
2743
  loadMeta: async () => ({
2722
2744
  extractionCount: 0,
2723
2745
  lastExtractionAt: null,
2746
+ lastConsolidationAt: null,
2724
2747
  totalMemories: 0,
2725
2748
  totalEntities: 0,
2749
+ processedExtractionFingerprints: [],
2726
2750
  }),
2727
2751
  saveMeta: async () => undefined,
2728
2752
  }),
@@ -0,0 +1,114 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { StorageManager } from "./storage.js";
7
+ import { withHeldFileLock } from "./utils/serialize-mutations.js";
8
+
9
+ // Real wall-clock is deliberate here: the lock helper's acquire budget and
10
+ // heartbeat polling run on platform timers, and the contention windows below
11
+ // exist to bracket updateMeta's real 2s acquire budget. Fake timers cannot
12
+ // drive the helper's internal polling loop.
13
+ function delay(ms: number): Promise<void> {
14
+ const { promise, resolve } = Promise.withResolvers<void>();
15
+ setTimeout(resolve, ms);
16
+ return promise;
17
+ }
18
+
19
+ /** Await the real signal "holder owns the lock file", not a guessed duration. */
20
+ async function waitForLockFile(lockPath: string): Promise<void> {
21
+ const deadline = Date.now() + 2_000;
22
+ for (;;) {
23
+ try {
24
+ await stat(lockPath);
25
+ return;
26
+ } catch {
27
+ if (Date.now() >= deadline) throw new Error(`lock file never appeared: ${lockPath}`);
28
+ await delay(20);
29
+ }
30
+ }
31
+ }
32
+
33
+ async function makeStorage(): Promise<{
34
+ storage: StorageManager;
35
+ lockPath: string;
36
+ cleanup: () => Promise<void>;
37
+ }> {
38
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-meta-lock-"));
39
+ await mkdir(path.join(root, "state"), { recursive: true });
40
+ return {
41
+ storage: new StorageManager(root),
42
+ lockPath: path.join(root, "state", "meta.json.lock"),
43
+ cleanup: () => rm(root, { recursive: true, force: true }),
44
+ };
45
+ }
46
+
47
+ test("updateMeta fails closed when meta.json.lock stays held past the acquire budget", async () => {
48
+ const { storage, lockPath, cleanup } = await makeStorage();
49
+ try {
50
+ await storage.updateMeta((meta) => {
51
+ meta.extractionCount = 1;
52
+ });
53
+
54
+ // A live external holder (a different owner id through the same helper the
55
+ // out-of-process drain workers use) keeps the lock past updateMeta's 2s
56
+ // wait budget. The update must fail instead of writing unlocked.
57
+ const hold = withHeldFileLock(
58
+ lockPath,
59
+ { staleMs: 30_000, maxWaitMs: 30_000, pollMs: 50 },
60
+ async () => {
61
+ await delay(2_600);
62
+ },
63
+ );
64
+ await waitForLockFile(lockPath);
65
+
66
+ await assert.rejects(
67
+ storage.updateMeta((meta) => {
68
+ meta.extractionCount = 2;
69
+ }),
70
+ /not acquired/,
71
+ );
72
+ assert.equal(
73
+ (await storage.loadMeta()).extractionCount,
74
+ 1,
75
+ "a timed-out update must not perform an unlocked read-modify-write",
76
+ );
77
+
78
+ await hold;
79
+ await storage.updateMeta((meta) => {
80
+ meta.extractionCount = 3;
81
+ });
82
+ assert.equal((await storage.loadMeta()).extractionCount, 3);
83
+ } finally {
84
+ await cleanup();
85
+ }
86
+ });
87
+
88
+ test("concurrent updateMeta additive updates both survive short external contention", async () => {
89
+ const { storage, lockPath, cleanup } = await makeStorage();
90
+ try {
91
+ const hold = withHeldFileLock(
92
+ lockPath,
93
+ { staleMs: 30_000, maxWaitMs: 30_000, pollMs: 50 },
94
+ async () => {
95
+ await delay(300);
96
+ },
97
+ );
98
+ await waitForLockFile(lockPath);
99
+ await Promise.all([
100
+ storage.updateMeta((meta) => {
101
+ meta.totalMemories += 5;
102
+ }),
103
+ storage.updateMeta((meta) => {
104
+ meta.totalEntities += 7;
105
+ }),
106
+ ]);
107
+ await hold;
108
+ const meta = await storage.loadMeta();
109
+ assert.equal(meta.totalMemories, 5);
110
+ assert.equal(meta.totalEntities, 7);
111
+ } finally {
112
+ await cleanup();
113
+ }
114
+ });
package/src/storage.ts CHANGED
@@ -34,6 +34,7 @@ import { appendContextTransformRecords, readContextTransformRecords } from "./st
34
34
  import type { ContextTransformTelemetryRecord } from "./active-context-transform.js";
35
35
  import { readMaybeEncryptedLines, readMemoryActionEventRowsFromLines } from "./storage/secure-line-reader.js";
36
36
  import { parseOfflineSyncDigestCache, type OfflineSyncDigestCacheEntry } from "./offline-sync-digest-cache.js";
37
+ import { updateMetaSerialized, withHeldFileLock } from "./utils/serialize-mutations.js";
37
38
  import {
38
39
  appendLifecycleEventsSerialized,
39
40
  type DrainPendingLifecycleForSyncResult,
@@ -5558,12 +5559,8 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
5558
5559
  if (err instanceof SecureStoreLockedError) throw err;
5559
5560
  if (!isErrnoCode(err, "ENOENT")) throw err;
5560
5561
  return {
5561
- extractionCount: 0,
5562
- lastExtractionAt: null,
5563
- lastConsolidationAt: null,
5564
- totalMemories: 0,
5565
- totalEntities: 0,
5566
- processedExtractionFingerprints: [],
5562
+ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null,
5563
+ totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [],
5567
5564
  };
5568
5565
  }
5569
5566
  }
@@ -5573,6 +5570,10 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
5573
5570
  await this.writeStorageSecureFile(path.join(this.stateDir, "meta.json"), JSON.stringify(state, null, 2));
5574
5571
  }
5575
5572
 
5573
+ async updateMeta(mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>): Promise<void> {
5574
+ await updateMetaSerialized(this.stateDir, () => this.loadMeta(), (meta) => this.saveMeta(meta), mutate);
5575
+ }
5576
+
5576
5577
  async appendMemoryActionEvents(events: MemoryActionEvent[]): Promise<number> {
5577
5578
  if (events.length === 0) return 0;
5578
5579
  await this.ensureDirectories();
@@ -733,3 +733,56 @@ async function refreshHeldLock(
733
733
  }
734
734
  return lockHeldBySelf(held);
735
735
  }
736
+
737
+ /**
738
+ * Process-wide chains serializing meta updates per state dir inside one
739
+ * process. The cross-process advisory lock below only orders DIFFERENT
740
+ * processes; without the chain, two same-process updates would contend on the
741
+ * lock file and one could time out to failure.
742
+ */
743
+ const metaUpdateChains = new Map<string, Promise<unknown>>();
744
+
745
+ /**
746
+ * Serialized meta read-modify-write for one state dir (`<stateDir>/meta.json`).
747
+ * Parallel extraction workers (the daemon's bounded queue and out-of-process
748
+ * buffer-drain workers) each load meta, mutate counters, fingerprints or retry
749
+ * state, and save; unlocked, a slow writer's save silently reverts a fast
750
+ * writer's update. The chain orders same-process callers and the advisory lock
751
+ * file (`meta.json.lock`) orders different processes; a worker that cannot
752
+ * take the lock inside the budget FAILS the update instead of writing
753
+ * unlocked — callers are fail-open, so the outcome is a logged miss retried by
754
+ * the next extraction, never a silent lost update. The mutator may return
755
+ * `false` to skip the save (nothing changed).
756
+ */
757
+ export async function updateMetaSerialized<M>(
758
+ dir: string,
759
+ loadMeta: () => Promise<M>,
760
+ saveMeta: (meta: M) => Promise<void>,
761
+ mutate: (meta: M) => boolean | void | Promise<boolean | void>,
762
+ ): Promise<void> {
763
+ const previous = metaUpdateChains.get(dir) ?? Promise.resolve();
764
+ const run = previous.then(() =>
765
+ withHeldFileLock(
766
+ path.join(dir, "meta.json.lock"),
767
+ // Bounded: a meta update is a short read-merge-write, never a scan.
768
+ { staleMs: 5_000, maxWaitMs: 2_000, pollMs: 50 },
769
+ async (acquired) => {
770
+ // A lock we could not take (busy peer past the wait budget, or a
771
+ // filesystem error) must NOT fall back to an unlocked
772
+ // read-modify-write: that silently reintroduces the lost-update race
773
+ // this function exists to close. Fail instead — every caller treats
774
+ // meta updates as fail-open, so the outcome is a logged miss that
775
+ // the next extraction retries, never a silent clobber.
776
+ if (!acquired) {
777
+ throw new Error(`meta update skipped: meta.json.lock not acquired (${dir})`);
778
+ }
779
+ const meta = await loadMeta();
780
+ if ((await mutate(meta)) === false) return;
781
+ await saveMeta(meta);
782
+ },
783
+ ),
784
+ );
785
+ // Every waiter keeps its own result/error; the chain only carries completion.
786
+ metaUpdateChains.set(dir, run.then(() => undefined, () => undefined));
787
+ await run;
788
+ }