@remnic/core 10.0.6 → 10.0.7

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 (364) hide show
  1. package/dist/access-admin-ops-surface.js +53 -52
  2. package/dist/access-authorization-probe.js +54 -53
  3. package/dist/access-boundary.js +53 -52
  4. package/dist/access-cli.js +104 -103
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +53 -52
  7. package/dist/access-http-briefing.js +54 -53
  8. package/dist/access-http-query.js +55 -54
  9. package/dist/access-http.js +73 -72
  10. package/dist/access-idempotency.d.ts.map +1 -1
  11. package/dist/access-idempotency.js +2 -1
  12. package/dist/access-identity-continuity-surface.js +53 -52
  13. package/dist/access-lcm-surface.js +53 -52
  14. package/dist/access-mcp.js +65 -64
  15. package/dist/access-namespace-preflight.js +53 -52
  16. package/dist/access-observe-write-category.js +12 -11
  17. package/dist/access-observe-write-surface.js +53 -52
  18. package/dist/access-offline-manifest.js +11 -10
  19. package/dist/access-operations-batch.js +57 -56
  20. package/dist/access-operations.js +64 -63
  21. package/dist/access-recall-concurrency.js +53 -52
  22. package/dist/access-recall-response.js +53 -52
  23. package/dist/access-recall-surface.js +53 -52
  24. package/dist/access-schema.js +7 -6
  25. package/dist/access-service.js +53 -52
  26. package/dist/access-surface-catalog.js +55 -54
  27. package/dist/access-surface-catalog.js.map +1 -1
  28. package/dist/active-recall.js +23 -22
  29. package/dist/active-recall.js.map +1 -1
  30. package/dist/{auto-sync-FN7E3OTO.js → auto-sync-WSJAOLOP.js} +8 -7
  31. package/dist/{auto-sync-FN7E3OTO.js.map → auto-sync-WSJAOLOP.js.map} +1 -1
  32. package/dist/briefing.js +16 -15
  33. package/dist/buffer.js +11 -10
  34. package/dist/{capsule-merge-42GJGDUC.js → capsule-merge-M4HETD27.js} +5 -4
  35. package/dist/{capsule-merge-42GJGDUC.js.map → capsule-merge-M4HETD27.js.map} +1 -1
  36. package/dist/causal-behavior.js +2 -2
  37. package/dist/causal-chain.js +2 -2
  38. package/dist/causal-consolidation.js +19 -18
  39. package/dist/causal-consolidation.js.map +1 -1
  40. package/dist/causal-retrieval.js +2 -2
  41. package/dist/causal-trajectory-graph.js +4 -3
  42. package/dist/causal-trajectory-graph.js.map +1 -1
  43. package/dist/causal-trajectory.js +1 -1
  44. package/dist/{chunk-OZABSIOV.js → chunk-2AFHZTPI.js} +2 -2
  45. package/dist/{chunk-UK5EH7AI.js → chunk-2CXQZXWM.js} +3 -3
  46. package/dist/{chunk-RIEGCK2W.js → chunk-2KRNPFPK.js} +2 -2
  47. package/dist/{chunk-N7QV3CMX.js → chunk-2NCBDGND.js} +2 -2
  48. package/dist/{chunk-FLV7M7CT.js → chunk-2PBZBNY7.js} +2 -2
  49. package/dist/{chunk-KSUOM3YU.js → chunk-2SKI5ZSH.js} +11 -11
  50. package/dist/{chunk-6C76FZRM.js → chunk-3DSRYRGR.js} +2 -2
  51. package/dist/{chunk-JA72X46J.js → chunk-3GLQUOL2.js} +2 -2
  52. package/dist/{chunk-CLCMDKTB.js → chunk-3I3U6FFT.js} +2 -2
  53. package/dist/{chunk-BNMI3JBV.js → chunk-4EHA327M.js} +3 -3
  54. package/dist/{chunk-QWUJWBZ5.js → chunk-4G4B3Z2B.js} +2 -2
  55. package/dist/{chunk-R7QY5ZVB.js → chunk-4NDWE44P.js} +2 -2
  56. package/dist/{chunk-U6HWKOGD.js → chunk-5GN5LRQW.js} +2 -2
  57. package/dist/{chunk-VFUEZZBS.js → chunk-5JMI26Q7.js} +5 -4
  58. package/dist/chunk-5JMI26Q7.js.map +1 -0
  59. package/dist/{chunk-MQNOIH2C.js → chunk-5O4UJ6PM.js} +3 -3
  60. package/dist/{chunk-HRZZJ4XB.js → chunk-645A37SC.js} +2 -2
  61. package/dist/{chunk-JA54KIYD.js → chunk-65FYHYBE.js} +2 -2
  62. package/dist/{chunk-CHDGJJV6.js → chunk-6VAHJ2F6.js} +2 -2
  63. package/dist/chunk-7LEQREP2.js +9 -0
  64. package/dist/chunk-7LEQREP2.js.map +1 -0
  65. package/dist/{chunk-TZQOCQXB.js → chunk-AL6EHLB5.js} +2 -2
  66. package/dist/{chunk-D6M3VKMY.js → chunk-B2UK6O2O.js} +3 -3
  67. package/dist/{chunk-VZBW6CDG.js → chunk-BOOFTFFE.js} +1 -1
  68. package/dist/{chunk-YJEUHGZZ.js → chunk-BR75HFPC.js} +2 -2
  69. package/dist/{chunk-GF4H3B3R.js → chunk-BSWHZJSP.js} +3 -3
  70. package/dist/{chunk-2U3R3DDH.js → chunk-BTJ6CAIA.js} +2 -2
  71. package/dist/{chunk-SNXBXYUE.js → chunk-CG2E6WT5.js} +3 -3
  72. package/dist/{chunk-GE6HETIF.js → chunk-CRYAYFUE.js} +2 -2
  73. package/dist/{chunk-GEGUBU3D.js → chunk-D6C4JWSN.js} +3 -3
  74. package/dist/{chunk-2UF42WXV.js → chunk-DSVRW5BD.js} +15 -15
  75. package/dist/{chunk-7LYRFI5A.js → chunk-E3OSDZ6M.js} +2 -2
  76. package/dist/{chunk-2COXDQ6C.js → chunk-E6KXJSMA.js} +2 -2
  77. package/dist/{chunk-6KHF6PAB.js → chunk-EBQCVOGC.js} +3 -3
  78. package/dist/{chunk-FFAZ2XFU.js → chunk-EHRE7ZG3.js} +5 -6
  79. package/dist/chunk-EHRE7ZG3.js.map +1 -0
  80. package/dist/{chunk-SZXPKUMN.js → chunk-ETGXRGSR.js} +4 -4
  81. package/dist/{chunk-KQI2PKYP.js → chunk-F2QIPGTG.js} +2 -2
  82. package/dist/{chunk-XVC7JU2N.js → chunk-FQD2KPQH.js} +2 -2
  83. package/dist/{chunk-QMFKW4OU.js → chunk-FRRR4KOW.js} +3 -3
  84. package/dist/{chunk-Q562GF7J.js → chunk-GH5R5BIH.js} +2 -2
  85. package/dist/{chunk-JMF5Q4JY.js → chunk-GTSQJTNS.js} +7 -7
  86. package/dist/{chunk-I56MTWE7.js → chunk-HHE57I2Z.js} +2 -2
  87. package/dist/{chunk-MULNBSPP.js → chunk-HT2C4EXO.js} +2 -2
  88. package/dist/{chunk-EMVJU7WC.js → chunk-IE2J3V5N.js} +2 -2
  89. package/dist/{chunk-L34W4W4I.js → chunk-IJU2ZWPE.js} +2 -2
  90. package/dist/{chunk-U6OXJQEH.js → chunk-J5QL66XK.js} +2 -2
  91. package/dist/{chunk-AJ6ORARY.js → chunk-JALYQUYO.js} +2 -2
  92. package/dist/chunk-JALYQUYO.js.map +1 -0
  93. package/dist/{chunk-FC6RCOIU.js → chunk-JFGX2DKT.js} +2 -2
  94. package/dist/{chunk-ANZXWOYD.js → chunk-JH4XB3RX.js} +2 -2
  95. package/dist/{chunk-N4PYB235.js → chunk-KI6UOSNR.js} +2 -2
  96. package/dist/{chunk-4K2H4KTR.js → chunk-KMAHEIOH.js} +3 -3
  97. package/dist/{chunk-EQUD7FP4.js → chunk-KYY4EACB.js} +2 -2
  98. package/dist/{chunk-46WM6JX2.js → chunk-LZKOVXMX.js} +2 -2
  99. package/dist/{chunk-I25RQA65.js → chunk-M4MAVJJX.js} +2 -2
  100. package/dist/{chunk-TL4PM7PC.js → chunk-M6PEFWJ4.js} +2 -2
  101. package/dist/{chunk-XPRAOTJY.js → chunk-MHGHEBTR.js} +2 -2
  102. package/dist/{chunk-VC4J4UTI.js → chunk-NJTW2MUV.js} +2 -2
  103. package/dist/{chunk-CCMXCZLT.js → chunk-OCIJYYCA.js} +7 -7
  104. package/dist/{chunk-KCCZFDJD.js → chunk-ODGXQUPR.js} +2 -2
  105. package/dist/{chunk-5AYUHSAU.js → chunk-OFCHT2TN.js} +2 -2
  106. package/dist/{chunk-QGLEM46C.js → chunk-OH75RBYF.js} +5 -5
  107. package/dist/{chunk-37HCPM56.js → chunk-OX5LMCEQ.js} +10 -10
  108. package/dist/{chunk-SWRN6U7Q.js → chunk-PBYY4WYM.js} +2 -2
  109. package/dist/{chunk-QRZMJ2WN.js → chunk-PO6MNTWH.js} +2 -2
  110. package/dist/{chunk-2CP577K6.js → chunk-PPIASWWR.js} +2 -2
  111. package/dist/{chunk-OGVJXIHX.js → chunk-PXTZOU5F.js} +2 -2
  112. package/dist/{chunk-IJ4FEGZR.js → chunk-Q67D4JYB.js} +2 -2
  113. package/dist/{chunk-DTXN4NVD.js → chunk-QF7XX74D.js} +36 -36
  114. package/dist/chunk-QF7XX74D.js.map +1 -0
  115. package/dist/{chunk-KQT4DRKF.js → chunk-QHL33ZDS.js} +2 -2
  116. package/dist/{chunk-MFZACYZ6.js → chunk-QUNDYIYZ.js} +3 -3
  117. package/dist/{chunk-TGF43K66.js → chunk-QWDH4QNA.js} +4 -4
  118. package/dist/{chunk-W5XWHTDO.js → chunk-QZZPUREO.js} +3 -3
  119. package/dist/{chunk-VUEG3TZ4.js → chunk-RVJPY6E4.js} +2 -2
  120. package/dist/{chunk-KE5PWLI6.js → chunk-T5XG6GQI.js} +3 -3
  121. package/dist/{chunk-EP5WXE2G.js → chunk-TCYLCPC6.js} +2 -2
  122. package/dist/{chunk-WWLAKRMX.js → chunk-TPNCVFVI.js} +2 -2
  123. package/dist/{chunk-WG4TMZJ2.js → chunk-TVRTJZQ5.js} +2 -2
  124. package/dist/{chunk-YGPXV546.js → chunk-UL7WKKGZ.js} +2 -2
  125. package/dist/{chunk-4JNTGPBR.js → chunk-UTXWLTX5.js} +2 -2
  126. package/dist/{chunk-YJAOYEWI.js → chunk-V4ACBX3C.js} +2 -2
  127. package/dist/{chunk-DJMVSUPK.js → chunk-VBNIQ6H2.js} +4 -4
  128. package/dist/{chunk-PO7NXNGC.js → chunk-VFTK5WKI.js} +2 -2
  129. package/dist/{chunk-L2VS27PZ.js → chunk-VIPGYPEG.js} +4 -4
  130. package/dist/{chunk-ENNC3RZ6.js → chunk-VQBGV44L.js} +2 -2
  131. package/dist/{chunk-5BPXMAUP.js → chunk-VUQDJ3JB.js} +4 -4
  132. package/dist/{chunk-7CXL25AA.js → chunk-W4RLOZOG.js} +3 -3
  133. package/dist/{chunk-LHRQBMJN.js → chunk-WADSXS4K.js} +2 -2
  134. package/dist/{chunk-QEPL7KIZ.js → chunk-WUUE2I2A.js} +6 -6
  135. package/dist/{chunk-IM2GO42H.js → chunk-WVHRNAXL.js} +4 -4
  136. package/dist/chunk-WVHRNAXL.js.map +1 -0
  137. package/dist/{chunk-TDMNADPI.js → chunk-XKEDESWC.js} +2 -2
  138. package/dist/{chunk-UXIZ5XN5.js → chunk-XLIK2VDE.js} +3 -3
  139. package/dist/{chunk-2UPTRDGM.js → chunk-Y6DZ33PA.js} +4 -4
  140. package/dist/{chunk-7CZIKCJR.js → chunk-Y6IHIORY.js} +47 -47
  141. package/dist/{chunk-63UNVGZW.js → chunk-YBSWXKCH.js} +4 -4
  142. package/dist/{chunk-IFCKHPY4.js → chunk-YDH2FSDI.js} +2 -2
  143. package/dist/{chunk-L3PWXD73.js → chunk-YMVLPVQP.js} +2 -2
  144. package/dist/{chunk-TRBLJU6Y.js → chunk-YNSHGVXT.js} +26 -26
  145. package/dist/{chunk-L6GNS5GB.js → chunk-YS7SA4HD.js} +2 -2
  146. package/dist/{chunk-RSMIRLKM.js → chunk-YV54LYEB.js} +2 -2
  147. package/dist/{chunk-EIFYYHAF.js → chunk-YYO447A7.js} +4 -4
  148. package/dist/{chunk-S5TGRZWY.js → chunk-Z2ZUV6FE.js} +3 -3
  149. package/dist/{chunk-XQSJXNWC.js → chunk-Z4CETAL7.js} +2 -2
  150. package/dist/{chunk-MH2DXDN6.js → chunk-Z5IQTRDN.js} +2 -2
  151. package/dist/{chunk-SIYZDT2V.js → chunk-ZBDTSFRA.js} +2 -2
  152. package/dist/{chunk-75PT4N7Q.js → chunk-ZI7LMSNI.js} +2 -2
  153. package/dist/{chunk-EUT2WCWS.js → chunk-ZQ3WU6JO.js} +2 -2
  154. package/dist/{chunk-JYG2334B.js → chunk-ZSJGYC3C.js} +2 -2
  155. package/dist/cli.js +88 -87
  156. package/dist/coding/export-okf-codegraph.js +16 -15
  157. package/dist/coding/export-okf-codegraph.js.map +1 -1
  158. package/dist/coding/pre-action-gate.js +2 -2
  159. package/dist/compounding/engine.js +17 -16
  160. package/dist/config.js +23 -22
  161. package/dist/connectors/codex-materialize-runner.js +16 -15
  162. package/dist/connectors/index.js +16 -15
  163. package/dist/consolidation-provenance-check.js +5 -4
  164. package/dist/consolidation-undo.js +4 -3
  165. package/dist/consolidation-undo.js.map +1 -1
  166. package/dist/contradiction/index.js +6 -5
  167. package/dist/convergence-refresh.js +18 -17
  168. package/dist/corpus-watermark.js +18 -17
  169. package/dist/deep-recall-http-glue.js +8 -7
  170. package/dist/embedding-fallback.js +4 -3
  171. package/dist/embedding-generation-lock.js +3 -2
  172. package/dist/entity-retrieval.js +16 -15
  173. package/dist/explicit-capture.js +8 -7
  174. package/dist/external-wiki-access.js +54 -53
  175. package/dist/extraction.js +21 -20
  176. package/dist/{forget-SZ2BXDGF.js → forget-OHAJ6PSU.js} +4 -3
  177. package/dist/{forget-SZ2BXDGF.js.map → forget-OHAJ6PSU.js.map} +1 -1
  178. package/dist/{graph-edge-decay-PVKDKLQK.js → graph-edge-decay-CZ3SYLM4.js} +7 -6
  179. package/dist/graph-jsonl.js +5 -4
  180. package/dist/graph-snapshot.js +5 -4
  181. package/dist/graph-write-lock.js +3 -2
  182. package/dist/graph.js +4 -3
  183. package/dist/harmonic-retrieval.js +16 -15
  184. package/dist/http-retry.d.ts.map +1 -1
  185. package/dist/http-retry.js +6 -8
  186. package/dist/http-retry.js.map +1 -1
  187. package/dist/index.js +104 -103
  188. package/dist/location/index.js +19 -18
  189. package/dist/maintenance/memory-governance.js +16 -15
  190. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +16 -15
  191. package/dist/maintenance/rebuild-memory-projection-runner.js +18 -17
  192. package/dist/maintenance/rebuild-memory-projection.js +17 -16
  193. package/dist/memory-browse-operations.js +54 -53
  194. package/dist/memory-browse.js +6 -5
  195. package/dist/memory-subject.js +4 -3
  196. package/dist/namespaces/migrate.js +17 -16
  197. package/dist/namespaces/search.js +5 -4
  198. package/dist/namespaces/storage.js +16 -15
  199. package/dist/offline-sync-changeset-generations.js +5 -4
  200. package/dist/offline-sync-embedding-generation.js +4 -3
  201. package/dist/offline-sync-impression-drain.js +4 -3
  202. package/dist/offline-sync-snapshot-stream.js +7 -6
  203. package/dist/offline-sync.js +6 -5
  204. package/dist/operator-doctor-corpus.js +19 -18
  205. package/dist/operator-toolkit.js +35 -34
  206. package/dist/orchestration/maintenance.js +26 -25
  207. package/dist/orchestrator.js +104 -103
  208. package/dist/page-versioning.js +4 -3
  209. package/dist/procedural/procedure-miner.d.ts.map +1 -1
  210. package/dist/qmd-daemon-session.d.ts.map +1 -1
  211. package/dist/qmd-daemon-session.js +2 -1
  212. package/dist/qmd.d.ts.map +1 -1
  213. package/dist/qmd.js +3 -2
  214. package/dist/recall-navigation-operations.js +54 -53
  215. package/dist/recall-navigation.js +53 -52
  216. package/dist/recall-state-view-admission.js +6 -5
  217. package/dist/recall-state.js +3 -2
  218. package/dist/recall-why-service.js +6 -5
  219. package/dist/reconcile/manifest.js +4 -3
  220. package/dist/{resolution-TV3F7ZTH.js → resolution-E4GLHLY6.js} +7 -6
  221. package/dist/resume-bundles.js +24 -23
  222. package/dist/salvage-envelope.js +6 -5
  223. package/dist/search/factory.js +4 -3
  224. package/dist/search/index.js +4 -3
  225. package/dist/semantic-consolidation.js +17 -16
  226. package/dist/semantic-rule-promotion.js +16 -15
  227. package/dist/semantic-rule-verifier.js +16 -15
  228. package/dist/source-agent-qualifier.js +20 -19
  229. package/dist/standup/build.js +16 -15
  230. package/dist/storage.js +15 -14
  231. package/dist/subject-config.js +5 -4
  232. package/dist/summarizer.js +4 -3
  233. package/dist/summary-snapshot.js +3 -2
  234. package/dist/support-passport/index.js +56 -55
  235. package/dist/testing/orchestrator-lite.d.ts +1 -2
  236. package/dist/testing/orchestrator-lite.d.ts.map +1 -1
  237. package/dist/transfer/capsule-import.js +5 -4
  238. package/dist/transfer/export-okf.js +16 -15
  239. package/dist/utils/serialize-mutations.d.ts.map +1 -1
  240. package/dist/utils/serialize-mutations.js +2 -1
  241. package/dist/utils/sleep.d.ts +3 -0
  242. package/dist/utils/sleep.d.ts.map +1 -0
  243. package/dist/verified-recall.js +16 -15
  244. package/dist/who-knows-cli.js +17 -16
  245. package/dist/who-knows.js +16 -15
  246. package/dist/write-envelope.js +5 -4
  247. package/package.json +2 -2
  248. package/src/access-idempotency.ts +1 -4
  249. package/src/http-retry.ts +2 -6
  250. package/src/memory-projection-batch.test.ts +55 -0
  251. package/src/offline-sync-digest-cache.test.ts +45 -0
  252. package/src/procedural/procedure-miner.ts +1 -4
  253. package/src/qmd-daemon-session.ts +1 -4
  254. package/src/qmd.ts +0 -4
  255. package/src/testing/orchestrator-lite.ts +2 -7
  256. package/src/utils/serialize-mutations.ts +1 -13
  257. package/src/utils/sleep.test.ts +32 -0
  258. package/src/utils/sleep.ts +5 -0
  259. package/dist/chunk-AJ6ORARY.js.map +0 -1
  260. package/dist/chunk-DTXN4NVD.js.map +0 -1
  261. package/dist/chunk-FFAZ2XFU.js.map +0 -1
  262. package/dist/chunk-IM2GO42H.js.map +0 -1
  263. package/dist/chunk-VFUEZZBS.js.map +0 -1
  264. /package/dist/{chunk-OZABSIOV.js.map → chunk-2AFHZTPI.js.map} +0 -0
  265. /package/dist/{chunk-UK5EH7AI.js.map → chunk-2CXQZXWM.js.map} +0 -0
  266. /package/dist/{chunk-RIEGCK2W.js.map → chunk-2KRNPFPK.js.map} +0 -0
  267. /package/dist/{chunk-N7QV3CMX.js.map → chunk-2NCBDGND.js.map} +0 -0
  268. /package/dist/{chunk-FLV7M7CT.js.map → chunk-2PBZBNY7.js.map} +0 -0
  269. /package/dist/{chunk-KSUOM3YU.js.map → chunk-2SKI5ZSH.js.map} +0 -0
  270. /package/dist/{chunk-6C76FZRM.js.map → chunk-3DSRYRGR.js.map} +0 -0
  271. /package/dist/{chunk-JA72X46J.js.map → chunk-3GLQUOL2.js.map} +0 -0
  272. /package/dist/{chunk-CLCMDKTB.js.map → chunk-3I3U6FFT.js.map} +0 -0
  273. /package/dist/{chunk-BNMI3JBV.js.map → chunk-4EHA327M.js.map} +0 -0
  274. /package/dist/{chunk-QWUJWBZ5.js.map → chunk-4G4B3Z2B.js.map} +0 -0
  275. /package/dist/{chunk-R7QY5ZVB.js.map → chunk-4NDWE44P.js.map} +0 -0
  276. /package/dist/{chunk-U6HWKOGD.js.map → chunk-5GN5LRQW.js.map} +0 -0
  277. /package/dist/{chunk-MQNOIH2C.js.map → chunk-5O4UJ6PM.js.map} +0 -0
  278. /package/dist/{chunk-HRZZJ4XB.js.map → chunk-645A37SC.js.map} +0 -0
  279. /package/dist/{chunk-JA54KIYD.js.map → chunk-65FYHYBE.js.map} +0 -0
  280. /package/dist/{chunk-CHDGJJV6.js.map → chunk-6VAHJ2F6.js.map} +0 -0
  281. /package/dist/{chunk-TZQOCQXB.js.map → chunk-AL6EHLB5.js.map} +0 -0
  282. /package/dist/{chunk-D6M3VKMY.js.map → chunk-B2UK6O2O.js.map} +0 -0
  283. /package/dist/{chunk-VZBW6CDG.js.map → chunk-BOOFTFFE.js.map} +0 -0
  284. /package/dist/{chunk-YJEUHGZZ.js.map → chunk-BR75HFPC.js.map} +0 -0
  285. /package/dist/{chunk-GF4H3B3R.js.map → chunk-BSWHZJSP.js.map} +0 -0
  286. /package/dist/{chunk-2U3R3DDH.js.map → chunk-BTJ6CAIA.js.map} +0 -0
  287. /package/dist/{chunk-SNXBXYUE.js.map → chunk-CG2E6WT5.js.map} +0 -0
  288. /package/dist/{chunk-GE6HETIF.js.map → chunk-CRYAYFUE.js.map} +0 -0
  289. /package/dist/{chunk-GEGUBU3D.js.map → chunk-D6C4JWSN.js.map} +0 -0
  290. /package/dist/{chunk-2UF42WXV.js.map → chunk-DSVRW5BD.js.map} +0 -0
  291. /package/dist/{chunk-7LYRFI5A.js.map → chunk-E3OSDZ6M.js.map} +0 -0
  292. /package/dist/{chunk-2COXDQ6C.js.map → chunk-E6KXJSMA.js.map} +0 -0
  293. /package/dist/{chunk-6KHF6PAB.js.map → chunk-EBQCVOGC.js.map} +0 -0
  294. /package/dist/{chunk-SZXPKUMN.js.map → chunk-ETGXRGSR.js.map} +0 -0
  295. /package/dist/{chunk-KQI2PKYP.js.map → chunk-F2QIPGTG.js.map} +0 -0
  296. /package/dist/{chunk-XVC7JU2N.js.map → chunk-FQD2KPQH.js.map} +0 -0
  297. /package/dist/{chunk-QMFKW4OU.js.map → chunk-FRRR4KOW.js.map} +0 -0
  298. /package/dist/{chunk-Q562GF7J.js.map → chunk-GH5R5BIH.js.map} +0 -0
  299. /package/dist/{chunk-JMF5Q4JY.js.map → chunk-GTSQJTNS.js.map} +0 -0
  300. /package/dist/{chunk-I56MTWE7.js.map → chunk-HHE57I2Z.js.map} +0 -0
  301. /package/dist/{chunk-MULNBSPP.js.map → chunk-HT2C4EXO.js.map} +0 -0
  302. /package/dist/{chunk-EMVJU7WC.js.map → chunk-IE2J3V5N.js.map} +0 -0
  303. /package/dist/{chunk-L34W4W4I.js.map → chunk-IJU2ZWPE.js.map} +0 -0
  304. /package/dist/{chunk-U6OXJQEH.js.map → chunk-J5QL66XK.js.map} +0 -0
  305. /package/dist/{chunk-FC6RCOIU.js.map → chunk-JFGX2DKT.js.map} +0 -0
  306. /package/dist/{chunk-ANZXWOYD.js.map → chunk-JH4XB3RX.js.map} +0 -0
  307. /package/dist/{chunk-N4PYB235.js.map → chunk-KI6UOSNR.js.map} +0 -0
  308. /package/dist/{chunk-4K2H4KTR.js.map → chunk-KMAHEIOH.js.map} +0 -0
  309. /package/dist/{chunk-EQUD7FP4.js.map → chunk-KYY4EACB.js.map} +0 -0
  310. /package/dist/{chunk-46WM6JX2.js.map → chunk-LZKOVXMX.js.map} +0 -0
  311. /package/dist/{chunk-I25RQA65.js.map → chunk-M4MAVJJX.js.map} +0 -0
  312. /package/dist/{chunk-TL4PM7PC.js.map → chunk-M6PEFWJ4.js.map} +0 -0
  313. /package/dist/{chunk-XPRAOTJY.js.map → chunk-MHGHEBTR.js.map} +0 -0
  314. /package/dist/{chunk-VC4J4UTI.js.map → chunk-NJTW2MUV.js.map} +0 -0
  315. /package/dist/{chunk-CCMXCZLT.js.map → chunk-OCIJYYCA.js.map} +0 -0
  316. /package/dist/{chunk-KCCZFDJD.js.map → chunk-ODGXQUPR.js.map} +0 -0
  317. /package/dist/{chunk-5AYUHSAU.js.map → chunk-OFCHT2TN.js.map} +0 -0
  318. /package/dist/{chunk-QGLEM46C.js.map → chunk-OH75RBYF.js.map} +0 -0
  319. /package/dist/{chunk-37HCPM56.js.map → chunk-OX5LMCEQ.js.map} +0 -0
  320. /package/dist/{chunk-SWRN6U7Q.js.map → chunk-PBYY4WYM.js.map} +0 -0
  321. /package/dist/{chunk-QRZMJ2WN.js.map → chunk-PO6MNTWH.js.map} +0 -0
  322. /package/dist/{chunk-2CP577K6.js.map → chunk-PPIASWWR.js.map} +0 -0
  323. /package/dist/{chunk-OGVJXIHX.js.map → chunk-PXTZOU5F.js.map} +0 -0
  324. /package/dist/{chunk-IJ4FEGZR.js.map → chunk-Q67D4JYB.js.map} +0 -0
  325. /package/dist/{chunk-KQT4DRKF.js.map → chunk-QHL33ZDS.js.map} +0 -0
  326. /package/dist/{chunk-MFZACYZ6.js.map → chunk-QUNDYIYZ.js.map} +0 -0
  327. /package/dist/{chunk-TGF43K66.js.map → chunk-QWDH4QNA.js.map} +0 -0
  328. /package/dist/{chunk-W5XWHTDO.js.map → chunk-QZZPUREO.js.map} +0 -0
  329. /package/dist/{chunk-VUEG3TZ4.js.map → chunk-RVJPY6E4.js.map} +0 -0
  330. /package/dist/{chunk-KE5PWLI6.js.map → chunk-T5XG6GQI.js.map} +0 -0
  331. /package/dist/{chunk-EP5WXE2G.js.map → chunk-TCYLCPC6.js.map} +0 -0
  332. /package/dist/{chunk-WWLAKRMX.js.map → chunk-TPNCVFVI.js.map} +0 -0
  333. /package/dist/{chunk-WG4TMZJ2.js.map → chunk-TVRTJZQ5.js.map} +0 -0
  334. /package/dist/{chunk-YGPXV546.js.map → chunk-UL7WKKGZ.js.map} +0 -0
  335. /package/dist/{chunk-4JNTGPBR.js.map → chunk-UTXWLTX5.js.map} +0 -0
  336. /package/dist/{chunk-YJAOYEWI.js.map → chunk-V4ACBX3C.js.map} +0 -0
  337. /package/dist/{chunk-DJMVSUPK.js.map → chunk-VBNIQ6H2.js.map} +0 -0
  338. /package/dist/{chunk-PO7NXNGC.js.map → chunk-VFTK5WKI.js.map} +0 -0
  339. /package/dist/{chunk-L2VS27PZ.js.map → chunk-VIPGYPEG.js.map} +0 -0
  340. /package/dist/{chunk-ENNC3RZ6.js.map → chunk-VQBGV44L.js.map} +0 -0
  341. /package/dist/{chunk-5BPXMAUP.js.map → chunk-VUQDJ3JB.js.map} +0 -0
  342. /package/dist/{chunk-7CXL25AA.js.map → chunk-W4RLOZOG.js.map} +0 -0
  343. /package/dist/{chunk-LHRQBMJN.js.map → chunk-WADSXS4K.js.map} +0 -0
  344. /package/dist/{chunk-QEPL7KIZ.js.map → chunk-WUUE2I2A.js.map} +0 -0
  345. /package/dist/{chunk-TDMNADPI.js.map → chunk-XKEDESWC.js.map} +0 -0
  346. /package/dist/{chunk-UXIZ5XN5.js.map → chunk-XLIK2VDE.js.map} +0 -0
  347. /package/dist/{chunk-2UPTRDGM.js.map → chunk-Y6DZ33PA.js.map} +0 -0
  348. /package/dist/{chunk-7CZIKCJR.js.map → chunk-Y6IHIORY.js.map} +0 -0
  349. /package/dist/{chunk-63UNVGZW.js.map → chunk-YBSWXKCH.js.map} +0 -0
  350. /package/dist/{chunk-IFCKHPY4.js.map → chunk-YDH2FSDI.js.map} +0 -0
  351. /package/dist/{chunk-L3PWXD73.js.map → chunk-YMVLPVQP.js.map} +0 -0
  352. /package/dist/{chunk-TRBLJU6Y.js.map → chunk-YNSHGVXT.js.map} +0 -0
  353. /package/dist/{chunk-L6GNS5GB.js.map → chunk-YS7SA4HD.js.map} +0 -0
  354. /package/dist/{chunk-RSMIRLKM.js.map → chunk-YV54LYEB.js.map} +0 -0
  355. /package/dist/{chunk-EIFYYHAF.js.map → chunk-YYO447A7.js.map} +0 -0
  356. /package/dist/{chunk-S5TGRZWY.js.map → chunk-Z2ZUV6FE.js.map} +0 -0
  357. /package/dist/{chunk-XQSJXNWC.js.map → chunk-Z4CETAL7.js.map} +0 -0
  358. /package/dist/{chunk-MH2DXDN6.js.map → chunk-Z5IQTRDN.js.map} +0 -0
  359. /package/dist/{chunk-SIYZDT2V.js.map → chunk-ZBDTSFRA.js.map} +0 -0
  360. /package/dist/{chunk-75PT4N7Q.js.map → chunk-ZI7LMSNI.js.map} +0 -0
  361. /package/dist/{chunk-EUT2WCWS.js.map → chunk-ZQ3WU6JO.js.map} +0 -0
  362. /package/dist/{chunk-JYG2334B.js.map → chunk-ZSJGYC3C.js.map} +0 -0
  363. /package/dist/{graph-edge-decay-PVKDKLQK.js.map → graph-edge-decay-CZ3SYLM4.js.map} +0 -0
  364. /package/dist/{resolution-TV3F7ZTH.js.map → resolution-E4GLHLY6.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/serialize-mutations.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Shared serialized-mutation utilities for TOCTOU hotspots (issue #1524).\n//\n// Two complementary primitives that the namespace catalog (`queueCritical` +\n// `withHeldCatalogLock`), the storage router's resolve-hook serialization, and\n// the summary-snapshot writer each re-implement today:\n//\n// 1. `serializeMutations(key, task)` — keyed IN-PROCESS async serialization\n// that recovers after a rejection (CLAUDE.md rule #40). One failed task\n// never poisons the tasks queued behind it; the failed task's error is\n// still surfaced to ITS caller.\n//\n// 2. `withHeldFileLock(lockPath, opts, task)` — a held CROSS-PROCESS file\n// lock with replacement-safe stale breaking (the NG7Bg invariant from\n// #1506 round 28) and ownership-checked release.\n//\n// This is the UTILITY module only. Per-issue PR split: one PR for the utility\n// + tests (this file), then one PR per adoption hotspot (catalog, router\n// provenance, summary snapshot). No adoptions live here.\n// ---------------------------------------------------------------------------\n\nimport { randomUUID } from \"node:crypto\";\nimport { link, mkdir, open, readFile, rename, stat, unlink, utimes } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 1. serializeMutations — keyed async serialization with rejection recovery\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * One entry in the per-key serialization map. `tail` is the recovered promise\n * the next queued task chains off of; it never rejects (both settle handlers\n * swallow), so a prior task's failure can never break subsequent ones.\n */\ninterface MutationChainEntry {\n tail: Promise<void>;\n}\n\n/**\n * Instance-scoped keyed serializer. Holds the per-key chain map so that all\n * tasks queued under the same key on the SAME serializer run strictly in order.\n *\n * The map is instance-scoped (not module-level) so tests can construct a fresh\n * serializer per case and avoid cross-test contamination, and so adopters that\n * want isolation (e.g. one serializer per storage root) can have it. The free\n * {@link serializeMutations} export delegates to a single shared default\n * instance for callers that want process-wide serialization.\n */\nexport class MutationSerializer {\n private readonly chains = new Map<string, MutationChainEntry>();\n\n /**\n * Run `task` strictly after every other task already queued under `key` on\n * this serializer has settled.\n *\n * Rejection recovery (rule #40, mirroring the catalog's `queueCritical`):\n * if a prior task rejects, later tasks STILL RUN, while the rejecting task's\n * error is surfaced to ITS OWN caller. Concretely, the recovered tail is\n * `run.then(noop, noop)` — never a bare `.then(fn)`, which would let one\n * failure kill every queued task behind it.\n *\n * No unbounded growth: when a chain's last task settles and no newer task\n * chained onto it, its entry is deleted (the storage router's\n * `inFlightResolved` marker-then-clear discipline).\n */\n serialize<T>(key: string, task: () => Promise<T>): Promise<T> {\n if (typeof key !== \"string\" || key.length === 0) {\n throw new TypeError(\"MutationSerializer.serialize: key must be a non-empty string\");\n }\n if (typeof task !== \"function\") {\n throw new TypeError(\"MutationSerializer.serialize: task must be a function returning a promise\");\n }\n\n let entry = this.chains.get(key);\n if (!entry) {\n entry = { tail: Promise.resolve() };\n this.chains.set(key, entry);\n }\n\n // Chain this task off the prior tail. `tail.then(task)` runs task only once\n // the previous task has settled, preserving read-modify-write ordering.\n const run = entry.tail.then(task);\n\n // Recover the tail after a rejection so a failed task never poisons later\n // ones. Both handlers swallow; `run` still carries the original resolution\n // (or rejection) to THIS caller. This is the line a naive `.then(fn)`\n // implementation omits — see the \"naive poison chain\" prove-fail test.\n const recovered = run.then(settleNoop, settleNoop);\n entry.tail = recovered;\n\n // Self-cleaning: once our recovered tail settles, if no newer task chained\n // onto us the entry still points at `recovered` and is safe to delete. A\n // concurrent `serialize()` call enqueues synchronously and would have\n // replaced `entry.tail` BEFORE this microtask runs, so the identity check\n // is race-free (no newer task's entry can be wrongly removed).\n //\n // `recovered` cannot reject in correct operation (both handlers above\n // swallow) and the cleanup body cannot throw — but we attach a rejection\n // handler anyway so that IF the recovery invariant is ever broken, the\n // failure surfaces as a behavioral assertion (skipped tasks) rather than an\n // unhandled-rejection storm that masks which task failed. The handler is a\n // no-op: cleanup only runs on fulfillment.\n void recovered.then(\n () => {\n if (entry && entry.tail === recovered) {\n this.chains.delete(key);\n }\n },\n () => undefined,\n );\n\n return run;\n }\n\n /**\n * Test-only: the number of keys with a not-yet-cleaned chain. Used to assert\n * the no-unbounded-growth invariant. Not part of the public contract.\n */\n pendingKeysForTest(): number {\n return this.chains.size;\n }\n}\n\n/**\n * Recovery handler shared by both settle arms. Named (not inline\n * `() => undefined`) so the chain assignment stays self-documenting in stack\n * traces and the review-patterns poison-chain check can see the chain is\n * recovered, not bare `.then(fn)`.\n */\nfunction settleNoop(): void {\n /* swallow — the original resolution/rejection is carried by `run` */\n}\n\n/**\n * Process-wide default serializer backing the free {@link serializeMutations}\n * export. Lazy so it is only created when first used (tests that construct\n * their own `MutationSerializer` pay nothing).\n */\nlet defaultSerializer: MutationSerializer | undefined;\n\n/**\n * Free-function entry point (issue #1524 signature). Serializes `task` against\n * every other task queued under `key` across the whole process, via a shared\n * default {@link MutationSerializer}. For isolated/testable serialization,\n * construct a `MutationSerializer` directly.\n */\nexport function serializeMutations<T>(key: string, task: () => Promise<T>): Promise<T> {\n if (!defaultSerializer) defaultSerializer = new MutationSerializer();\n return defaultSerializer.serialize(key, task);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 2. withHeldFileLock — cross-process held file lock with stale breaking\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Options for {@link withHeldFileLock}. */\nexport interface HeldFileLockOptions {\n /**\n * A lock whose mtime is older than this (in ms) is treated as a crashed\n * holder and broken. Required — there is no safe default, since the right\n * value depends on how long the guarded critical section can legitimately\n * run.\n */\n readonly staleMs: number;\n /**\n * Bounded acquisition: give up trying to acquire a busy lock after this long\n * (ms) and invoke `task(false)` best-effort WITHOUT holding the lock, rather\n * than blocking forever or crashing the primary op. Default 5000ms (matches\n * the namespace catalog's `REBUILD_LOCK_MAX_WAIT_MS`).\n */\n readonly maxWaitMs?: number;\n /**\n * Poll interval (ms) while waiting for a busy lock to clear. Default 50ms.\n */\n readonly pollMs?: number;\n /**\n * While WE hold the lock, refresh its mtime on this cadence (ms) so a\n * legitimately long task is not mistaken for a crashed holder and broken out\n * from under. Default `floor(staleMs / 3)` (at least 100ms), mirroring the\n * catalog heartbeat ratio. Must be comfortably below `staleMs`.\n */\n readonly heartbeatMs?: number;\n /**\n * Test seam (NG7Bg, #1506 round 28): fires AFTER a lock is judged stale and\n * BEFORE the re-verify + unlink, simulating a replacement lock being created\n * in the race window. No-op in production.\n */\n readonly onBeforeBreakStaleUnlinkForTest?: () => Promise<void> | void;\n /**\n * Test seam (codex P2): fires AFTER the release rename moves the lock to a\n * trash path and BEFORE the ownership re-verify/restore — simulating a third\n * contender acquiring the (now-empty) lockPath in the race window. No-op in\n * production. Used to prove the pre-check prevents the rename entirely.\n */\n readonly onAfterReleaseRenameForTest?: () => Promise<void> | void;\n /**\n * Test seam (#2033): fires inside {@link HeldFileLockController.refresh} AFTER\n * the ownership pre-check passes and BEFORE the `utimes` mtime bump —\n * simulating a peer that deletes the lock (making the bump throw) or replaces\n * it (a different owner the post-bump re-check must catch). No-op in\n * production; proves refresh reports LOST rather than clobbering a peer.\n */\n readonly onBeforeRefreshUtimesForTest?: () => Promise<void> | void;\n /**\n * Best-effort hook for non-fatal lock warnings (heartbeat refresh failure,\n * release-time ownership check failure). Never throws into the caller. If\n * omitted, warnings are swallowed (the lock is advisory; release/heartbeat\n * failures must never crash the guarded op).\n */\n readonly onLockWarning?: (message: string, err: unknown) => void;\n}\n\n/**\n * Control surface handed to a held-lock task so a long, CPU-bound critical\n * section can re-assert ownership immediately before its destructive write.\n */\nexport interface HeldFileLockController {\n /**\n * Re-verify THIS acquirer still owns the lock and, when it does, refresh the\n * lock's mtime. Returns `true` only while we still hold it.\n *\n * The timer heartbeat cannot fire while a synchronous, CPU-bound section\n * (a large parse/merge/sort/serialize) blocks the event loop, so a peer can\n * judge the lock stale, break it, and start its own write within the stale\n * window. A caller about to perform a destructive rewrite MUST call this\n * first: `false` means the lock was stale-broken/replaced and the caller MUST\n * abort rather than clobber the peer that now holds it; `true` also re-stamps\n * the mtime so the bounded write that immediately follows cannot itself be\n * judged stale mid-write. On the best-effort unlocked path (`acquired` was\n * `false`) this always resolves `false` — there is no lock to hold.\n */\n refresh(): Promise<boolean>;\n /**\n * Why the lock was not acquired. `timeout` means a busy peer consumed the\n * bounded wait; `error` means acquisition failed due to filesystem setup or\n * I/O and must not be retried as contention.\n */\n readonly failure?: \"timeout\" | \"error\";\n}\n\n/** Default bounded acquisition wait, mirroring the catalog. */\nconst DEFAULT_MAX_WAIT_MS = 5_000;\n/** Default busy-lock poll interval, mirroring the catalog. */\nconst DEFAULT_POLL_MS = 50;\n/** Floor for the derived heartbeat cadence. */\nconst MIN_HEARTBEAT_MS = 100;\n/** Node's setTimeout/setInterval 32-bit signed-int ceiling (2^31 − 1 ms ≈ 24.8\n * days). Delays above this are silently clamped to 1ms by the Node timer, so\n * timer-backed options (pollMs, heartbeatMs) must be rejected at this boundary\n * (chatgpt-codex-connector P2). */\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\n\n/** Internal handle for a lock we successfully acquired. */\ninterface HeldLock {\n readonly path: string;\n readonly ownerId: string;\n}\ntype LockAcquisitionFailure = { readonly failure: \"timeout\" | \"error\" };\n\n/**\n * Run `task` under an exclusive on-disk lock at `lockPath`.\n *\n * Cross-process mutex via `open(lockPath, \"wx\")` (atomic exclusive create).\n * While held, a heartbeat timer refreshes the lock's mtime so a legitimately\n * long task is not mistaken for a crashed holder and broken out from under. A\n * lock older than `opts.staleMs` is treated as stale and broken — but\n * REPLACEMENT-SAFE (NG7Bg): we capture the stale lock's identity (full content\n * line: `<pid> <owner-uuid> <iso>`) when judging it stale, then RE-READ and\n * RE-STAT immediately before `unlink`, deleting only if byte-identical AND\n * still stale. A replacement lock created in the window has a different owner\n * id / timestamp, so its content differs and is left untouched.\n *\n * `task` receives `acquired: boolean` — `true` when we hold the lock, `false`\n * when acquisition is unavailable. The controller's `failure` field\n * distinguishes a bounded busy-lock timeout from a permanent filesystem\n * acquisition error.\n * This explicit callback keeps the lock utility as the single lock home while\n * allowing callers to choose their timeout and failure policy.\n *\n * Release is ownership-checked: we only `unlink` a lock whose content still\n * identifies THIS acquirer (same owner id), so a replacement created after we\n * stopped heartbeating is never destroyed — mirroring the catalog's\n * `rebuildLockHeldBySelf`.\n *\n * ADOPTION NOTE: lock only the brief final read-merge-write window, never a\n * long scan — a scan-length lock makes concurrent writers time out and\n * silently drop work (catalog round 5, codex/cursor P2).\n */\nexport async function withHeldFileLock<T>(\n lockPath: string,\n opts: HeldFileLockOptions,\n task: (acquired: boolean, lock: HeldFileLockController) => Promise<T>,\n): Promise<T> {\n if (typeof lockPath !== \"string\" || lockPath.length === 0) {\n throw new TypeError(\"withHeldFileLock: lockPath must be a non-empty string\");\n }\n if (typeof opts?.staleMs !== \"number\" || !Number.isFinite(opts.staleMs) || opts.staleMs <= 0) {\n throw new TypeError(\n `withHeldFileLock: opts.staleMs must be a positive finite number ` +\n `(valid range: > 0 ms, finite; got ${formatInvalidNumber(opts?.staleMs)}).`,\n );\n }\n\n // Validate optional timings: a NaN/Infinity here is a real hazard (e.g.\n // `Date.now() + NaN` === NaN, so `Date.now() >= deadline` is always false and\n // the bounded acquire loop would wait forever instead of falling back to\n // best-effort). Reject invalid input rather than silently defaulting it\n // (codex P2 review). Omitting an option still picks its default.\n const maxWaitMs = optionalPositiveMs(opts.maxWaitMs, \"maxWaitMs\", DEFAULT_MAX_WAIT_MS, MAX_TIMER_DELAY_MS);\n const pollMs = optionalPositiveMs(opts.pollMs, \"pollMs\", DEFAULT_POLL_MS, MAX_TIMER_DELAY_MS);\n const heartbeatMs = optionalPositiveMs(\n opts.heartbeatMs,\n \"heartbeatMs\",\n Math.max(MIN_HEARTBEAT_MS, Math.floor(opts.staleMs / 3)),\n MAX_TIMER_DELAY_MS,\n );\n if (heartbeatMs >= opts.staleMs) {\n throw new TypeError(\n `withHeldFileLock: heartbeatMs (${heartbeatMs}) must be below staleMs (${opts.staleMs}) ` +\n `(valid range: > 0 and < staleMs ms) so at least one heartbeat lands per stale window.`,\n );\n }\n if (heartbeatMs > MAX_TIMER_DELAY_MS) {\n throw new TypeError(\n `withHeldFileLock: derived heartbeatMs (${heartbeatMs} = floor(staleMs/3)) exceeds ` +\n `Node's setTimeout ceiling (${MAX_TIMER_DELAY_MS} ms). Use an explicit opts.heartbeatMs ` +\n `at or below ${MAX_TIMER_DELAY_MS} ms.`,\n );\n }\n // Wrap the consumer's warning hook so a throwing callback never turns a\n // non-fatal advisory lock warning into an unhandled rejection (heartbeat\n // catch handler) or overrides the task's result (release path). The option\n // is documented as never throwing into the caller; enforce that here\n // (codex P2 review).\n const rawWarn = opts.onLockWarning;\n const warn = (message: string, err: unknown): void => {\n if (!rawWarn) return;\n try {\n rawWarn(message, err);\n } catch {\n /* swallow — a throwing advisory hook must not crash the guarded op */\n }\n };\n\n // Per-call owner identity. Two withHeldFileLock calls in the SAME process\n // get different ids, so neither mistakes the other's lock for its own\n // (stronger than the catalog's per-instance id, which is what we want for a\n // stateless utility).\n const ownerId = randomUUID();\n const lockDir = path.dirname(lockPath);\n\n const acquisition = await acquireLock(lockPath, lockDir, ownerId, opts, maxWaitMs, pollMs);\n if (\"failure\" in acquisition) {\n return task(false, { refresh: async () => false, failure: acquisition.failure });\n }\n const held = acquisition;\n\n // Heartbeat: while WE hold the lock, refresh its mtime so age-based stale\n // detection sees an active holder and does not break us out from under\n // (catalog round 5). Failures are swallowed (advisory lock); the timer is\n // always cleared in the finally.\n //\n // OWNERSHIP CHECK (codex P2): if our event loop was paused long enough that\n // another process judged us stale, broke our lock, and created a replacement,\n // we must NOT refresh the replacement's mtime — that would keep a (possibly\n // crashed) replacement looking fresh. Verify lockHeldBySelf before each\n // utimes; if ownership is lost, stop heartbeating (our lock is gone).\n const heartbeat = setInterval(() => {\n lockHeldBySelf(held)\n .then((ours) => {\n if (!ours) return; // broken/replaced — stop refreshing\n return utimes(held.path, new Date(), new Date());\n })\n .catch((err: unknown) => {\n warn(\"withHeldFileLock heartbeat refresh failed\", err);\n });\n }, heartbeatMs);\n // Don't keep the event loop alive solely for the heartbeat.\n heartbeat.unref?.();\n const controller: HeldFileLockController = {\n refresh: () => refreshHeldLock(held, warn, opts.onBeforeRefreshUtimesForTest),\n };\n try {\n return await task(true, controller);\n } finally {\n clearInterval(heartbeat);\n await releaseLock(held, warn, opts.onAfterReleaseRenameForTest);\n }\n}\n\n/**\n * Resolve an optional millisecond timing option, REJECTING invalid values\n * (NaN, Infinity, non-positive, or above `maxMs`) rather than silently defaulting\n * them. A NaN or Infinity maxWaitMs would make the bounded acquire loop wait\n * forever (`Date.now() + NaN` is NaN); a non-positive poll/heartbeat makes no\n * sense. Timer-backed options (pollMs, heartbeatMs) are bounded to Node's\n * setTimeout ceiling (`MAX_TIMER_DELAY_MS`): a value above 2^31−1 is silently\n * clamped to 1ms by the timer, turning a typo into tight polling (codex P2).\n * Omitting the option (`undefined`) picks `fallback`. Non-number types are also\n * rejected (defensive against config/env coercion).\n */\nfunction optionalPositiveMs(\n value: number | undefined,\n name: \"maxWaitMs\" | \"pollMs\" | \"heartbeatMs\",\n fallback: number,\n maxMs: number,\n): number {\n if (value === undefined) return fallback;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new TypeError(\n `withHeldFileLock: opts.${name} must be a positive finite number ` +\n `(valid range: > 0 ms, finite; got ${formatInvalidNumber(value)}). ` +\n `Omit the option to use the default of ${fallback} ms.`,\n );\n }\n if (value > maxMs) {\n throw new TypeError(\n `withHeldFileLock: opts.${name} (${value} ms) exceeds the ${maxMs} ms ` +\n `ceiling (Node's setTimeout clamps larger delays to 1ms, turning a ` +\n `typo into tight polling). Omit the option to use the default of ${fallback} ms.`,\n );\n }\n return value;\n}\n\n/**\n * Human-readable label for a rejected numeric input. Makes the error message\n * immediately actionable for NaN/Infinity (which print as \"NaN\"/\"Infinity\" via\n * String() but are easier to triage with an explicit sign), and surfaces the\n * actual type for non-number values (defensive against config/env coercion).\n */\nfunction formatInvalidNumber(value: unknown): string {\n if (typeof value === \"number\") {\n if (Number.isNaN(value)) return \"NaN\";\n if (value === Infinity) return \"+Infinity\";\n if (value === -Infinity) return \"-Infinity\";\n return String(value);\n }\n return `${typeof value} ${JSON.stringify(value)}`;\n}\n\n/**\n * Atomically create the lock file, looping until acquired or the bounded\n * deadline. Reports `timeout` for a busy lock and `error` for permanent\n * filesystem setup/I/O failures so callers can choose the safe policy.\n */\nasync function acquireLock(\n lockPath: string,\n lockDir: string,\n ownerId: string,\n opts: HeldFileLockOptions,\n maxWaitMs: number,\n pollMs: number,\n): Promise<HeldLock | LockAcquisitionFailure> {\n try {\n await mkdir(lockDir, { recursive: true });\n } catch {\n // Lock-directory setup failure is a permanent acquisition error.\n return { failure: \"error\" };\n }\n const deadline = Date.now() + maxWaitMs;\n for (;;) {\n try {\n const handle = await open(lockPath, \"wx\");\n let wroteMeta = true;\n try {\n await handle.writeFile(`${process.pid} ${ownerId} ${new Date().toISOString()}\\n`, \"utf8\");\n } catch {\n // The metadata write failed; the lock file may be empty or partial.\n // Our ownership check on release would NOT find this ownerId, leaving\n // a malformed lock that lingers until stale and blocks other callers\n // out of the mutex (codex P2). Undo our exclusive create and report\n // acquisition failure so the caller runs best-effort instead.\n wroteMeta = false;\n } finally {\n try {\n await handle.close();\n } catch {\n // close() can report a deferred I/O error (e.g. write that appeared\n // to succeed but failed on flush). The lock file may be malformed —\n // treat it as a metadata-write failure so the cleanup path unlinks\n // the orphaned lock (codex P2 review).\n wroteMeta = false;\n }\n }\n if (!wroteMeta) {\n await unlink(lockPath).catch(() => undefined);\n return { failure: \"error\" };\n }\n return { path: lockPath, ownerId };\n } catch (err) {\n if ((err as NodeJS.ErrnoException | undefined)?.code !== \"EEXIST\") {\n // Unexpected filesystem errors are permanent acquisition failures.\n return { failure: \"error\" };\n }\n // Lock exists: break it if stale, then poll. breakStaleLock is\n // replacement-safe (NG7Bg) and never throws.\n await breakStaleLock(lockPath, opts.staleMs, opts.onBeforeBreakStaleUnlinkForTest);\n if (Date.now() >= deadline) return { failure: \"timeout\" };\n // Cap the sleep to the remaining budget so a large pollMs cannot block\n // acquisition far past maxWaitMs (e.g. maxWaitMs=1000, pollMs=60000\n // would otherwise block ~60s instead of 1s — codex P2).\n await sleep(Math.min(pollMs, deadline - Date.now()));\n }\n }\n}\n\n/**\n * Replacement-safe stale-lock breaking (NG7Bg, #1506 round 28). Capture the\n * lock's identity when judging it stale, then ATOMICALLY rename it to a unique\n * trash path and verify the moved content matches. A replacement lock created\n * in the race window is either left untouched (different identity at\n * lockPath, so the rename moves the stale lock — not the replacement) or\n * restored (if the rename accidentally moves a replacement, the verify\n * detects the mismatch and renames it back).\n *\n * ATOMICITY (codex P2): `rename` is atomic on POSIX — only ONE contender can\n * successfully rename a given file. This eliminates the TOCTOU between the\n * identity/stat checks and the deletion that a bare `unlink` leaves open:\n * without rename, contender A could verify identity X, pause, then unlink\n * contender B's freshly acquired replacement Y. With rename, A moves whatever\n * is at lockPath, then checks: if it is X, A broke the stale lock; if it is\n * not X (a replacement appeared between A's last check and the rename), A\n * restores it.\n */\nasync function breakStaleLock(\n lockPath: string,\n staleMs: number,\n onBeforeBreakStaleUnlinkForTest: (() => Promise<void> | void) | undefined,\n): Promise<void> {\n let staleIdentity: string;\n try {\n const info = await stat(lockPath);\n if (Date.now() - info.mtimeMs <= staleMs) {\n // Not stale (a live holder's heartbeat keeps it fresh) — leave it.\n return;\n }\n staleIdentity = await readFile(lockPath, \"utf8\");\n } catch {\n // Lock vanished (released by holder) or stat/read failed — nothing to do.\n return;\n }\n // Test seam: simulate a replacement lock being created in the race window\n // between the staleness judgment and the atomic break. No-op in production.\n if (onBeforeBreakStaleUnlinkForTest) {\n await onBeforeBreakStaleUnlinkForTest();\n }\n try {\n // Re-validate immediately before breaking: the lock must still carry the\n // SAME identity AND still be stale.\n const current = await readFile(lockPath, \"utf8\");\n if (current !== staleIdentity) return; // replaced — leave the fresh lock\n const recheck = await stat(lockPath);\n if (Date.now() - recheck.mtimeMs <= staleMs) return; // heartbeat refreshed it\n\n // ATOMIC BREAK: rename is atomic on POSIX. Only one contender succeeds;\n // others get ENOENT (the file is already gone). After the rename, verify\n // the moved content: if it matches staleIdentity we broke the right lock;\n // if it does not, a replacement appeared in the window and we restore it.\n const trashPath = `${lockPath}.breaking.${process.pid}.${Date.now()}`;\n await rename(lockPath, trashPath);\n try {\n const moved = await readFile(trashPath, \"utf8\");\n if (moved !== staleIdentity) {\n // We accidentally moved a replacement lock (created between our last\n // check and the rename). Restore it so the replacement holder's lock\n // survives. Use link (not rename) to AVOID overwriting a fresh lock\n // that a third contender may have acquired at lockPath while the file\n // was in trash: link fails with EEXIST if lockPath exists, leaving\n // the third contender's lock intact (codex P2 review).\n try {\n await link(trashPath, lockPath);\n // link succeeded — remove the redundant trash hard link. The lock\n // now lives only at lockPath.\n await unlink(trashPath).catch(() => undefined);\n } catch {\n // lockPath already exists (a third contender acquired it). Do NOT\n // unlink the moved file — it may be a LIVE lock whose holder is\n // still in its critical section. Destroying it would leave the\n // holder running with no visible lock, breaking mutual exclusion\n // (codex P2). Leave it in trash as a breadcrumb; it is not at\n // lockPath so it does not block other contenders.\n }\n } else {\n // Content matches — but verify the moved file is STILL stale. The\n // original holder may have resumed and heartbeated between our\n // pre-rename stat() and the rename, refreshing the mtime. If so, the\n // holder is live: restore the lock instead of deleting it (codex P2).\n const movedStat = await stat(trashPath);\n if (Date.now() - movedStat.mtimeMs <= staleMs) {\n // Mtime was refreshed — the holder resumed. Restore the lock.\n try {\n await link(trashPath, lockPath);\n await unlink(trashPath).catch(() => undefined);\n } catch {\n // lockPath already exists — another contender acquired it. Do NOT\n // unlink the moved file (it may be a live lock). Leave it in trash.\n }\n } else {\n // Still stale — we broke the right lock. Clean up the trash.\n await unlink(trashPath).catch(() => undefined);\n }\n }\n } catch {\n // Could not read the trash file — clean it up best-effort.\n await unlink(trashPath).catch(() => undefined);\n }\n } catch {\n // The lock changed/vanished between checks — another process handled it.\n }\n}\n\n/**\n * Release the lock ONLY if its content still identifies THIS acquirer (same\n * owner id). Two-stage ownership check:\n *\n * 1. PRE-CHECK (chatgpt-codex-connector P2): read lockPath BEFORE renaming.\n * If the lock is already a replacement (a contender broke our stale lock),\n * return WITHOUT renaming — renaming a replacement out of lockPath leaves\n * it empty, letting a third contender acquire while the replacement holder\n * is still active. The replacement is safe at lockPath; leave it alone.\n *\n * 2. ATOMIC CLAIM: if the pre-check saw our ownerId, rename lockPath→trash\n * (POSIX-atomic) and re-verify on the moved file. A replacement could\n * appear between the pre-check and the rename; if the moved file is no\n * longer ours, restore it via link (non-overwriting). This ties the\n * ownership check to the deletion so a bare readFile-then-unlink TOCTOU\n * cannot delete a fresh replacement (codex P2).\n */\nasync function releaseLock(\n held: HeldLock,\n warn: (message: string, err: unknown) => void,\n onAfterReleaseRenameForTest: (() => Promise<void> | void) | undefined,\n): Promise<void> {\n try {\n // PRE-CHECK (chatgpt-codex-connector P2): read lockPath before renaming. If\n // the lock is no longer ours, a contender broke our stale lock and created a\n // replacement. Return WITHOUT renaming — renaming the replacement out of\n // lockPath leaves it empty, so a third contender could acquire while the\n // replacement holder is still active. The replacement is safe at lockPath.\n let precheck: string;\n try {\n precheck = await readFile(held.path, \"utf8\");\n } catch {\n return; // lock vanished — nothing to release.\n }\n if (!precheck.includes(held.ownerId)) {\n return; // replacement lock — leave it untouched for its holder.\n }\n // It was ours when we read it. Atomically claim via rename, then re-verify\n // on the moved file: a replacement could appear between the pre-check read\n // above and this rename.\n const trashPath = `${held.path}.releasing.${process.pid}.${Date.now()}`;\n await rename(held.path, trashPath);\n // Test seam: simulate a third contender acquiring the now-empty lockPath\n // in the rename-to-restore window. No-op in production.\n if (onAfterReleaseRenameForTest) {\n await onAfterReleaseRenameForTest();\n }\n try {\n const moved = await readFile(trashPath, \"utf8\");\n if (moved.includes(held.ownerId)) {\n // Still our lock — safe to delete.\n await unlink(trashPath).catch(() => undefined);\n } else {\n // Not ours: a replacement appeared between the pre-check and the rename.\n // Restore it via link (non-overwriting — if lockPath already has a newer\n // lock, leave it).\n try {\n await link(trashPath, held.path);\n } catch {\n // lockPath already exists — a newer holder is active. Leave the\n // moved file in trash rather than destroying a live lock (codex P2).\n return;\n }\n await unlink(trashPath).catch(() => undefined);\n }\n } catch {\n // Could not read the moved file — clean it up best-effort.\n await unlink(trashPath).catch(() => undefined);\n }\n } catch (err) {\n // Best-effort release; a stale lock will be broken on the next acquire.\n warn(\"withHeldFileLock release failed\", err);\n }\n}\n\n/**\n * Whether the lock file at `held.path` was written by THIS acquirer (same owner\n * id). Reads the content and matches the `<pid> <owner-uuid>` prefix; the iso\n * timestamp varies so it is not part of the identity check.\n */\nasync function lockHeldBySelf(held: HeldLock): Promise<boolean> {\n try {\n const body = await readFile(held.path, \"utf8\");\n const parts = body.trim().split(/\\s+/);\n const fileOwner = parts[1];\n return typeof fileOwner === \"string\" && fileOwner === held.ownerId;\n } catch {\n return false;\n }\n}\n\n/**\n * Re-assert ownership and refresh the lock mtime for {@link HeldFileLockController.refresh}.\n * Returns `true` only while THIS acquirer still owns the lock. When ownership\n * was lost (a peer stale-broke and replaced the lock while a CPU-bound section\n * blocked the event loop), returns `false` WITHOUT touching the replacement's\n * mtime — mirroring the heartbeat's ownership guard.\n *\n * FAIL-CLOSED on the mtime bump (#2033): a `utimes` failure means we cannot\n * prove the lock is still ours AND fresh — the file may have been stale-broken,\n * deleted, or replaced between the ownership check and the bump — so we report\n * lost ownership rather than let the caller rewrite the ledger on an unrefreshed\n * (soon-stale) or already-replaced lock. After a successful bump we RE-CHECK\n * ownership: a peer could have broken and replaced the lock in the window, in\n * which case `utimes` just refreshed the replacement's mtime and we must not\n * report held.\n */\nasync function refreshHeldLock(\n held: HeldLock,\n warn: (message: string, err: unknown) => void,\n onBeforeRefreshUtimesForTest?: () => Promise<void> | void,\n): Promise<boolean> {\n if (!(await lockHeldBySelf(held))) return false;\n if (onBeforeRefreshUtimesForTest) await onBeforeRefreshUtimesForTest();\n try {\n await utimes(held.path, new Date(), new Date());\n } catch (err) {\n warn(\"withHeldFileLock manual refresh failed\", err);\n return false;\n }\n return lockHeldBySelf(held);\n}\n\nfunction sleep(ms: number): Promise<void> {\n // Manual deferred instead of Promise.withResolvers (ES2024) — plugin-openclaw's\n // standalone tsconfig targets ES2022 lib and this module is reachable from its\n // type graph, so withResolvers would TS2550 there (same fix as\n // extraction-faithfulness.ts:467).\n return new Promise<void>((resolve) => {\n // NOT unref'd: this polls inside an awaited acquire loop, so the caller's\n // await chain keeps the loop alive; unref would let Node exit mid-poll when\n // nothing else is pending (the heartbeat interval IS unref'd separately).\n setTimeout(resolve, ms);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/qmd-daemon-session.ts"],"sourcesContent":["import { abortError, throwIfAborted } from \"./abort-error.js\";\nimport { log } from \"./logger.js\";\nimport { type CommandChildProcess, launchProcess } from \"./runtime/child-process.js\";\nimport { mergeEnv } from \"./runtime/env.js\";\n\nexport type QmdRuntimeEnv = Record<string, string | undefined>;\n\nexport type QmdDaemonSpawn = (\n command: string,\n args: string[],\n options?: Record<string, unknown>\n) => CommandChildProcess;\n\ntype PendingRequest = {\n resolve: (value: unknown) => void;\n reject: (reason: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n cleanup: () => void;\n};\n\nlet nextJsonRpcId = 1;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Drop C0 controls and one ANSI CSI sequence. Char scan on purpose:\n * the regex-safety ratchet flags new regular expressions on changed lines.\n */\nfunction sanitizeForLog(value: string, max = 200): string {\n let out = \"\";\n const cap = Math.min(value.length, max);\n for (let i = 0; i < cap; i++) {\n const code = value.charCodeAt(i);\n if (code === 0x1b && value.charCodeAt(i + 1) === 0x5b) {\n i += 2;\n while (i < cap) {\n const next = value.charCodeAt(i);\n const isLetter = (next >= 65 && next <= 90) || (next >= 97 && next <= 122);\n if (isLetter) break;\n i += 1;\n }\n continue;\n }\n if (code < 0x20 || code === 0x7f) continue;\n out += value[i] ?? \"\";\n }\n if (value.length > max) return `${out}…(truncated)`;\n return out;\n}\n\nfunction isTimeoutMessage(message: string): boolean {\n return message.toLowerCase().includes(\"timed out\");\n}\n\n/**\n * One `qmd mcp` child and its JSON-RPC session.\n *\n * Aborting or timing out a `tools/call` writes MCP `notifications/cancelled`\n * and rejects the caller immediately. The SDK drops the tool result once the\n * server's request controller is aborted, so this session does not wait for\n * a response after cancel. The initialize handshake is not cancelled: a slow\n * model load is supposed to stay alive for the next probe.\n *\n * The child is not killed on cancel. Reloading the rerank model costs longer\n * than the recall budget, and a slow query would then take the warm process\n * down on every timeout.\n */\nexport class QmdDaemonSession {\n private child: CommandChildProcess | null = null;\n private initialized = false;\n private buffer = \"\";\n private startPromise: Promise<boolean> | null = null;\n private pendingRequests = new Map<number, PendingRequest>();\n private readonly qmdPath: string;\n private readonly runtimeEnv: QmdRuntimeEnv;\n private readonly indexName?: string;\n private readonly spawn: QmdDaemonSpawn;\n\n constructor(\n qmdPath: string,\n runtimeEnv: QmdRuntimeEnv = {},\n indexName?: string,\n spawn: QmdDaemonSpawn = launchProcess\n ) {\n this.qmdPath = qmdPath;\n this.runtimeEnv = runtimeEnv;\n this.indexName = indexName?.trim() || undefined;\n this.spawn = spawn;\n }\n\n /** Spawn the qmd mcp child process and perform MCP handshake. */\n async start(): Promise<boolean> {\n if (this.child && !this.child.killed && this.initialized) {\n return true;\n }\n if (this.startPromise) {\n return this.startPromise;\n }\n this.startPromise = (async () => {\n const processAlreadyRunning = this.child != null && !this.child.killed;\n if (!processAlreadyRunning) {\n if (this.child) {\n this.cleanup({ killChild: true });\n }\n try {\n const args = this.indexName ? [\"--index\", this.indexName, \"mcp\"] : [\"mcp\"];\n const child = this.spawn(this.qmdPath, args, {\n env: mergeEnv({ NO_COLOR: \"1\", ...this.runtimeEnv }),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n this.child = child;\n this.buffer = \"\";\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n if (this.child !== child) return;\n this.handleStdoutData(data);\n });\n child.stderr?.on(\"data\", (data: Buffer) => {\n if (this.child !== child) return;\n const msg = data.toString().trim();\n if (msg) log.debug(`QMD mcp stderr: ${sanitizeForLog(msg)}`);\n });\n child.stdin?.on(\"error\", (err) => {\n log.debug(`QMD mcp stdin error (suppressed): ${err.message}`);\n });\n child.on(\"error\", (err) => {\n if (this.child !== child) return;\n log.debug(`QMD mcp process error: ${err.message}`);\n this.cleanup({ child });\n });\n child.on(\"close\", (code) => {\n if (this.child !== child) return;\n log.debug(`QMD mcp process exited (code ${code})`);\n this.cleanup({ child });\n });\n } catch (err) {\n log.debug(`QMD mcp: failed to spawn process: ${err}`);\n this.cleanup({ killChild: true });\n return false;\n }\n } else {\n log.debug(\"QMD mcp: process already running, retrying handshake\");\n }\n\n try {\n const result = await this.sendRequest(\n \"initialize\",\n {\n protocolVersion: \"2024-11-05\",\n capabilities: {},\n clientInfo: { name: \"openclaw-remnic\", version: \"1.0.0\" },\n },\n 60_000\n );\n if (!result) {\n this.cleanup({ killChild: true });\n return false;\n }\n this.sendNotification(\"notifications/initialized\");\n this.initialized = true;\n log.info(\"QMD mcp: stdio session initialized\");\n return true;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (isTimeoutMessage(msg)) {\n log.debug(\"QMD mcp: handshake timed out — process still loading, will retry later\");\n this.initialized = false;\n } else {\n log.debug(`QMD mcp: failed to start stdio session: ${err}`);\n this.cleanup({ killChild: true });\n }\n return false;\n } finally {\n this.startPromise = null;\n }\n })();\n return this.startPromise;\n }\n\n /** Call an MCP tool and return the parsed result. */\n async callTool(\n name: string,\n args: Record<string, unknown>,\n timeoutMs = 30_000,\n signal?: AbortSignal\n ): Promise<unknown> {\n if (!this.child || this.child.killed || !this.initialized) {\n throw new Error(\"QMD mcp process not running\");\n }\n return this.sendRequest(\"tools/call\", { name, arguments: args }, timeoutMs, signal);\n }\n\n /** Kill stdio process and clear state so the next probe can restart. */\n invalidate(): void {\n this.cleanup({ killChild: true });\n }\n\n /** Kill stdio process and wait briefly for the child handle to close. */\n async close(timeoutMs = 1_000): Promise<void> {\n const target = this.child;\n if (!target) {\n this.cleanup({ killChild: true });\n return;\n }\n\n let closed = false;\n const closedPromise = new Promise<void>((resolve) => {\n target.once(\"close\", () => {\n closed = true;\n resolve();\n });\n });\n\n this.cleanup({ killChild: true });\n await Promise.race([closedPromise, sleep(timeoutMs)]);\n if (!closed) {\n try {\n target.kill(\"SIGKILL\");\n } catch {\n // Ignore process-kill races during shutdown.\n }\n await Promise.race([closedPromise, sleep(250)]);\n }\n }\n\n isActive(): boolean {\n return this.child !== null && !this.child.killed && this.initialized;\n }\n\n /** True while the process is spawned but the MCP handshake has not yet completed. */\n isLoading(): boolean {\n return this.child !== null && !this.child.killed && !this.initialized;\n }\n\n private sendRequest(\n method: string,\n params: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal\n ): Promise<unknown> {\n return new Promise((resolve, reject) => {\n throwIfAborted(signal, `QMD mcp ${method} aborted before request`);\n if (!this.child || !this.child.stdin || this.child.killed) {\n reject(new Error(\"QMD mcp process not available\"));\n return;\n }\n\n const id = nextJsonRpcId++;\n let written = false;\n const abandon = (reason: string) => {\n // Only a tool call that actually went out can be holding the worker.\n // initialize stays running across a slow model load.\n if (!written || method !== \"tools/call\") return;\n this.sendNotification(\"notifications/cancelled\", { requestId: id, reason });\n };\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n cleanup();\n const reason = `QMD mcp ${method} timed out after ${timeoutMs}ms`;\n abandon(reason);\n reject(new Error(reason));\n }, timeoutMs);\n const onAbort = () => {\n clearTimeout(timer);\n this.pendingRequests.delete(id);\n cleanup();\n abandon(\"client aborted\");\n reject(abortError(`QMD mcp ${method} aborted`));\n };\n const cleanup = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n };\n\n this.pendingRequests.set(id, { resolve, reject, timer, cleanup });\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n const message = `${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`;\n try {\n this.child.stdin.write(message, (err) => {\n if (!err) return;\n written = false;\n clearTimeout(timer);\n this.pendingRequests.delete(id);\n cleanup();\n reject(new Error(`Failed to write to QMD mcp stdin: ${err.message}`));\n });\n written = true;\n } catch (err) {\n written = false;\n clearTimeout(timer);\n this.pendingRequests.delete(id);\n cleanup();\n const messageText = err instanceof Error ? err.message : String(err);\n reject(new Error(`Failed to write to QMD mcp stdin: ${messageText}`));\n }\n });\n }\n\n private sendNotification(method: string, params?: Record<string, unknown>): void {\n if (!this.child || !this.child.stdin || this.child.killed) return;\n if (this.child.stdin.destroyed) return;\n const msg: Record<string, unknown> = { jsonrpc: \"2.0\", method };\n if (params) msg.params = params;\n try {\n this.child.stdin.write(`${JSON.stringify(msg)}\\n`);\n } catch {\n // Ignore EPIPE / write-after-close\n }\n }\n\n private handleStdoutData(data: Buffer | string): void {\n this.buffer += data.toString();\n let newlineIdx: number;\n // Re-evaluated on every pass so a blank line cannot leave a stale index.\n while ((newlineIdx = this.buffer.indexOf(\"\\n\")) !== -1) {\n const line = this.buffer.slice(0, newlineIdx).trim();\n this.buffer = this.buffer.slice(newlineIdx + 1);\n if (!line) continue;\n try {\n const msg = JSON.parse(line) as Record<string, unknown>;\n this.handleMessage(msg);\n } catch {\n log.debug(`QMD mcp: unparseable stdout: ${sanitizeForLog(line, 200)}`);\n }\n }\n }\n\n private handleMessage(msg: Record<string, unknown>): void {\n if (msg.id !== undefined && msg.id !== null) {\n const pending = this.pendingRequests.get(msg.id as number);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(msg.id as number);\n pending.cleanup();\n if (msg.error) {\n pending.reject(new Error(JSON.stringify(msg.error)));\n } else {\n pending.resolve(msg.result);\n }\n }\n return;\n }\n if (msg.method) {\n log.debug(`QMD mcp notification: ${msg.method}`);\n }\n }\n\n private cleanup(opts?: { killChild?: boolean; child?: CommandChildProcess | null }): void {\n const target = opts?.child ?? this.child;\n if (!target) return;\n if (opts?.child && this.child !== opts.child) {\n return;\n }\n if (opts?.killChild && !target.killed) {\n target.kill(\"SIGTERM\");\n }\n this.initialized = false;\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.cleanup();\n pending.reject(new Error(\"QMD mcp process terminated\"));\n }\n this.pendingRequests.clear();\n this.startPromise = null;\n this.child = null;\n this.buffer = \"\";\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/access-idempotency.ts"],"sourcesContent":["import { mkdir, open, readFile, rename, stat, unlink, utimes, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\n\ntype AccessIdempotencyEntry = {\n recordedAt: string;\n requestHash: string;\n response: unknown;\n};\n\ntype AccessIdempotencyTestHooks = {\n beforeFlushWrite?: () => Promise<void> | void;\n lockTimeoutMs?: number;\n staleLockMs?: number;\n lockHeartbeatMs?: number;\n beforeLockOwnerWrite?: (lockPath: string, ownerToken: string) => Promise<void> | void;\n beforeStaleLockUnlink?: () => Promise<void> | void;\n};\n\nlet testHooks: AccessIdempotencyTestHooks | null = null;\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(value, (_key, candidate) => {\n if (!candidate || typeof candidate !== \"object\" || Array.isArray(candidate)) {\n return candidate;\n }\n return Object.keys(candidate)\n .sort()\n .reduce<Record<string, unknown>>((acc, key) => {\n acc[key] = (candidate as Record<string, unknown>)[key];\n return acc;\n }, {});\n });\n}\n\nexport function hashAccessIdempotencyPayload(payload: unknown): string {\n return createHash(\"sha256\").update(stableStringify(payload)).digest(\"hex\");\n}\n\nexport function setAccessIdempotencyTestHooks(hooks: AccessIdempotencyTestHooks | null): void {\n testHooks = hooks;\n}\n\nexport class AccessIdempotencyStore {\n private readonly statePath: string;\n private readonly lockPath: string;\n private readonly keyLockDir: string;\n private loadedMtimeMs = 0;\n private state: Record<string, AccessIdempotencyEntry> = {};\n private writeQueue: Promise<void> = Promise.resolve();\n\n constructor(memoryDir: string) {\n this.statePath = path.join(memoryDir, \"state\", \"access-idempotency.json\");\n this.lockPath = `${this.statePath}.lock`;\n this.keyLockDir = path.join(memoryDir, \"state\", \"access-idempotency-locks\");\n }\n\n async get(key: string, requestHash: string): Promise<{ response?: unknown; conflict: boolean }> {\n await this.writeQueue.catch(() => {});\n await this.reload({ forceRefresh: true });\n const entry = this.state[key];\n if (!entry) return { conflict: false };\n if (entry.requestHash !== requestHash) {\n return { conflict: true };\n }\n return {\n conflict: false,\n response: JSON.parse(JSON.stringify(entry.response)),\n };\n }\n\n async put(key: string, requestHash: string, response: unknown): Promise<void> {\n await this.withWriteQueue(async () => {\n await this.reload({ forceRefresh: true });\n this.state[key] = {\n recordedAt: new Date().toISOString(),\n requestHash,\n response: JSON.parse(JSON.stringify(response)),\n };\n await this.prune();\n await this.flush();\n });\n }\n\n async withKeyLock<T>(key: string, callback: () => Promise<T>): Promise<T> {\n const keyHash = createHash(\"sha256\").update(key).digest(\"hex\");\n const keyLockPath = path.join(this.keyLockDir, `${keyHash}.lock`);\n return this.withExclusiveFileLock(keyLockPath, callback);\n }\n\n private async reload(options: { forceRefresh?: boolean } = {}): Promise<void> {\n if (options.forceRefresh === true) {\n try {\n const fileStat = await stat(this.statePath);\n if (fileStat.mtimeMs < this.loadedMtimeMs) {\n return;\n }\n } catch {\n this.state = {};\n this.loadedMtimeMs = 0;\n return;\n }\n }\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Record<string, AccessIdempotencyEntry>;\n if (parsed && typeof parsed === \"object\") {\n this.state = parsed;\n this.loadedMtimeMs = await this.readMtimeMs();\n return;\n }\n } catch {\n // Missing or malformed state should fail open to an empty store.\n }\n this.state = {};\n this.loadedMtimeMs = 0;\n }\n\n private async prune(): Promise<void> {\n const entries = Object.entries(this.state);\n if (entries.length <= 512) return;\n entries\n .sort((left, right) => right[1].recordedAt.localeCompare(left[1].recordedAt))\n .slice(512)\n .forEach(([key]) => {\n delete this.state[key];\n });\n }\n\n private async flush(): Promise<void> {\n await mkdir(path.dirname(this.statePath), { recursive: true });\n await this.withExclusiveFileLock(this.lockPath, async () => {\n try {\n const raw = await readFile(this.statePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Record<string, AccessIdempotencyEntry>;\n if (parsed && typeof parsed === \"object\") {\n this.state = {\n ...parsed,\n ...this.state,\n };\n await this.prune();\n }\n } catch {\n // Fail open when there is no pre-existing shared state to merge.\n }\n\n await testHooks?.beforeFlushWrite?.();\n\n const tempPath = `${this.statePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;\n try {\n await writeFile(tempPath, JSON.stringify(this.state, null, 2), \"utf-8\");\n await rename(tempPath, this.statePath);\n } finally {\n await unlink(tempPath).catch(() => undefined);\n }\n this.loadedMtimeMs = await this.readMtimeMs();\n });\n }\n\n private async readMtimeMs(): Promise<number> {\n try {\n return (await stat(this.statePath)).mtimeMs;\n } catch {\n return 0;\n }\n }\n\n private async withExclusiveFileLock<T>(lockPath: string, callback: () => Promise<T>): Promise<T> {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const timeoutMs = testHooks?.lockTimeoutMs ?? 5_000;\n const staleLockMs = testHooks?.staleLockMs ?? 30_000;\n const lockHeartbeatMs = testHooks?.lockHeartbeatMs ?? Math.max(1_000, Math.floor(staleLockMs / 3));\n const startedAt = Date.now();\n\n while (true) {\n const ownerToken = `${process.pid}:${randomUUID()}`;\n try {\n const handle = await open(lockPath, \"wx\");\n try {\n await testHooks?.beforeLockOwnerWrite?.(lockPath, ownerToken);\n await handle.writeFile(ownerToken, \"utf-8\");\n } catch (error) {\n await handle.close().catch(() => undefined);\n await unlink(lockPath).catch(() => undefined);\n throw error;\n }\n let heartbeat: NodeJS.Timeout | null = null;\n if (lockHeartbeatMs > 0) {\n heartbeat = setInterval(() => {\n void utimes(lockPath, new Date(), new Date()).catch(() => undefined);\n }, lockHeartbeatMs);\n heartbeat.unref?.();\n }\n try {\n return await callback();\n } finally {\n if (heartbeat) {\n clearInterval(heartbeat);\n }\n await handle.close().catch(() => undefined);\n await unlinkLockIfOwner(lockPath, ownerToken, staleLockMs);\n }\n } catch (error) {\n if (!isAlreadyExistsError(error)) throw error;\n try {\n const lockStat = await stat(lockPath);\n const lockOwner = await readLockOwner(lockPath);\n if (Date.now() - lockStat.mtimeMs > staleLockMs) {\n await testHooks?.beforeStaleLockUnlink?.();\n const removed = await unlinkStaleLockIfUnchanged({\n lockPath,\n observedOwner: lockOwner,\n observedMtimeMs: lockStat.mtimeMs,\n staleLockMs,\n });\n if (!removed) {\n await sleep(10);\n }\n continue;\n }\n } catch {\n continue;\n }\n if (Date.now() - startedAt > timeoutMs) {\n throw new Error(\"timed out acquiring access idempotency lock\");\n }\n await sleep(10);\n }\n }\n }\n\n private async withWriteQueue<T>(callback: () => Promise<T>): Promise<T> {\n const previous = this.writeQueue;\n let release!: () => void;\n this.writeQueue = new Promise<void>((resolve) => {\n release = resolve;\n });\n await previous.catch(() => {});\n try {\n return await callback();\n } finally {\n release();\n }\n }\n}\n\nfunction isAlreadyExistsError(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"EEXIST\";\n}\n\nasync function readLockOwner(lockPath: string): Promise<string | null> {\n try {\n return await readFile(lockPath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function unlinkLockIfOwner(lockPath: string, ownerToken: string, staleLockMs: number): Promise<void> {\n const reclaimHandle = await openStaleReclaimLock(`${lockPath}.reclaim`, staleLockMs);\n if (!reclaimHandle) return;\n try {\n await reclaimHandle.writeFile(`${process.pid}:${Date.now()}`, \"utf-8\");\n await unlinkLockIfOwnerWhileReclaimHeld(lockPath, ownerToken);\n } finally {\n await reclaimHandle.close().catch(() => undefined);\n await unlink(`${lockPath}.reclaim`).catch(() => undefined);\n }\n}\n\nasync function unlinkLockIfOwnerWhileReclaimHeld(lockPath: string, ownerToken: string): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>>;\n try {\n handle = await open(lockPath, \"r\");\n } catch {\n return;\n }\n try {\n const openedStat = await handle.stat();\n const currentOwner = await handle.readFile(\"utf-8\");\n if (currentOwner !== ownerToken) return;\n let currentStat: Awaited<ReturnType<typeof stat>>;\n try {\n currentStat = await stat(lockPath);\n } catch {\n return;\n }\n if (!isSameFileIdentity(openedStat, currentStat)) return;\n } finally {\n await handle.close().catch(() => undefined);\n }\n await unlink(lockPath).catch(() => undefined);\n}\n\nfunction isSameFileIdentity(left: Awaited<ReturnType<typeof stat>>, right: Awaited<ReturnType<typeof stat>>): boolean {\n return left.dev === right.dev && left.ino === right.ino;\n}\n\nasync function unlinkStaleLockIfUnchanged(options: {\n lockPath: string;\n observedOwner: string | null;\n observedMtimeMs: number;\n staleLockMs: number;\n}): Promise<boolean> {\n const reclaimPath = `${options.lockPath}.reclaim`;\n const reclaimHandle = await openStaleReclaimLock(reclaimPath, options.staleLockMs);\n if (!reclaimHandle) return false;\n try {\n await reclaimHandle.writeFile(`${process.pid}:${Date.now()}`, \"utf-8\");\n return await unlinkStaleLockIfStillOwned(options);\n } finally {\n await reclaimHandle.close().catch(() => undefined);\n await unlink(reclaimPath).catch(() => undefined);\n }\n}\n\nasync function openStaleReclaimLock(\n reclaimPath: string,\n staleLockMs: number,\n): Promise<Awaited<ReturnType<typeof open>> | null> {\n try {\n return await open(reclaimPath, \"wx\");\n } catch (error) {\n if (!isAlreadyExistsError(error)) throw error;\n }\n\n try {\n const reclaimStat = await stat(reclaimPath);\n if (Date.now() - reclaimStat.mtimeMs <= staleLockMs) return null;\n await unlink(reclaimPath);\n } catch {\n return null;\n }\n\n try {\n return await open(reclaimPath, \"wx\");\n } catch (error) {\n if (isAlreadyExistsError(error)) return null;\n throw error;\n }\n}\n\nasync function unlinkStaleLockIfStillOwned(options: {\n lockPath: string;\n observedOwner: string | null;\n observedMtimeMs: number;\n staleLockMs: number;\n}): Promise<boolean> {\n let currentStat: Awaited<ReturnType<typeof stat>>;\n try {\n currentStat = await stat(options.lockPath);\n } catch {\n return true;\n }\n if (currentStat.mtimeMs !== options.observedMtimeMs) return false;\n if (Date.now() - currentStat.mtimeMs <= options.staleLockMs) return false;\n const currentOwner = await readLockOwner(options.lockPath);\n if (currentOwner !== options.observedOwner) return false;\n await unlink(options.lockPath);\n return true;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}