@remnic/core 10.0.68 → 10.0.70

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 (356) hide show
  1. package/dist/access-admin-ops-surface.js +80 -77
  2. package/dist/access-authorization-probe.js +81 -78
  3. package/dist/access-boundary.js +80 -77
  4. package/dist/access-cli.js +194 -191
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +80 -77
  7. package/dist/access-http-briefing.js +81 -78
  8. package/dist/access-http-query.js +82 -79
  9. package/dist/access-http.js +104 -101
  10. package/dist/access-identity-continuity-surface.js +80 -77
  11. package/dist/access-lcm-surface.js +80 -77
  12. package/dist/access-mcp.js +93 -90
  13. package/dist/access-namespace-preflight.js +80 -77
  14. package/dist/access-observe-lcm-archive.d.ts +5 -0
  15. package/dist/access-observe-lcm-archive.d.ts.map +1 -0
  16. package/dist/access-observe-lcm-archive.js +9 -0
  17. package/dist/access-observe-objective-state.d.ts +8 -0
  18. package/dist/access-observe-objective-state.d.ts.map +1 -0
  19. package/dist/access-observe-objective-state.js +17 -0
  20. package/dist/access-observe-stage-timing.d.ts +11 -0
  21. package/dist/access-observe-stage-timing.d.ts.map +1 -0
  22. package/dist/access-observe-stage-timing.js +9 -0
  23. package/dist/access-observe-stage-timing.js.map +1 -0
  24. package/dist/access-observe-write-category.js +13 -13
  25. package/dist/access-observe-write-surface.d.ts.map +1 -1
  26. package/dist/access-observe-write-surface.js +80 -77
  27. package/dist/access-offline-manifest.js +8 -8
  28. package/dist/access-operations-batch.js +83 -80
  29. package/dist/access-operations.js +90 -87
  30. package/dist/access-recall-concurrency.js +80 -77
  31. package/dist/access-recall-response.js +80 -77
  32. package/dist/access-recall-surface.js +80 -77
  33. package/dist/access-schema.js +12 -12
  34. package/dist/access-service.d.ts +1 -0
  35. package/dist/access-service.d.ts.map +1 -1
  36. package/dist/access-service.js +80 -77
  37. package/dist/access-surface-catalog.js +82 -79
  38. package/dist/access-surface-catalog.js.map +1 -1
  39. package/dist/active-recall.js +24 -24
  40. package/dist/adapters/index.js +4 -4
  41. package/dist/adapters/registry.js +2 -2
  42. package/dist/briefing.js +15 -15
  43. package/dist/buffer.js +8 -8
  44. package/dist/calibration.js +4 -4
  45. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  46. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  47. package/dist/causal-behavior.js +2 -2
  48. package/dist/causal-chain.js +2 -2
  49. package/dist/causal-consolidation.js +20 -20
  50. package/dist/causal-retrieval.js +2 -2
  51. package/dist/causal-trajectory-graph.js +3 -3
  52. package/dist/causal-trajectory.js +1 -1
  53. package/dist/{chunk-NCZGGGWI.js → chunk-2BBMCMJS.js} +12 -12
  54. package/dist/{chunk-UVVFPCPX.js → chunk-2GV24KFD.js} +2 -2
  55. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  56. package/dist/{chunk-QDZWQBLU.js → chunk-2R7OCBRR.js} +2 -2
  57. package/dist/{chunk-U2CAGMUD.js → chunk-2YPIFJHL.js} +2 -2
  58. package/dist/{chunk-4SOWMA55.js → chunk-3AASGFR4.js} +3 -3
  59. package/dist/{chunk-CI3MAAIY.js → chunk-3F7N4I5I.js} +2 -2
  60. package/dist/{chunk-PLCYKAWH.js → chunk-3ICAWZUU.js} +4 -4
  61. package/dist/{chunk-B4DXREWC.js → chunk-3W37NITP.js} +2 -2
  62. package/dist/{chunk-R4ZHF4K2.js → chunk-43FLJLT2.js} +4 -4
  63. package/dist/{chunk-RLVI2M5Q.js → chunk-4BOBW6H4.js} +2 -2
  64. package/dist/{chunk-HXFIILGF.js → chunk-4RASUAN3.js} +4 -4
  65. package/dist/{chunk-JLMOH5JA.js → chunk-5FMGPV34.js} +2 -2
  66. package/dist/{chunk-EI6M5NVK.js → chunk-5JBVPJCT.js} +2 -2
  67. package/dist/{chunk-74AY3S7U.js → chunk-5RW247F5.js} +10 -10
  68. package/dist/{chunk-P4TV3TQ2.js → chunk-5VQSKBNF.js} +5 -5
  69. package/dist/{chunk-H5DIRCZP.js → chunk-64U42JL5.js} +18 -18
  70. package/dist/{chunk-4XINO77L.js → chunk-66JOLMWS.js} +2 -2
  71. package/dist/{chunk-WHROVQ5Q.js → chunk-66PEACZP.js} +3 -3
  72. package/dist/{chunk-GKL6QYZO.js → chunk-6L63IN3M.js} +1 -1
  73. package/dist/{chunk-Y3XPSGNF.js → chunk-6QS5NQJH.js} +2 -2
  74. package/dist/{chunk-KOLJJN5G.js → chunk-AQNHEDXP.js} +11 -4
  75. package/dist/chunk-AQNHEDXP.js.map +1 -0
  76. package/dist/{chunk-6WKWL53V.js → chunk-B7YAIHAV.js} +127 -121
  77. package/dist/chunk-B7YAIHAV.js.map +1 -0
  78. package/dist/{chunk-M4IEZGCK.js → chunk-BFBS7OFQ.js} +7 -7
  79. package/dist/{chunk-4PG5R6BJ.js → chunk-BNU5TQLR.js} +2 -2
  80. package/dist/{chunk-B3MWELZ3.js → chunk-BW33YTF3.js} +2 -2
  81. package/dist/{chunk-H6VJSITJ.js → chunk-CIL4GHMD.js} +3 -3
  82. package/dist/{chunk-5UM4H64E.js → chunk-D3AGBN6G.js} +2 -2
  83. package/dist/{chunk-IG3EZ3TF.js → chunk-DM3UZUCN.js} +2 -2
  84. package/dist/{chunk-PMDOGXA2.js → chunk-EF3HNVL4.js} +2 -2
  85. package/dist/{chunk-TVE7W67N.js → chunk-EIZ4MK33.js} +2 -2
  86. package/dist/{chunk-AUFPPBC3.js → chunk-ENPJX3BB.js} +3 -3
  87. package/dist/{chunk-63A2BU2B.js → chunk-F4ZHKDBW.js} +5 -5
  88. package/dist/{chunk-3LBDEGQA.js → chunk-FDUCIUQ5.js} +2 -2
  89. package/dist/{chunk-CTH2CQCB.js → chunk-FM6UKZAE.js} +2 -2
  90. package/dist/{chunk-XVZA6VXC.js → chunk-FZVROZKC.js} +4 -4
  91. package/dist/{chunk-IAGVM7EY.js → chunk-GMSRLDVR.js} +2 -2
  92. package/dist/{chunk-HPB5TF55.js → chunk-GT4G2S2Y.js} +3 -3
  93. package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
  94. package/dist/{chunk-5MGKGEL7.js → chunk-HNF7IMYQ.js} +2 -2
  95. package/dist/{chunk-W7K5BP3M.js → chunk-HQPFACXO.js} +2 -2
  96. package/dist/{chunk-TECK5IVY.js → chunk-HZNURRDS.js} +2 -2
  97. package/dist/{chunk-SXTVZ6JL.js → chunk-I6HRHJRA.js} +2 -2
  98. package/dist/{chunk-NHWDTOBN.js → chunk-ILD7N3XC.js} +20 -20
  99. package/dist/{chunk-4NV7O76Q.js → chunk-J5TVHCDG.js} +2 -2
  100. package/dist/{chunk-AL3TCNSJ.js → chunk-J6GZ2254.js} +4 -4
  101. package/dist/{chunk-OH55F76C.js → chunk-JEX2JL3U.js} +2 -2
  102. package/dist/{chunk-AVGDYYMA.js → chunk-JGQEF33K.js} +3 -3
  103. package/dist/{chunk-6P7U3HWG.js → chunk-K36RMINQ.js} +3 -3
  104. package/dist/{chunk-UW6LBFYN.js → chunk-K47G2CBP.js} +6 -6
  105. package/dist/{chunk-3RHU5MD7.js → chunk-K7LLGCPH.js} +2 -2
  106. package/dist/{chunk-BUKK5SWA.js → chunk-KQAFEZQX.js} +2 -2
  107. package/dist/chunk-L3E7MLMK.js +20 -0
  108. package/dist/chunk-L3E7MLMK.js.map +1 -0
  109. package/dist/{chunk-R2BIVU2Z.js → chunk-LTST4R7U.js} +2 -2
  110. package/dist/{chunk-IZBIDPMY.js → chunk-LYTKNK5O.js} +2 -2
  111. package/dist/{chunk-HH73M65H.js → chunk-MCFI6G7U.js} +3 -3
  112. package/dist/{chunk-V6SBA5O7.js → chunk-MFKBYXBY.js} +5 -5
  113. package/dist/{chunk-AYJQ644F.js → chunk-MK7HXGM6.js} +2 -2
  114. package/dist/{chunk-MOIJWWNA.js → chunk-MKMIY3E7.js} +13 -13
  115. package/dist/{chunk-EXQJMFRF.js → chunk-MRN2MKB7.js} +2 -2
  116. package/dist/{chunk-W26D2USW.js → chunk-MUH7RR6T.js} +6 -6
  117. package/dist/{chunk-64JLBFCQ.js → chunk-N7NAGECH.js} +2 -2
  118. package/dist/{chunk-75DCOS6I.js → chunk-OAKOAOEG.js} +67 -67
  119. package/dist/{chunk-FAF4A76Q.js → chunk-OAQO5AZJ.js} +512 -378
  120. package/dist/chunk-OAQO5AZJ.js.map +1 -0
  121. package/dist/{chunk-OPFVXM2D.js → chunk-OX6GY22Q.js} +2 -2
  122. package/dist/chunk-QCFZDYYG.js +34 -0
  123. package/dist/chunk-QCFZDYYG.js.map +1 -0
  124. package/dist/{chunk-MMCO4SRG.js → chunk-QNN4FYBD.js} +19 -11
  125. package/dist/chunk-QNN4FYBD.js.map +1 -0
  126. package/dist/{chunk-XWE3EOWX.js → chunk-RZED7FAU.js} +2 -2
  127. package/dist/{chunk-YSATDEBY.js → chunk-SBRUTD5O.js} +5 -5
  128. package/dist/{chunk-WVP2I6C5.js → chunk-SDL22RRW.js} +50 -11
  129. package/dist/chunk-SDL22RRW.js.map +1 -0
  130. package/dist/{chunk-ZORS5BG5.js → chunk-SQO5SEKA.js} +10 -10
  131. package/dist/{chunk-PRKNAIGZ.js → chunk-T6T2RMWW.js} +5 -5
  132. package/dist/{chunk-O5ZINWW3.js → chunk-THFSCUFC.js} +2 -2
  133. package/dist/{chunk-CIEEYVUB.js → chunk-TPX7XXY4.js} +2 -2
  134. package/dist/{chunk-OCK4D5PC.js → chunk-TSZP7GGJ.js} +2 -2
  135. package/dist/{chunk-L334PSLW.js → chunk-U2A56LLF.js} +8 -8
  136. package/dist/{chunk-TGQW3AMK.js → chunk-UIFWZMO7.js} +23 -23
  137. package/dist/{chunk-LN4X2NDA.js → chunk-UJ2FP2RV.js} +10 -10
  138. package/dist/{chunk-4MPAKYZU.js → chunk-VDYRXI3E.js} +5 -5
  139. package/dist/{chunk-IHCXMYSE.js → chunk-VPEIWMUT.js} +2 -2
  140. package/dist/{chunk-ELHJK7LM.js → chunk-WBN66HPI.js} +6 -6
  141. package/dist/{chunk-BJLCV2P3.js → chunk-WGRAB3KB.js} +2 -2
  142. package/dist/{chunk-IVUVQPTQ.js → chunk-WIW4LOZL.js} +2 -2
  143. package/dist/{chunk-5KDDAQ6K.js → chunk-WN2DASTS.js} +10 -10
  144. package/dist/chunk-WZU5AGO6.js +38 -0
  145. package/dist/chunk-WZU5AGO6.js.map +1 -0
  146. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  147. package/dist/{chunk-5Y6VA6X2.js → chunk-XDUP6SWG.js} +2 -2
  148. package/dist/{chunk-GHHQLSSY.js → chunk-XR4QR3JT.js} +3 -3
  149. package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
  150. package/dist/{chunk-QGFR73SN.js → chunk-YIG4HPY4.js} +2 -2
  151. package/dist/{chunk-UYUFDTIX.js → chunk-YIXWY4OZ.js} +2 -2
  152. package/dist/{chunk-GJ4HU6LK.js → chunk-YLMTFRZR.js} +2 -2
  153. package/dist/{chunk-GGD2YDS6.js → chunk-YRMSHA7H.js} +2 -2
  154. package/dist/{chunk-Y7427SL7.js → chunk-ZG4GNPRE.js} +216 -50
  155. package/dist/chunk-ZG4GNPRE.js.map +1 -0
  156. package/dist/{chunk-KZZ5EZ7O.js → chunk-ZS5LREUZ.js} +2 -2
  157. package/dist/{chunk-JLGBJ4AX.js → chunk-ZYEWTVNQ.js} +10 -10
  158. package/dist/cli.js +131 -128
  159. package/dist/coding/export-okf-codegraph.js +14 -14
  160. package/dist/coding/pre-action-gate.js +2 -2
  161. package/dist/compounding/engine.js +16 -16
  162. package/dist/config.js +24 -24
  163. package/dist/connectors/codex-materialize-runner.js +17 -17
  164. package/dist/connectors/codex-materialize.js +2 -2
  165. package/dist/connectors/index.js +18 -18
  166. package/dist/consolidation-provenance-check.js +2 -2
  167. package/dist/convergence-refresh.js +18 -18
  168. package/dist/corpus-watermark.js +18 -18
  169. package/dist/deep-recall-http-glue.js +13 -13
  170. package/dist/deep-recall.js +2 -2
  171. package/dist/embedding-fallback.js +2 -2
  172. package/dist/entity-retrieval.js +18 -18
  173. package/dist/event-order-recall.js +2 -2
  174. package/dist/external-wiki-access.js +81 -78
  175. package/dist/extraction-span-materialize.js +2 -2
  176. package/dist/extraction.js +30 -30
  177. package/dist/{graph-edge-decay-OEX5OGMI.js → graph-edge-decay-CXTNJ7IQ.js} +6 -6
  178. package/dist/graph-edge-decay-CXTNJ7IQ.js.map +1 -0
  179. package/dist/graph-jsonl.js +4 -4
  180. package/dist/graph-snapshot.js +4 -4
  181. package/dist/graph.js +3 -3
  182. package/dist/harmonic-retrieval.js +15 -15
  183. package/dist/index.js +279 -276
  184. package/dist/lcm/archive.d.ts +12 -2
  185. package/dist/lcm/archive.d.ts.map +1 -1
  186. package/dist/lcm/archive.js +2 -2
  187. package/dist/lcm/engine.d.ts +59 -8
  188. package/dist/lcm/engine.d.ts.map +1 -1
  189. package/dist/lcm/engine.js +6 -6
  190. package/dist/lcm/index.js +16 -16
  191. package/dist/lcm/queue.d.ts +7 -3
  192. package/dist/lcm/queue.d.ts.map +1 -1
  193. package/dist/lcm/queue.js +1 -1
  194. package/dist/lcm/summarizer.d.ts +8 -1
  195. package/dist/lcm/summarizer.d.ts.map +1 -1
  196. package/dist/lcm/summarizer.js +3 -3
  197. package/dist/local-llm.js +4 -4
  198. package/dist/location/index.js +17 -17
  199. package/dist/maintenance/memory-governance.js +16 -16
  200. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -15
  201. package/dist/maintenance/rebuild-memory-projection-runner.js +18 -18
  202. package/dist/maintenance/rebuild-memory-projection.js +17 -17
  203. package/dist/memory-browse-operations.js +81 -78
  204. package/dist/memory-browse.js +6 -6
  205. package/dist/namespaces/migrate.js +16 -16
  206. package/dist/namespaces/search.js +7 -7
  207. package/dist/namespaces/storage.js +15 -15
  208. package/dist/offline-sync-changeset-generations.js +5 -5
  209. package/dist/offline-sync-embedding-generation.js +4 -4
  210. package/dist/offline-sync-snapshot-stream.js +7 -7
  211. package/dist/offline-sync.js +9 -9
  212. package/dist/operator-doctor-corpus.js +19 -19
  213. package/dist/operator-toolkit.js +39 -39
  214. package/dist/orchestration/buffer-drain-runner.js +194 -191
  215. package/dist/orchestration/buffer-drain-runner.js.map +1 -1
  216. package/dist/orchestration/extraction-persist.d.ts.map +1 -1
  217. package/dist/orchestration/maintenance.js +31 -31
  218. package/dist/orchestration/promotion-dedup-window.d.ts +118 -0
  219. package/dist/orchestration/promotion-dedup-window.d.ts.map +1 -0
  220. package/dist/orchestrator.js +195 -192
  221. package/dist/qmd.js +4 -4
  222. package/dist/recall-navigation-operations.js +81 -78
  223. package/dist/recall-navigation.js +80 -77
  224. package/dist/recall-state-view-admission.js +6 -6
  225. package/dist/recall-why-service.js +6 -6
  226. package/dist/resume-bundles.js +27 -27
  227. package/dist/search/factory.js +6 -6
  228. package/dist/search/index.js +12 -12
  229. package/dist/semantic-consolidation.js +23 -23
  230. package/dist/semantic-rule-promotion.js +15 -15
  231. package/dist/semantic-rule-verifier.js +15 -15
  232. package/dist/source-agent-qualifier.js +22 -22
  233. package/dist/standup/build.js +15 -15
  234. package/dist/storage.js +14 -14
  235. package/dist/summarizer.js +6 -6
  236. package/dist/support-passport/index.js +83 -80
  237. package/dist/transfer/backup.js +2 -2
  238. package/dist/transfer/capsule-export.js +2 -2
  239. package/dist/transfer/capsule-import.js +2 -2
  240. package/dist/transfer/export-okf.js +15 -15
  241. package/dist/verified-recall.js +15 -15
  242. package/dist/who-knows-cli.js +16 -16
  243. package/dist/who-knows.js +15 -15
  244. package/package.json +2 -2
  245. package/src/access-observe-lcm-archive.ts +30 -0
  246. package/src/access-observe-lcm-fail-closed.test.ts +118 -0
  247. package/src/access-observe-objective-state.ts +40 -0
  248. package/src/access-observe-stage-timing.test.ts +246 -0
  249. package/src/access-observe-stage-timing.ts +58 -0
  250. package/src/access-observe-write-surface.ts +45 -45
  251. package/src/access-service.ts +1 -0
  252. package/src/extraction-shared-promotion-parallel-dedup.test.ts +212 -0
  253. package/src/lcm/archive.ts +59 -7
  254. package/src/lcm/engine.ts +256 -53
  255. package/src/lcm/queue.test.ts +32 -14
  256. package/src/lcm/queue.ts +24 -16
  257. package/src/lcm/summarizer.ts +15 -2
  258. package/src/lcm-durability.test.ts +856 -0
  259. package/src/lcm-engine.test.ts +12 -3
  260. package/src/orchestration/extraction-persist.ts +40 -35
  261. package/src/orchestration/promotion-dedup-window.ts +319 -0
  262. package/dist/chunk-6WKWL53V.js.map +0 -1
  263. package/dist/chunk-FAF4A76Q.js.map +0 -1
  264. package/dist/chunk-KOLJJN5G.js.map +0 -1
  265. package/dist/chunk-MMCO4SRG.js.map +0 -1
  266. package/dist/chunk-WVP2I6C5.js.map +0 -1
  267. package/dist/chunk-Y7427SL7.js.map +0 -1
  268. /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-observe-lcm-archive.js.map} +0 -0
  269. /package/dist/{graph-edge-decay-OEX5OGMI.js.map → access-observe-objective-state.js.map} +0 -0
  270. /package/dist/{chunk-NCZGGGWI.js.map → chunk-2BBMCMJS.js.map} +0 -0
  271. /package/dist/{chunk-UVVFPCPX.js.map → chunk-2GV24KFD.js.map} +0 -0
  272. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  273. /package/dist/{chunk-QDZWQBLU.js.map → chunk-2R7OCBRR.js.map} +0 -0
  274. /package/dist/{chunk-U2CAGMUD.js.map → chunk-2YPIFJHL.js.map} +0 -0
  275. /package/dist/{chunk-4SOWMA55.js.map → chunk-3AASGFR4.js.map} +0 -0
  276. /package/dist/{chunk-CI3MAAIY.js.map → chunk-3F7N4I5I.js.map} +0 -0
  277. /package/dist/{chunk-PLCYKAWH.js.map → chunk-3ICAWZUU.js.map} +0 -0
  278. /package/dist/{chunk-B4DXREWC.js.map → chunk-3W37NITP.js.map} +0 -0
  279. /package/dist/{chunk-R4ZHF4K2.js.map → chunk-43FLJLT2.js.map} +0 -0
  280. /package/dist/{chunk-RLVI2M5Q.js.map → chunk-4BOBW6H4.js.map} +0 -0
  281. /package/dist/{chunk-HXFIILGF.js.map → chunk-4RASUAN3.js.map} +0 -0
  282. /package/dist/{chunk-JLMOH5JA.js.map → chunk-5FMGPV34.js.map} +0 -0
  283. /package/dist/{chunk-EI6M5NVK.js.map → chunk-5JBVPJCT.js.map} +0 -0
  284. /package/dist/{chunk-74AY3S7U.js.map → chunk-5RW247F5.js.map} +0 -0
  285. /package/dist/{chunk-P4TV3TQ2.js.map → chunk-5VQSKBNF.js.map} +0 -0
  286. /package/dist/{chunk-H5DIRCZP.js.map → chunk-64U42JL5.js.map} +0 -0
  287. /package/dist/{chunk-4XINO77L.js.map → chunk-66JOLMWS.js.map} +0 -0
  288. /package/dist/{chunk-WHROVQ5Q.js.map → chunk-66PEACZP.js.map} +0 -0
  289. /package/dist/{chunk-GKL6QYZO.js.map → chunk-6L63IN3M.js.map} +0 -0
  290. /package/dist/{chunk-Y3XPSGNF.js.map → chunk-6QS5NQJH.js.map} +0 -0
  291. /package/dist/{chunk-M4IEZGCK.js.map → chunk-BFBS7OFQ.js.map} +0 -0
  292. /package/dist/{chunk-4PG5R6BJ.js.map → chunk-BNU5TQLR.js.map} +0 -0
  293. /package/dist/{chunk-B3MWELZ3.js.map → chunk-BW33YTF3.js.map} +0 -0
  294. /package/dist/{chunk-H6VJSITJ.js.map → chunk-CIL4GHMD.js.map} +0 -0
  295. /package/dist/{chunk-5UM4H64E.js.map → chunk-D3AGBN6G.js.map} +0 -0
  296. /package/dist/{chunk-IG3EZ3TF.js.map → chunk-DM3UZUCN.js.map} +0 -0
  297. /package/dist/{chunk-PMDOGXA2.js.map → chunk-EF3HNVL4.js.map} +0 -0
  298. /package/dist/{chunk-TVE7W67N.js.map → chunk-EIZ4MK33.js.map} +0 -0
  299. /package/dist/{chunk-AUFPPBC3.js.map → chunk-ENPJX3BB.js.map} +0 -0
  300. /package/dist/{chunk-63A2BU2B.js.map → chunk-F4ZHKDBW.js.map} +0 -0
  301. /package/dist/{chunk-3LBDEGQA.js.map → chunk-FDUCIUQ5.js.map} +0 -0
  302. /package/dist/{chunk-CTH2CQCB.js.map → chunk-FM6UKZAE.js.map} +0 -0
  303. /package/dist/{chunk-XVZA6VXC.js.map → chunk-FZVROZKC.js.map} +0 -0
  304. /package/dist/{chunk-IAGVM7EY.js.map → chunk-GMSRLDVR.js.map} +0 -0
  305. /package/dist/{chunk-HPB5TF55.js.map → chunk-GT4G2S2Y.js.map} +0 -0
  306. /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
  307. /package/dist/{chunk-5MGKGEL7.js.map → chunk-HNF7IMYQ.js.map} +0 -0
  308. /package/dist/{chunk-W7K5BP3M.js.map → chunk-HQPFACXO.js.map} +0 -0
  309. /package/dist/{chunk-TECK5IVY.js.map → chunk-HZNURRDS.js.map} +0 -0
  310. /package/dist/{chunk-SXTVZ6JL.js.map → chunk-I6HRHJRA.js.map} +0 -0
  311. /package/dist/{chunk-NHWDTOBN.js.map → chunk-ILD7N3XC.js.map} +0 -0
  312. /package/dist/{chunk-4NV7O76Q.js.map → chunk-J5TVHCDG.js.map} +0 -0
  313. /package/dist/{chunk-AL3TCNSJ.js.map → chunk-J6GZ2254.js.map} +0 -0
  314. /package/dist/{chunk-OH55F76C.js.map → chunk-JEX2JL3U.js.map} +0 -0
  315. /package/dist/{chunk-AVGDYYMA.js.map → chunk-JGQEF33K.js.map} +0 -0
  316. /package/dist/{chunk-6P7U3HWG.js.map → chunk-K36RMINQ.js.map} +0 -0
  317. /package/dist/{chunk-UW6LBFYN.js.map → chunk-K47G2CBP.js.map} +0 -0
  318. /package/dist/{chunk-3RHU5MD7.js.map → chunk-K7LLGCPH.js.map} +0 -0
  319. /package/dist/{chunk-BUKK5SWA.js.map → chunk-KQAFEZQX.js.map} +0 -0
  320. /package/dist/{chunk-R2BIVU2Z.js.map → chunk-LTST4R7U.js.map} +0 -0
  321. /package/dist/{chunk-IZBIDPMY.js.map → chunk-LYTKNK5O.js.map} +0 -0
  322. /package/dist/{chunk-HH73M65H.js.map → chunk-MCFI6G7U.js.map} +0 -0
  323. /package/dist/{chunk-V6SBA5O7.js.map → chunk-MFKBYXBY.js.map} +0 -0
  324. /package/dist/{chunk-AYJQ644F.js.map → chunk-MK7HXGM6.js.map} +0 -0
  325. /package/dist/{chunk-MOIJWWNA.js.map → chunk-MKMIY3E7.js.map} +0 -0
  326. /package/dist/{chunk-EXQJMFRF.js.map → chunk-MRN2MKB7.js.map} +0 -0
  327. /package/dist/{chunk-W26D2USW.js.map → chunk-MUH7RR6T.js.map} +0 -0
  328. /package/dist/{chunk-64JLBFCQ.js.map → chunk-N7NAGECH.js.map} +0 -0
  329. /package/dist/{chunk-75DCOS6I.js.map → chunk-OAKOAOEG.js.map} +0 -0
  330. /package/dist/{chunk-OPFVXM2D.js.map → chunk-OX6GY22Q.js.map} +0 -0
  331. /package/dist/{chunk-XWE3EOWX.js.map → chunk-RZED7FAU.js.map} +0 -0
  332. /package/dist/{chunk-YSATDEBY.js.map → chunk-SBRUTD5O.js.map} +0 -0
  333. /package/dist/{chunk-ZORS5BG5.js.map → chunk-SQO5SEKA.js.map} +0 -0
  334. /package/dist/{chunk-PRKNAIGZ.js.map → chunk-T6T2RMWW.js.map} +0 -0
  335. /package/dist/{chunk-O5ZINWW3.js.map → chunk-THFSCUFC.js.map} +0 -0
  336. /package/dist/{chunk-CIEEYVUB.js.map → chunk-TPX7XXY4.js.map} +0 -0
  337. /package/dist/{chunk-OCK4D5PC.js.map → chunk-TSZP7GGJ.js.map} +0 -0
  338. /package/dist/{chunk-L334PSLW.js.map → chunk-U2A56LLF.js.map} +0 -0
  339. /package/dist/{chunk-TGQW3AMK.js.map → chunk-UIFWZMO7.js.map} +0 -0
  340. /package/dist/{chunk-LN4X2NDA.js.map → chunk-UJ2FP2RV.js.map} +0 -0
  341. /package/dist/{chunk-4MPAKYZU.js.map → chunk-VDYRXI3E.js.map} +0 -0
  342. /package/dist/{chunk-IHCXMYSE.js.map → chunk-VPEIWMUT.js.map} +0 -0
  343. /package/dist/{chunk-ELHJK7LM.js.map → chunk-WBN66HPI.js.map} +0 -0
  344. /package/dist/{chunk-BJLCV2P3.js.map → chunk-WGRAB3KB.js.map} +0 -0
  345. /package/dist/{chunk-IVUVQPTQ.js.map → chunk-WIW4LOZL.js.map} +0 -0
  346. /package/dist/{chunk-5KDDAQ6K.js.map → chunk-WN2DASTS.js.map} +0 -0
  347. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  348. /package/dist/{chunk-5Y6VA6X2.js.map → chunk-XDUP6SWG.js.map} +0 -0
  349. /package/dist/{chunk-GHHQLSSY.js.map → chunk-XR4QR3JT.js.map} +0 -0
  350. /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
  351. /package/dist/{chunk-QGFR73SN.js.map → chunk-YIG4HPY4.js.map} +0 -0
  352. /package/dist/{chunk-UYUFDTIX.js.map → chunk-YIXWY4OZ.js.map} +0 -0
  353. /package/dist/{chunk-GJ4HU6LK.js.map → chunk-YLMTFRZR.js.map} +0 -0
  354. /package/dist/{chunk-GGD2YDS6.js.map → chunk-YRMSHA7H.js.map} +0 -0
  355. /package/dist/{chunk-KZZ5EZ7O.js.map → chunk-ZS5LREUZ.js.map} +0 -0
  356. /package/dist/{chunk-JLGBJ4AX.js.map → chunk-ZYEWTVNQ.js.map} +0 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Noncontent stage timing for slow/failed observe requests (25s incident).
3
+ * A failed request must record which stage blocked it (phase attribution) in
4
+ * the `access-observe-failed` log line without leaking message content,
5
+ * session keys, namespaces or principals, and must still throw unchanged.
6
+ */
7
+ import assert from "node:assert/strict";
8
+ import { mkdtemp, rm } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import path from "node:path";
11
+ import test from "node:test";
12
+
13
+ import { EngramAccessService, NamespaceNotWritableError } from "./access-service.js";
14
+ import { initLogger, resetLogger, type LoggerBackend } from "./logger.js";
15
+ import { Orchestrator } from "./orchestrator.js";
16
+ import { parseConfig } from "./config.js";
17
+ import type { EngramAccessObserveRequest } from "./access-service.js";
18
+ import type { CodingContext, PluginConfig } from "./types.js";
19
+
20
+ interface OrchestratorInternals {
21
+ config: PluginConfig;
22
+ _codingContextBySession: Map<string, CodingContext>;
23
+ }
24
+
25
+ function makeService(memoryDir: string): EngramAccessService {
26
+ // SAFETY: the probe drives only the observe scope path on this orchestrator.
27
+ const orch = Object.create(Orchestrator.prototype) as Orchestrator;
28
+ // SAFETY: the probe sets exactly these two internal fields.
29
+ const internals: OrchestratorInternals = {
30
+ config: parseConfig({
31
+ namespacesEnabled: true,
32
+ defaultNamespace: "default",
33
+ sharedNamespace: "shared",
34
+ namespacePolicies: [],
35
+ codingMode: { projectScope: true },
36
+ transcriptEnabled: false,
37
+ memoryDir,
38
+ }),
39
+ _codingContextBySession: new Map(),
40
+ };
41
+ Object.assign(orch, internals);
42
+ return new EngramAccessService(orch);
43
+ }
44
+
45
+ test("a rejected observe logs phase attribution with stage durations and still throws", async () => {
46
+ const dir = await mkdtemp(path.join(tmpdir(), "remnic-observe-timing-"));
47
+ const lines: string[] = [];
48
+ const backend: LoggerBackend = {
49
+ info(msg) {
50
+ lines.push(msg);
51
+ },
52
+ warn(msg) {
53
+ lines.push(msg);
54
+ },
55
+ error(msg) {
56
+ lines.push(msg);
57
+ },
58
+ };
59
+ initLogger(backend, false, { timestamps: false });
60
+ try {
61
+ const service = makeService(dir);
62
+ // SAFETY: the literal sets the request fields the scope path reads.
63
+ const rejectedRequest = {
64
+ sessionKey: "sess-timing-1",
65
+ authenticatedPrincipal: "alice",
66
+ namespace: "victim-secret",
67
+ messages: [
68
+ { role: "user", content: "first secret message body" },
69
+ { role: "assistant", content: "second secret message body" },
70
+ ],
71
+ } as EngramAccessObserveRequest;
72
+ await assert.rejects(
73
+ service.observe(rejectedRequest),
74
+ NamespaceNotWritableError
75
+ );
76
+
77
+ const failed = lines.filter((l) => l.includes("access-observe-failed"));
78
+ assert.equal(failed.length, 1, `expected exactly one access-observe-failed line, got: ${failed}`);
79
+ const line = failed[0] ?? "";
80
+ // Phase attribution: the blocked stage (scope) plus total and message count.
81
+ assert.match(line, /totalMs=\d+/);
82
+ assert.match(line, /messages=2/);
83
+ assert.match(line, /scopeMs=\d+/);
84
+ // Noncontent: no message bodies, session key, namespace or principal.
85
+ assert.ok(!line.includes("secret"), `leaked content in: ${line}`);
86
+ assert.ok(!line.includes("sess-timing-1"), `leaked session key in: ${line}`);
87
+ assert.ok(!line.includes("victim-secret"), `leaked namespace in: ${line}`);
88
+ assert.ok(!line.includes("alice"), `leaked principal in: ${line}`);
89
+ } finally {
90
+ resetLogger();
91
+ await rm(dir, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ // Plain-object orchestrator for full observe success/failure paths (mirrors
96
+ // the access-service-observe-lcm-parity harness).
97
+ function makeProbe(options: {
98
+ enqueueObserveMessages: (sessionKey: string, messages: unknown[]) => Promise<void> | void;
99
+ }) {
100
+ const config = parseConfig({
101
+ namespacesEnabled: true,
102
+ defaultNamespace: "default",
103
+ sharedNamespace: "shared",
104
+ namespacePolicies: [],
105
+ defaultRecallNamespaces: ["self", "shared"],
106
+ codingMode: { projectScope: true },
107
+ transcriptEnabled: false,
108
+ memoryDir: "/synthetic/remnic-observe-timing",
109
+ objectiveStateMemoryEnabled: false,
110
+ objectiveStateSnapshotWritesEnabled: false,
111
+ principalFromSessionKeyMode: "prefix",
112
+ principalFromSessionKeyRules: [],
113
+ recallCrossNamespaceBudgetEnabled: false,
114
+ recallCrossNamespaceBudgetWindowMs: 60_000,
115
+ recallCrossNamespaceBudgetSoftLimit: 10,
116
+ recallCrossNamespaceBudgetHardLimit: 30,
117
+ });
118
+ const contexts = new Map<string, CodingContext>();
119
+ const extractionCalls: string[][] = [];
120
+ // SAFETY: the probe exercises the observe surface; the rest of Orchestrator
121
+ // is unused on this path.
122
+ const orch = {
123
+ config,
124
+ getCodingContextForSession: (sk: string | undefined) => (sk ? contexts.get(sk) : null) ?? null,
125
+ setCodingContextForSession: (sk: string, ctx: CodingContext | null) => {
126
+ if (ctx === null) contexts.delete(sk);
127
+ else contexts.set(sk, ctx);
128
+ },
129
+ applyCodingNamespaceOverlay: (sk: string | undefined, base: string) =>
130
+ Orchestrator.prototype.applyCodingNamespaceOverlay.call(orch, sk, base),
131
+ resolvePrincipal: (sk?: string) => Orchestrator.prototype.resolvePrincipal.call(orch, sk),
132
+ resolveSelfNamespace: (sk?: string) => Orchestrator.prototype.resolveSelfNamespace.call(orch, sk),
133
+ lcmEngine: {
134
+ enabled: true,
135
+ enqueueObserveMessages: options.enqueueObserveMessages,
136
+ },
137
+ ingestReplayBatch: async (turns: Array<{ sessionKey: string }>) => {
138
+ extractionCalls.push(turns.map((t) => t.sessionKey));
139
+ },
140
+ } as any;
141
+ return { orch, extractionCalls };
142
+ }
143
+
144
+ test("a slow successful observe logs access-observe-slow with per-stage durations", async () => {
145
+ const lines: string[] = [];
146
+ const backend: LoggerBackend = {
147
+ info(msg) {
148
+ lines.push(msg);
149
+ },
150
+ warn(msg) {
151
+ lines.push(msg);
152
+ },
153
+ error(msg) {
154
+ lines.push(msg);
155
+ },
156
+ };
157
+ initLogger(backend, false, { timestamps: false });
158
+ // Deterministic monotonic clock: every read advances 6s, so each timed
159
+ // stage records exactly 6000ms and the total exceeds the slow threshold.
160
+ let now = 0;
161
+ const realPerformanceNow = performance.now;
162
+ performance.now = () => (now += 6000);
163
+ try {
164
+ const { orch } = makeProbe({
165
+ enqueueObserveMessages: (_sessionKey) => {},
166
+ });
167
+ const service = new EngramAccessService(orch);
168
+ // SAFETY: the literal sets the request fields the observe path reads.
169
+ const slowRequest = {
170
+ sessionKey: "alice:sess-slow",
171
+ authenticatedPrincipal: "alice",
172
+ messages: [
173
+ { role: "user", content: "slow path message body" },
174
+ { role: "assistant", content: "reply body" },
175
+ ],
176
+ } as EngramAccessObserveRequest;
177
+ const res = await service.observe(slowRequest);
178
+ assert.equal(res.accepted, 2);
179
+
180
+ const slow = lines.filter((l) => l.includes("access-observe-slow"));
181
+ assert.equal(slow.length, 1, `expected one access-observe-slow line, got: ${lines}`);
182
+ const line = slow[0] ?? "";
183
+ assert.match(line, /totalMs=(\d+)/);
184
+ const totalMs = Number(/totalMs=(\d+)/.exec(line)?.[1]);
185
+ assert.ok(totalMs >= 10_000, `slow line must exceed threshold, got totalMs=${totalMs}`);
186
+ assert.match(line, /messages=2/);
187
+ // Real stages are attributed, including the durable-buffer terminal stage.
188
+ assert.match(line, /scopeMs=6000/);
189
+ assert.match(line, /durable-bufferMs=6000/);
190
+ assert.ok(!line.includes("sess-slow") && !line.includes("alice") && !line.includes("body"));
191
+ } finally {
192
+ performance.now = realPerformanceNow;
193
+ resetLogger();
194
+ }
195
+ });
196
+
197
+ test("a failed awaited LCM archival records the lcm-accept phase and still throws", async () => {
198
+ const lines: string[] = [];
199
+ const backend: LoggerBackend = {
200
+ info(msg) {
201
+ lines.push(msg);
202
+ },
203
+ warn(msg) {
204
+ lines.push(msg);
205
+ },
206
+ error(msg) {
207
+ lines.push(msg);
208
+ },
209
+ };
210
+ initLogger(backend, false, { timestamps: false });
211
+ try {
212
+ const { orch } = makeProbe({
213
+ enqueueObserveMessages: async () => {
214
+ throw new Error("lcm archival append failed");
215
+ },
216
+ });
217
+ const service = new EngramAccessService(orch);
218
+ // SAFETY: the literal sets the request fields the observe path reads.
219
+ const failedLcmRequest = {
220
+ sessionKey: "alice:sess-lcmfail",
221
+ authenticatedPrincipal: "alice",
222
+ messages: [
223
+ { role: "user", content: "lcm failure message body" },
224
+ { role: "assistant", content: "reply body" },
225
+ ],
226
+ } as EngramAccessObserveRequest;
227
+ await assert.rejects(
228
+ service.observe(failedLcmRequest),
229
+ /lcm archival append failed/
230
+ );
231
+
232
+ const failed = lines.filter((l) => l.includes("access-observe-failed"));
233
+ assert.equal(failed.length, 1, `expected exactly one access-observe-failed line, got: ${failed}`);
234
+ const line = failed[0] ?? "";
235
+ assert.match(line, /totalMs=\d+/);
236
+ assert.match(line, /messages=2/);
237
+ // The rejected LCM accept is attributed with its duration, alongside earlier stages.
238
+ assert.match(line, /scopeMs=\d+/);
239
+ assert.match(line, /lcm-acceptMs=\d+/);
240
+ assert.ok(!line.includes("sess-lcmfail"), `leaked session key in: ${line}`);
241
+ assert.ok(!line.includes("alice"), `leaked principal in: ${line}`);
242
+ assert.ok(!line.includes("body"), `leaked content in: ${line}`);
243
+ } finally {
244
+ resetLogger();
245
+ }
246
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Noncontent stage timing for the observe surface.
3
+ *
4
+ * Durations only — never message content, session keys, namespaces or
5
+ * principals. A monotonic clock (`performance.now`) keeps phase durations
6
+ * sane under wall-clock corrections.
7
+ *
8
+ * A slow observe logs `access-observe-slow`; a failed observe always logs
9
+ * `access-observe-failed`, so a 25s-class stall attributes to a phase
10
+ * instead of a guess.
11
+ */
12
+ import { log } from "./logger.js";
13
+
14
+ /** Observes slower than this log `access-observe-slow` with per-stage durations. */
15
+ const OBSERVE_SLOW_LOG_MS = 10_000;
16
+
17
+ export interface ObserveStageTimer {
18
+ /** Run one stage, recording its duration even when the stage rejects. */
19
+ timed<T>(stage: string, fn: () => Promise<T>): Promise<T>;
20
+ /** Log a slow successful observe with per-stage durations. */
21
+ logSlow(messages: number): void;
22
+ /** Log a failed observe with per-stage durations. */
23
+ logFailed(messages: number): void;
24
+ }
25
+
26
+ /** Timer for one observe request. */
27
+ export function createObserveStageTimer(): ObserveStageTimer {
28
+ const startedAtMs = performance.now();
29
+ const stageMs: Record<string, number> = {};
30
+
31
+ const formatStages = (): string =>
32
+ Object.entries(stageMs)
33
+ .map(([stage, ms]) => `${stage}Ms=${Math.round(ms)}`)
34
+ .join(" ");
35
+
36
+ return {
37
+ async timed<T>(stage: string, fn: () => Promise<T>): Promise<T> {
38
+ const stageStartMs = performance.now();
39
+ try {
40
+ return await fn();
41
+ } finally {
42
+ stageMs[stage] = (stageMs[stage] ?? 0) + (performance.now() - stageStartMs);
43
+ }
44
+ },
45
+ logSlow(messages: number): void {
46
+ const totalMs = Math.round(performance.now() - startedAtMs);
47
+ if (totalMs < OBSERVE_SLOW_LOG_MS) return;
48
+ log.warn(
49
+ `access-observe-slow totalMs=${totalMs} messages=${messages} ${formatStages()}`,
50
+ );
51
+ },
52
+ logFailed(messages: number): void {
53
+ log.warn(
54
+ `access-observe-failed totalMs=${Math.round(performance.now() - startedAtMs)} messages=${messages} ${formatStages()}`,
55
+ );
56
+ },
57
+ };
58
+ }
@@ -34,6 +34,9 @@ import { abortError } from "./abort-error.js";
34
34
  import { withBriefingDeadline } from "./briefing-deadline.js";
35
35
  import { extractionForceFlush } from "./access-extraction-force-flush.js";
36
36
  import { ObserveTranscriptPersister, observeTranscriptSessionKey } from "./access-observe-transcript.js";
37
+ import { createObserveStageTimer } from "./access-observe-stage-timing.js";
38
+ import { archiveObservedTurns } from "./access-observe-lcm-archive.js";
39
+ import { recordObserveObjectiveState } from "./access-observe-objective-state.js";
37
40
  import { FileCalendarSource, buildBriefing, parseBriefingFocus, parseBriefingWindow } from "./briefing.js";
38
41
  import {
39
42
  resolveCompressionCapabilities,
@@ -708,10 +711,16 @@ export class AccessObserveWriteSurface {
708
711
  request.sessionKey,
709
712
  pendingObserveScopeHint(request)
710
713
  );
714
+ // Noncontent stage timing for slow/failed observes: durations only, logged
715
+ // in the `access-observe-failed` line (always) and `access-observe-slow`
716
+ // (successes at or above the slow threshold). Never message content,
717
+ // session keys, namespaces or principals.
718
+ const stageTimer = createObserveStageTimer();
719
+ const timed = stageTimer.timed.bind(stageTimer);
711
720
  try {
712
721
  let scope: MemoryScopePlan;
713
722
  try {
714
- scope = await this.deps.resolveMemoryScopePlan(request);
723
+ scope = await timed("scope", () => this.deps.resolveMemoryScopePlan(request));
715
724
  } catch (err) {
716
725
  // A replay re-submit sets suppressQuarantine so a still-unwritable target
717
726
  // propagates instead of re-parking (#1888); observe has no dryRun path.
@@ -749,10 +758,12 @@ export class AccessObserveWriteSurface {
749
758
  // on the same session is project-scoped (rule 42: same namespace layer as
750
759
  // recall). Done AFTER the scope plan authorized the write, so a rejected
751
760
  // request never leaves orphaned context on the session.
752
- await this.deps.maybeAttachCodingContext(request.sessionKey, {
753
- cwd: request.cwd,
754
- projectTag: request.projectTag,
755
- });
761
+ await timed("context", () =>
762
+ this.deps.maybeAttachCodingContext(request.sessionKey, {
763
+ cwd: request.cwd,
764
+ projectTag: request.projectTag,
765
+ })
766
+ );
756
767
 
757
768
  // Prefix sessionKey with the EFFECTIVE write namespace for LCM archival so
758
769
  // observed turns are scoped to the same namespace project-scoped recall
@@ -771,51 +782,35 @@ export class AccessObserveWriteSurface {
771
782
  // (#928 contract, already auth-checked inside the scope plan), not the
772
783
  // general default-store write namespace.
773
784
  if (shouldWriteObjectiveState) {
774
- try {
775
- const objectiveStateLocation = await this.deps.objectiveStateStoreLocationForNamespace(
776
- scope.objectiveStateNamespace
777
- );
778
- await recordObjectiveStateSnapshotsFromObservedMessages({
779
- memoryDir: objectiveStateLocation.memoryDir,
780
- objectiveStateStoreDir: objectiveStateLocation.objectiveStateStoreDir,
781
- objectiveStateMemoryEnabled: resolveObjectiveStateCapabilities(this.deps.orchestrator.config)
782
- .objectiveStateMemory,
783
- objectiveStateSnapshotWritesEnabled: resolveObjectiveStateCapabilities(this.deps.orchestrator.config)
784
- .objectiveStateSnapshotWrites,
785
- sessionKey: request.sessionKey,
786
- recordedAt: new Date().toISOString(),
787
- messages: request.messages,
788
- });
789
- } catch (err) {
790
- log.error(`access-observe objective-state snapshot write failed: ${err}`);
791
- }
785
+ await timed("objective", () =>
786
+ recordObserveObjectiveState(
787
+ this.deps.orchestrator,
788
+ this.deps.objectiveStateStoreLocationForNamespace,
789
+ scope,
790
+ request,
791
+ ),
792
+ );
792
793
  }
793
794
 
794
795
  // 5. LCM archival → effective write namespace.
795
- // lcmArchived in the response means "LCM archival was queued" (not
796
- // "completed"), matching extractionQueued semantics. Both run async.
797
- let lcmArchived = false;
798
- if (this.deps.orchestrator.lcmEngine && this.deps.orchestrator.lcmEngine.enabled) {
799
- // Fire-and-forget: LCM archival writes to SQLite and builds summary
800
- // DAGs, which can take tens of seconds for large sessions. Don't
801
- // block the HTTP response — the caller only needs acknowledgment.
802
- try {
803
- this.deps.orchestrator.lcmEngine.enqueueObserveMessages(lcmSessionKey, request.messages);
804
- lcmArchived = true;
805
- } catch (err) {
806
- log.error(`access-observe LCM enqueue failed: ${err}`);
807
- }
808
- }
796
+ // The archival append is durable before this returns: the queue holds
797
+ // only a session reference, and summary DAGs are built in the
798
+ // background, so the caller is never blocked on inference.
799
+ const lcmArchived = await timed("lcm-accept", () =>
800
+ archiveObservedTurns(this.deps.orchestrator.lcmEngine, lcmSessionKey, request.messages),
801
+ );
809
802
 
810
803
  // 5b. Transcript persistence → the per-session transcript store the
811
- // hourly summarizer reads (issue #2783). Best-effort like the LCM
812
- // enqueue above: a transcript failure must not fail the observe.
804
+ // hourly summarizer reads (issue #2783). Best-effort: a transcript
805
+ // failure must not fail the observe.
813
806
  const transcriptPersisted = resolvePresentationCapabilities(this.deps.orchestrator.config).transcript
814
- ? await this.observeTranscriptPersister.persist(
815
- this.deps.orchestrator,
816
- observeTranscriptSessionKey(request.sessionKey, writeNamespace, this.deps.orchestrator.config),
817
- request.messages,
818
- request.sessionKey
807
+ ? await timed("transcript", () =>
808
+ this.observeTranscriptPersister.persist(
809
+ this.deps.orchestrator,
810
+ observeTranscriptSessionKey(request.sessionKey, writeNamespace, this.deps.orchestrator.config),
811
+ request.messages,
812
+ request.sessionKey
813
+ )
819
814
  )
820
815
  : false;
821
816
 
@@ -895,10 +890,11 @@ export class AccessObserveWriteSurface {
895
890
  extractionQueued = true;
896
891
  };
897
892
  }
898
- await this.deps.orchestrator.ingestReplayBatch(turns, ingestOptions);
893
+ await timed("durable-buffer", () => this.deps.orchestrator.ingestReplayBatch(turns, ingestOptions));
899
894
  // A native trigger may leave the durable capture pending rather than queued.
900
895
  }
901
896
 
897
+ stageTimer.logSlow(request.messages.length);
902
898
  log.info(
903
899
  `access-observe namespace=${namespace} effectiveNamespace=${writeNamespace} sessionKey=${request.sessionKey} messages=${request.messages.length} lcm=${lcmArchived} extraction=${extractionQueued} transcript=${transcriptPersisted}`
904
900
  );
@@ -924,6 +920,10 @@ export class AccessObserveWriteSurface {
924
920
  extractionQueued,
925
921
  transcriptPersisted,
926
922
  };
923
+ } catch (err) {
924
+ // Failed requests always get phase attribution (noncontent), regardless of duration.
925
+ stageTimer.logFailed(request.messages.length);
926
+ throw err;
927
927
  } finally {
928
928
  observePreparation.release();
929
929
  }
@@ -1079,6 +1079,7 @@ export interface EngramAccessObserveResponse {
1079
1079
  effectiveNamespace: string;
1080
1080
  /** Additive diagnostic view of the resolved scope plan (#1495). */
1081
1081
  scopeDebug?: EngramAccessScopeDebug;
1082
+ /** True when the observed turns are durably archived in LCM — the archive append completed before the response was produced. */
1082
1083
  lcmArchived: boolean;
1083
1084
  extractionQueued: boolean;
1084
1085
  /** True when at least one observe-derived turn was appended to the transcript store (issue #2783). False when the transcript capability is off, every message was filtered/deduped, or every append failed. */
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Codex P2 PRRT_kwDORJXyws6zzdG4 (#3311) — promotion-target writes must share
3
+ * the fact-dedup commit window.
4
+ *
5
+ * With extractionQueueConcurrency > 1 and shared auto-promotion enabled, two
6
+ * sessions extracting the same fact into DIFFERENT source namespaces can both
7
+ * pass the shared-namespace dedup gate (`hasFactContentHash`) before either
8
+ * promotion writes: the persist-time gate runs outside the commit window that
9
+ * protects the source writes, and the promotion destination write ran without
10
+ * any window at all. writeMemory mints a fresh id per write, so both
11
+ * promotions land distinct active shared copies — exactly the duplicate class
12
+ * `persistUnderFactDedupWindow` exists to prevent for source writes.
13
+ *
14
+ * The fix wraps each promotion destination write in
15
+ * `persistUnderFactDedupWindow` keyed on the DESTINATION storage dir, so the
16
+ * second promotion's in-window recheck observes the first promotion's
17
+ * committed hash and suppresses instead of duplicating. The invariant these
18
+ * tests assert — exactly one active shared copy survives a concurrent
19
+ * same-fact promotion pair — holds for every interleaving once the window is
20
+ * in place, because recheck+write are atomic per destination dir; before the
21
+ * fix the duplicate depends on the interleaving (two symmetric pipelines in
22
+ * lockstep both gate-miss before either write commits).
23
+ *
24
+ * These tests drive the real persistExtraction path (no LLM network; every
25
+ * gate that would call one is off) with two real source namespaces and the
26
+ * real shared destination, and assert the durable outcome.
27
+ */
28
+
29
+ import assert from "node:assert/strict";
30
+ import os from "node:os";
31
+ import path from "node:path";
32
+ import { mkdtemp, rm } from "node:fs/promises";
33
+ import test from "node:test";
34
+
35
+ import { parseConfig } from "./config.js";
36
+ import { Orchestrator } from "./orchestrator.js";
37
+ import type { ExtractionResult, PluginConfig } from "./types.js";
38
+ import type { LocalLlmClient } from "./local-llm.js";
39
+ import type { PersistExtractionFn } from "./testing/orchestrator-lite.js";
40
+ import type { StorageManager } from "./storage.js";
41
+
42
+ // Fixture contract for the private Orchestrator surface this suite drives.
43
+ interface OrchestratorFixture {
44
+ localLlm?: unknown;
45
+ chatCompletion?: unknown;
46
+ getStorage?: unknown;
47
+ persistExtraction?: unknown;
48
+ }
49
+
50
+ // Named owner contract for the private internals this suite drives.
51
+ interface OrchestratorInternals {
52
+ localLlm: LocalLlmClient;
53
+ getStorage: (ns: string) => Promise<StorageManager>;
54
+ persistExtraction: PersistExtractionFn;
55
+ }
56
+
57
+ function asInternals<T extends object>(value: OrchestratorFixture): T {
58
+ // SAFETY: the fixture provides the members the persist path reads; the
59
+ // remaining contract members are unused by these tests.
60
+ return value as T;
61
+ }
62
+
63
+ async function makeHarness(): Promise<{
64
+ orchestrator: Orchestrator;
65
+ persist: OrchestratorInternals;
66
+ memoryDir: string;
67
+ shared: StorageManager;
68
+ teamA: StorageManager;
69
+ teamB: StorageManager;
70
+ }> {
71
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-promotion-dedup-"));
72
+ const config: PluginConfig = parseConfig({
73
+ openaiApiKey: "sk-test",
74
+ memoryDir,
75
+ workspaceDir: path.join(memoryDir, "workspace"),
76
+ // Minimize every gate that needs a model or corpus-scale machinery so the
77
+ // fact reaches the normal (non-chunked) write path.
78
+ extractionJudgeEnabled: false,
79
+ temporalSupersessionEnabled: false,
80
+ contradictionDetectionEnabled: false,
81
+ chunkingEnabled: false,
82
+ embeddingFallbackEnabled: false,
83
+ qmdEnabled: false,
84
+ extractionMinChars: 0,
85
+ extractionMinImportanceLevel: "trivial",
86
+ inlineSourceAttributionEnabled: false,
87
+ // Shared auto-promotion from every source namespace into the default
88
+ // "shared" namespace (both default off — the deployment profile this
89
+ // finding targets).
90
+ namespacesEnabled: true,
91
+ autoPromoteToSharedEnabled: true,
92
+ });
93
+ const orchestrator = new Orchestrator(config);
94
+ const internals = asInternals<OrchestratorInternals>(orchestrator);
95
+ internals.localLlm = asInternals<LocalLlmClient>({ chatCompletion: async () => null });
96
+ const shared = await internals.getStorage("shared");
97
+ const teamA = await internals.getStorage("team-a");
98
+ const teamB = await internals.getStorage("team-b");
99
+ await Promise.all([shared, teamA, teamB].map((s) => s.ensureDirectories()));
100
+ return { orchestrator, persist: internals, memoryDir, shared, teamA, teamB };
101
+ }
102
+
103
+ function factResult(content: string): ExtractionResult {
104
+ const result = {
105
+ facts: [
106
+ {
107
+ content,
108
+ category: "fact",
109
+ // confidenceTier(0.96) === "explicit", which the default
110
+ // autoPromoteMinConfidenceTier ("explicit") admits.
111
+ confidence: 0.96,
112
+ tags: [],
113
+ },
114
+ ],
115
+ entities: [],
116
+ relationships: [],
117
+ questions: [],
118
+ profileUpdates: [],
119
+ };
120
+ // SAFETY: literal carries the full extraction-result shape persistExtraction
121
+ // consumes for a single fact candidate.
122
+ return result as ExtractionResult;
123
+ }
124
+
125
+ async function activeFactCopies(storage: StorageManager): Promise<number> {
126
+ const memories = await storage.readAllMemories();
127
+ return memories.filter(
128
+ (m) => m.frontmatter.category === "fact" && (m.frontmatter.status ?? "active") === "active",
129
+ ).length;
130
+ }
131
+
132
+ const FACT = "Deployment rollbacks complete within five minutes.";
133
+
134
+ test("the same fact promoted from two namespaces lands once in shared (serial baseline)", async () => {
135
+ const { persist, memoryDir, shared, teamA, teamB } = await makeHarness();
136
+ try {
137
+ const first = await persist.persistExtraction(
138
+ factResult(FACT),
139
+ teamA,
140
+ null,
141
+ { sessionKey: "agent:test:a", principal: "test" },
142
+ undefined,
143
+ null,
144
+ "User mentioned that deployment rollbacks complete within five minutes.",
145
+ );
146
+ const second = await persist.persistExtraction(
147
+ factResult(FACT),
148
+ teamB,
149
+ null,
150
+ { sessionKey: "agent:test:b", principal: "test" },
151
+ undefined,
152
+ null,
153
+ "User mentioned that deployment rollbacks complete within five minutes.",
154
+ );
155
+
156
+ // Each source namespace keeps its own copy (cross-namespace source writes
157
+ // never dedup), and the second promotion dedups through the shared gate.
158
+ assert.equal(first.persistedIds.length, 1, "first source write persists");
159
+ assert.equal(second.persistedIds.length, 1, "second source write persists in its own namespace");
160
+ assert.equal(await activeFactCopies(teamA), 1);
161
+ assert.equal(await activeFactCopies(teamB), 1);
162
+ assert.equal(await activeFactCopies(shared), 1, "shared namespace holds exactly one promoted copy");
163
+ } finally {
164
+ await rm(memoryDir, { recursive: true, force: true });
165
+ }
166
+ });
167
+
168
+ test("concurrent same-fact promotions from different namespaces leave exactly one active shared copy", async () => {
169
+ const { persist, memoryDir, shared, teamA, teamB } = await makeHarness();
170
+ try {
171
+ const sourceText = "User mentioned that deployment rollbacks complete within five minutes.";
172
+ // Two real sessions, different source namespaces, same fact, one shared
173
+ // destination. Before the fix this interleaving (both pipelines gate-miss
174
+ // in lockstep before either promotion writes) landed two distinct active
175
+ // shared copies; after the fix the destination window serializes the
176
+ // promotions and the invariant holds for every interleaving.
177
+ const [a, b] = await Promise.all([
178
+ persist.persistExtraction(
179
+ factResult(FACT),
180
+ teamA,
181
+ null,
182
+ { sessionKey: "agent:test:a", principal: "test" },
183
+ undefined,
184
+ null,
185
+ sourceText,
186
+ ),
187
+ persist.persistExtraction(
188
+ factResult(FACT),
189
+ teamB,
190
+ null,
191
+ { sessionKey: "agent:test:b", principal: "test" },
192
+ undefined,
193
+ null,
194
+ sourceText,
195
+ ),
196
+ ]);
197
+
198
+ // Each source namespace still persists its own copy — the window only
199
+ // serializes the shared DESTINATION.
200
+ assert.equal(await activeFactCopies(teamA), 1, "team-a keeps its source copy");
201
+ assert.equal(await activeFactCopies(teamB), 1, "team-b keeps its source copy");
202
+ assert.equal(a.persistedIds.length, 1);
203
+ assert.equal(b.persistedIds.length, 1);
204
+ assert.equal(
205
+ await activeFactCopies(shared),
206
+ 1,
207
+ "exactly one active shared copy survives the concurrent promotion pair",
208
+ );
209
+ } finally {
210
+ await rm(memoryDir, { recursive: true, force: true });
211
+ }
212
+ });