@remnic/core 9.3.684 → 9.3.686

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 (315) hide show
  1. package/dist/access-boundary.d.ts +4 -3
  2. package/dist/access-boundary.js +23 -23
  3. package/dist/access-cli.js +152 -71
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +3 -2
  6. package/dist/access-http.js +26 -26
  7. package/dist/access-mcp.d.ts +14 -3
  8. package/dist/access-mcp.js +25 -25
  9. package/dist/access-operations.d.ts +10 -4
  10. package/dist/access-operations.js +26 -24
  11. package/dist/access-schema.d.ts +4 -4
  12. package/dist/{access-service-D-siI-xJ.d.ts → access-service-DmCHJ4cH.d.ts} +106 -39
  13. package/dist/access-service.d.ts +3 -2
  14. package/dist/access-service.js +22 -22
  15. package/dist/access-surface-catalog.d.ts +3 -2
  16. package/dist/access-surface-catalog.js +2 -0
  17. package/dist/access-surface-catalog.js.map +1 -1
  18. package/dist/active-recall.js +2 -2
  19. package/dist/{auto-sync-5CJBJMPZ.js → auto-sync-NUQWSFQD.js} +7 -7
  20. package/dist/bootstrap.d.ts +2 -1
  21. package/dist/bootstrap.js +2 -2
  22. package/dist/boxes.js +2 -2
  23. package/dist/briefing.js +3 -3
  24. package/dist/buffer.js +2 -2
  25. package/dist/calibration.js +4 -4
  26. package/dist/catalog-COqWZlZ6.d.ts +456 -0
  27. package/dist/causal-behavior.js +4 -4
  28. package/dist/causal-chain.js +4 -4
  29. package/dist/causal-consolidation.js +11 -11
  30. package/dist/causal-retrieval.js +4 -4
  31. package/dist/causal-trajectory-graph.js +1 -1
  32. package/dist/causal-trajectory.js +1 -1
  33. package/dist/{chunk-ROHLEUTH.js → chunk-2KAYTPPT.js} +10 -10
  34. package/dist/{chunk-NHQGDVJF.js → chunk-2SJCWLQD.js} +3 -3
  35. package/dist/{chunk-YTWNKQ2G.js → chunk-3FC6LW6T.js} +2 -2
  36. package/dist/{chunk-5OE4PYY5.js → chunk-473JIN2U.js} +61 -11
  37. package/dist/chunk-473JIN2U.js.map +1 -0
  38. package/dist/{chunk-XEA4Z7JU.js → chunk-4FE2K57M.js} +3 -3
  39. package/dist/{chunk-WI7JKV2T.js → chunk-4N3TFFPH.js} +2 -2
  40. package/dist/{chunk-BTVX7ZXZ.js → chunk-4NFVPDIL.js} +4 -4
  41. package/dist/{chunk-OUWAQVDJ.js → chunk-5CEJH5ZN.js} +2 -2
  42. package/dist/{chunk-6QM24CP7.js → chunk-6GJS4BFH.js} +2 -2
  43. package/dist/{chunk-J2FBJ63F.js → chunk-6O6A6YUO.js} +4 -4
  44. package/dist/{chunk-HQ6NIBL6.js → chunk-7FL4CNPV.js} +2 -2
  45. package/dist/{chunk-5VDJMYTF.js → chunk-7WWURLG6.js} +3 -3
  46. package/dist/{chunk-QWRC7GIO.js → chunk-A4HH2EWA.js} +5 -5
  47. package/dist/{chunk-4SKKVWLQ.js → chunk-AGJH5ISO.js} +2 -2
  48. package/dist/{chunk-2L3KLWOV.js → chunk-B43NZNMG.js} +54 -92
  49. package/dist/chunk-B43NZNMG.js.map +1 -0
  50. package/dist/{chunk-WRE3JPAW.js → chunk-B4XVLHJA.js} +3 -3
  51. package/dist/{chunk-53FDU4CE.js → chunk-BLIWOONZ.js} +39 -36
  52. package/dist/chunk-BLIWOONZ.js.map +1 -0
  53. package/dist/{chunk-AJU4PJGY.js → chunk-BVKCV2ZY.js} +2 -2
  54. package/dist/{chunk-DQY7NJ5L.js → chunk-CTOQEZSN.js} +2 -2
  55. package/dist/{chunk-7OGJQP7T.js → chunk-DCWIQFNA.js} +4 -4
  56. package/dist/{chunk-MHQC2WU2.js → chunk-DKTSR7EK.js} +2 -2
  57. package/dist/{chunk-RN7MUWON.js → chunk-EHISUJFN.js} +2 -2
  58. package/dist/{chunk-WLEB7WCG.js → chunk-EO5QWINU.js} +2 -2
  59. package/dist/{chunk-XKXKSQU7.js → chunk-EXM3CQTZ.js} +2 -2
  60. package/dist/{chunk-M3FWYURP.js → chunk-FE6DQUNJ.js} +9 -9
  61. package/dist/{chunk-LCC5EZTT.js → chunk-FIVDN2SM.js} +4 -4
  62. package/dist/{chunk-3MY4W5V4.js → chunk-FUCUR2OZ.js} +550 -63
  63. package/dist/chunk-FUCUR2OZ.js.map +1 -0
  64. package/dist/{chunk-452WDNFO.js → chunk-GG6AJN7A.js} +2 -2
  65. package/dist/{chunk-2IBGHRIO.js → chunk-GS55WYRL.js} +3 -3
  66. package/dist/{chunk-IBTZEBUD.js → chunk-HYNHLBKA.js} +2 -2
  67. package/dist/{chunk-EVWIEEKZ.js → chunk-IQ7WCZRW.js} +2 -2
  68. package/dist/{chunk-B5XMS73R.js → chunk-IQVQJJL7.js} +2 -2
  69. package/dist/{chunk-6RHNCKHG.js → chunk-K43PI6DQ.js} +2 -2
  70. package/dist/{chunk-OIF36KGD.js → chunk-KCQA46NR.js} +2 -2
  71. package/dist/{chunk-2LDBXPLB.js → chunk-KF74X62T.js} +1 -1
  72. package/dist/{chunk-3EVIMVQU.js → chunk-KFBOZYME.js} +42 -3
  73. package/dist/chunk-KFBOZYME.js.map +1 -0
  74. package/dist/{chunk-MAV46GWQ.js → chunk-KYYL4U6X.js} +2 -2
  75. package/dist/{chunk-6GC5SGFE.js → chunk-L24JROPR.js} +2 -2
  76. package/dist/{chunk-Q5ZU3RNY.js → chunk-LQ6JI4VH.js} +2 -2
  77. package/dist/{chunk-GWKCEM3S.js → chunk-MCQDSY4G.js} +3 -3
  78. package/dist/{chunk-HP5FMB6L.js → chunk-MDJURR27.js} +2 -2
  79. package/dist/{chunk-FYEVFGJD.js → chunk-NN7QYW5W.js} +2 -2
  80. package/dist/chunk-NN7QYW5W.js.map +1 -0
  81. package/dist/{chunk-2ODBA7MQ.js → chunk-NU3CSQ4H.js} +5 -5
  82. package/dist/chunk-NU3CSQ4H.js.map +1 -0
  83. package/dist/{chunk-T2PO5MUF.js → chunk-O7GOFAM3.js} +2 -2
  84. package/dist/{chunk-Z2OXSMZK.js → chunk-OBXTMFZQ.js} +3 -3
  85. package/dist/{chunk-K6ZN34WC.js → chunk-OV4D5T7V.js} +3 -3
  86. package/dist/{chunk-OMLIFZ4I.js → chunk-PH3HOKYW.js} +2 -2
  87. package/dist/{chunk-C3IW2F5Z.js → chunk-PLBIPT6I.js} +2 -2
  88. package/dist/{chunk-QY7YA7OL.js → chunk-PNLCEFE4.js} +2 -2
  89. package/dist/{chunk-AGRPGAKR.js → chunk-PONNZ54D.js} +2 -2
  90. package/dist/{chunk-XZ4WBBB5.js → chunk-PWFWCGOO.js} +2 -2
  91. package/dist/{chunk-XWEXT4XU.js → chunk-QANVLERJ.js} +4 -4
  92. package/dist/{chunk-W4RVMTHR.js → chunk-QRDOSYOR.js} +2 -2
  93. package/dist/{chunk-OXNOINIP.js → chunk-QVMXQGT7.js} +24 -24
  94. package/dist/chunk-QVMXQGT7.js.map +1 -0
  95. package/dist/{chunk-6IMKOIZ6.js → chunk-R6OVFAX6.js} +2 -2
  96. package/dist/{chunk-5N5DXYDW.js → chunk-S2OU5DZY.js} +31 -10
  97. package/dist/chunk-S2OU5DZY.js.map +1 -0
  98. package/dist/{chunk-JOASJWQR.js → chunk-SANZHXY2.js} +2 -2
  99. package/dist/{chunk-7DTASS5T.js → chunk-SJHM6I4J.js} +2 -2
  100. package/dist/{chunk-M6BVYHBU.js → chunk-STOEE37X.js} +4 -4
  101. package/dist/{chunk-GKKAXVAJ.js → chunk-U33LWTQQ.js} +1 -7
  102. package/dist/chunk-U33LWTQQ.js.map +1 -0
  103. package/dist/{chunk-LXH3DIF2.js → chunk-U7D7NP4J.js} +2 -2
  104. package/dist/{chunk-DRD2Q7HQ.js → chunk-UFS7OXGL.js} +2 -2
  105. package/dist/{chunk-H3HDXD3U.js → chunk-UPTZYUYJ.js} +2 -2
  106. package/dist/{chunk-3Z7NPD5T.js → chunk-UTYBJR7M.js} +2 -2
  107. package/dist/{chunk-LN4YGHTM.js → chunk-UUH4YQOF.js} +2 -2
  108. package/dist/{chunk-6VF75M3X.js → chunk-VGUOEDTU.js} +2 -2
  109. package/dist/{chunk-44VFF3BB.js → chunk-VILEUJXC.js} +2 -2
  110. package/dist/{chunk-7SI52C65.js → chunk-VL7DP3OW.js} +2 -2
  111. package/dist/{chunk-7DHTMOND.js → chunk-VQ34TERH.js} +2 -2
  112. package/dist/{chunk-6VMIHVGO.js → chunk-VX6OBUDW.js} +2 -2
  113. package/dist/{chunk-EW5KFXHL.js → chunk-WDXCNJSF.js} +7 -7
  114. package/dist/{chunk-FMEKEF47.js → chunk-WIHPNY65.js} +79 -3
  115. package/dist/chunk-WIHPNY65.js.map +1 -0
  116. package/dist/{chunk-X6IRLNOO.js → chunk-WIWPSQYU.js} +2 -2
  117. package/dist/{chunk-DOCTITOP.js → chunk-WRFKZEO6.js} +2 -2
  118. package/dist/{chunk-E6ZDCOHM.js → chunk-XBZQRZ6G.js} +2 -2
  119. package/dist/{chunk-7YX23JBA.js → chunk-XHYGJVXL.js} +2 -2
  120. package/dist/{chunk-JD4SCARD.js → chunk-YN4ZT4CW.js} +1 -1
  121. package/dist/{chunk-YXWAILM4.js → chunk-YOI3ELXF.js} +2 -2
  122. package/dist/{chunk-XCAZF7KQ.js → chunk-ZA2S2VLL.js} +2 -2
  123. package/dist/{chunk-BEUDU7Y4.js → chunk-ZCWIH4LH.js} +2 -2
  124. package/dist/{chunk-V25ZAOSB.js → chunk-ZPTISBQU.js} +5 -5
  125. package/dist/{cli-ooj6JQBS.d.ts → cli-D8nZ2MPH.d.ts} +2 -2
  126. package/dist/cli.d.ts +4 -3
  127. package/dist/cli.js +44 -44
  128. package/dist/compounding/engine.js +4 -4
  129. package/dist/compounding/preference-consolidator.js +1 -1
  130. package/dist/config.js +2 -2
  131. package/dist/connectors/codex-materialize-runner.js +4 -4
  132. package/dist/connectors/codex-materialize.js +2 -2
  133. package/dist/connectors/index.js +5 -5
  134. package/dist/contradiction/index.js +3 -3
  135. package/dist/{contradiction-scan-AZTGFMPY.js → contradiction-scan-HWGEOUDS.js} +3 -3
  136. package/dist/conversation-index/backend.js +5 -5
  137. package/dist/conversation-index/cleanup.js +2 -2
  138. package/dist/conversation-index/faiss-adapter.js +2 -2
  139. package/dist/conversation-index/indexer.js +2 -2
  140. package/dist/conversation-index/search.js +2 -2
  141. package/dist/day-summary.js +2 -2
  142. package/dist/embedding-fallback.js +2 -2
  143. package/dist/entity-retrieval.js +4 -4
  144. package/dist/explicit-capture.d.ts +2 -1
  145. package/dist/explicit-capture.js +1 -1
  146. package/dist/extraction-judge-telemetry.js +2 -2
  147. package/dist/extraction-judge-training.js +2 -2
  148. package/dist/extraction-judge.js +5 -5
  149. package/dist/extraction.js +9 -9
  150. package/dist/fallback-llm.js +4 -4
  151. package/dist/{graph-edge-decay-KSVJGCZW.js → graph-edge-decay-D7OESCBR.js} +2 -2
  152. package/dist/graph-snapshot.js +2 -2
  153. package/dist/graph.js +1 -1
  154. package/dist/index.d.ts +5 -4
  155. package/dist/index.js +80 -80
  156. package/dist/lcm/archive.js +2 -2
  157. package/dist/lcm/engine.js +5 -5
  158. package/dist/lcm/index.js +5 -5
  159. package/dist/lcm/schema.js +2 -2
  160. package/dist/lcm/summarizer.js +3 -3
  161. package/dist/local-llm.js +2 -2
  162. package/dist/logger.js +1 -1
  163. package/dist/maintenance/memory-governance.js +3 -3
  164. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  165. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  166. package/dist/mcp-memory-inspector-app.d.ts +3 -2
  167. package/dist/model-registry.js +2 -2
  168. package/dist/models-json.js +2 -2
  169. package/dist/namespaces/migrate.d.ts +1 -0
  170. package/dist/namespaces/migrate.js +16 -16
  171. package/dist/namespaces/search.js +13 -13
  172. package/dist/namespaces/storage.d.ts +42 -1
  173. package/dist/namespaces/storage.js +3 -3
  174. package/dist/native-knowledge.js +2 -2
  175. package/dist/negative.js +2 -2
  176. package/dist/operator-toolkit.js +22 -22
  177. package/dist/{orchestrator-DIDDvwDw.d.ts → orchestrator-CA6ouzBn.d.ts} +3 -464
  178. package/dist/orchestrator.d.ts +2 -1
  179. package/dist/orchestrator.js +61 -61
  180. package/dist/profiling.js +2 -2
  181. package/dist/qmd.js +2 -2
  182. package/dist/recall-planner-llm.js +4 -4
  183. package/dist/recall-qos.js +2 -2
  184. package/dist/recall-state.js +2 -2
  185. package/dist/relevance.js +2 -2
  186. package/dist/{resolution-IDTEBJFS.js → resolution-MN36NW5P.js} +3 -3
  187. package/dist/resolve-provider-secret.js +2 -2
  188. package/dist/resume-bundles.js +4 -4
  189. package/dist/retrieval-agents.js +2 -2
  190. package/dist/routing/store.js +2 -2
  191. package/dist/schemas.d.ts +38 -38
  192. package/dist/search/embed-helper.js +2 -2
  193. package/dist/search/factory.js +12 -12
  194. package/dist/search/index.js +12 -12
  195. package/dist/search/lancedb-backend.js +2 -2
  196. package/dist/search/meilisearch-backend.js +2 -2
  197. package/dist/search/orama-backend.js +2 -2
  198. package/dist/search/remote-backend.js +2 -2
  199. package/dist/semantic-consolidation.js +5 -5
  200. package/dist/semantic-rule-promotion.js +3 -3
  201. package/dist/semantic-rule-verifier.js +3 -3
  202. package/dist/session-observer-state.js +2 -2
  203. package/dist/session-transcript-migration.js +2 -2
  204. package/dist/shared-context/manager.js +2 -2
  205. package/dist/storage.d.ts +4 -0
  206. package/dist/storage.js +2 -2
  207. package/dist/summarizer.js +7 -7
  208. package/dist/temporal-supersession.js +2 -2
  209. package/dist/threading.js +2 -2
  210. package/dist/transcript.js +2 -2
  211. package/dist/transfer/types.d.ts +22 -22
  212. package/dist/verified-recall.js +4 -4
  213. package/package.json +2 -2
  214. package/src/access-boundary.ts +2 -1
  215. package/src/access-cli.ts +94 -4
  216. package/src/access-http.ts +40 -2
  217. package/src/access-mcp.ts +55 -2
  218. package/src/access-operations.ts +66 -0
  219. package/src/access-service.ts +148 -73
  220. package/src/access-surface-catalog.test.ts +1 -1
  221. package/src/access-surface-catalog.ts +2 -0
  222. package/src/cli.ts +2 -1
  223. package/src/coding/decision-surfaces.test.ts +279 -0
  224. package/src/coding/decision-surfaces.ts +475 -0
  225. package/src/explicit-capture.ts +3 -12
  226. package/src/logger.ts +13 -4
  227. package/src/namespaces/catalog.test.ts +2 -2
  228. package/src/namespaces/storage.ts +81 -0
  229. package/src/orchestrator.ts +22 -64
  230. package/src/storage.ts +36 -36
  231. package/dist/chunk-2L3KLWOV.js.map +0 -1
  232. package/dist/chunk-2ODBA7MQ.js.map +0 -1
  233. package/dist/chunk-3EVIMVQU.js.map +0 -1
  234. package/dist/chunk-3MY4W5V4.js.map +0 -1
  235. package/dist/chunk-53FDU4CE.js.map +0 -1
  236. package/dist/chunk-5N5DXYDW.js.map +0 -1
  237. package/dist/chunk-5OE4PYY5.js.map +0 -1
  238. package/dist/chunk-FMEKEF47.js.map +0 -1
  239. package/dist/chunk-FYEVFGJD.js.map +0 -1
  240. package/dist/chunk-GKKAXVAJ.js.map +0 -1
  241. package/dist/chunk-OXNOINIP.js.map +0 -1
  242. /package/dist/{auto-sync-5CJBJMPZ.js.map → auto-sync-NUQWSFQD.js.map} +0 -0
  243. /package/dist/{chunk-ROHLEUTH.js.map → chunk-2KAYTPPT.js.map} +0 -0
  244. /package/dist/{chunk-NHQGDVJF.js.map → chunk-2SJCWLQD.js.map} +0 -0
  245. /package/dist/{chunk-YTWNKQ2G.js.map → chunk-3FC6LW6T.js.map} +0 -0
  246. /package/dist/{chunk-XEA4Z7JU.js.map → chunk-4FE2K57M.js.map} +0 -0
  247. /package/dist/{chunk-WI7JKV2T.js.map → chunk-4N3TFFPH.js.map} +0 -0
  248. /package/dist/{chunk-BTVX7ZXZ.js.map → chunk-4NFVPDIL.js.map} +0 -0
  249. /package/dist/{chunk-OUWAQVDJ.js.map → chunk-5CEJH5ZN.js.map} +0 -0
  250. /package/dist/{chunk-6QM24CP7.js.map → chunk-6GJS4BFH.js.map} +0 -0
  251. /package/dist/{chunk-J2FBJ63F.js.map → chunk-6O6A6YUO.js.map} +0 -0
  252. /package/dist/{chunk-HQ6NIBL6.js.map → chunk-7FL4CNPV.js.map} +0 -0
  253. /package/dist/{chunk-5VDJMYTF.js.map → chunk-7WWURLG6.js.map} +0 -0
  254. /package/dist/{chunk-QWRC7GIO.js.map → chunk-A4HH2EWA.js.map} +0 -0
  255. /package/dist/{chunk-4SKKVWLQ.js.map → chunk-AGJH5ISO.js.map} +0 -0
  256. /package/dist/{chunk-WRE3JPAW.js.map → chunk-B4XVLHJA.js.map} +0 -0
  257. /package/dist/{chunk-AJU4PJGY.js.map → chunk-BVKCV2ZY.js.map} +0 -0
  258. /package/dist/{chunk-DQY7NJ5L.js.map → chunk-CTOQEZSN.js.map} +0 -0
  259. /package/dist/{chunk-7OGJQP7T.js.map → chunk-DCWIQFNA.js.map} +0 -0
  260. /package/dist/{chunk-MHQC2WU2.js.map → chunk-DKTSR7EK.js.map} +0 -0
  261. /package/dist/{chunk-RN7MUWON.js.map → chunk-EHISUJFN.js.map} +0 -0
  262. /package/dist/{chunk-WLEB7WCG.js.map → chunk-EO5QWINU.js.map} +0 -0
  263. /package/dist/{chunk-XKXKSQU7.js.map → chunk-EXM3CQTZ.js.map} +0 -0
  264. /package/dist/{chunk-M3FWYURP.js.map → chunk-FE6DQUNJ.js.map} +0 -0
  265. /package/dist/{chunk-LCC5EZTT.js.map → chunk-FIVDN2SM.js.map} +0 -0
  266. /package/dist/{chunk-452WDNFO.js.map → chunk-GG6AJN7A.js.map} +0 -0
  267. /package/dist/{chunk-2IBGHRIO.js.map → chunk-GS55WYRL.js.map} +0 -0
  268. /package/dist/{chunk-IBTZEBUD.js.map → chunk-HYNHLBKA.js.map} +0 -0
  269. /package/dist/{chunk-EVWIEEKZ.js.map → chunk-IQ7WCZRW.js.map} +0 -0
  270. /package/dist/{chunk-B5XMS73R.js.map → chunk-IQVQJJL7.js.map} +0 -0
  271. /package/dist/{chunk-6RHNCKHG.js.map → chunk-K43PI6DQ.js.map} +0 -0
  272. /package/dist/{chunk-OIF36KGD.js.map → chunk-KCQA46NR.js.map} +0 -0
  273. /package/dist/{chunk-2LDBXPLB.js.map → chunk-KF74X62T.js.map} +0 -0
  274. /package/dist/{chunk-MAV46GWQ.js.map → chunk-KYYL4U6X.js.map} +0 -0
  275. /package/dist/{chunk-6GC5SGFE.js.map → chunk-L24JROPR.js.map} +0 -0
  276. /package/dist/{chunk-Q5ZU3RNY.js.map → chunk-LQ6JI4VH.js.map} +0 -0
  277. /package/dist/{chunk-GWKCEM3S.js.map → chunk-MCQDSY4G.js.map} +0 -0
  278. /package/dist/{chunk-HP5FMB6L.js.map → chunk-MDJURR27.js.map} +0 -0
  279. /package/dist/{chunk-T2PO5MUF.js.map → chunk-O7GOFAM3.js.map} +0 -0
  280. /package/dist/{chunk-Z2OXSMZK.js.map → chunk-OBXTMFZQ.js.map} +0 -0
  281. /package/dist/{chunk-K6ZN34WC.js.map → chunk-OV4D5T7V.js.map} +0 -0
  282. /package/dist/{chunk-OMLIFZ4I.js.map → chunk-PH3HOKYW.js.map} +0 -0
  283. /package/dist/{chunk-C3IW2F5Z.js.map → chunk-PLBIPT6I.js.map} +0 -0
  284. /package/dist/{chunk-QY7YA7OL.js.map → chunk-PNLCEFE4.js.map} +0 -0
  285. /package/dist/{chunk-AGRPGAKR.js.map → chunk-PONNZ54D.js.map} +0 -0
  286. /package/dist/{chunk-XZ4WBBB5.js.map → chunk-PWFWCGOO.js.map} +0 -0
  287. /package/dist/{chunk-XWEXT4XU.js.map → chunk-QANVLERJ.js.map} +0 -0
  288. /package/dist/{chunk-W4RVMTHR.js.map → chunk-QRDOSYOR.js.map} +0 -0
  289. /package/dist/{chunk-6IMKOIZ6.js.map → chunk-R6OVFAX6.js.map} +0 -0
  290. /package/dist/{chunk-JOASJWQR.js.map → chunk-SANZHXY2.js.map} +0 -0
  291. /package/dist/{chunk-7DTASS5T.js.map → chunk-SJHM6I4J.js.map} +0 -0
  292. /package/dist/{chunk-M6BVYHBU.js.map → chunk-STOEE37X.js.map} +0 -0
  293. /package/dist/{chunk-LXH3DIF2.js.map → chunk-U7D7NP4J.js.map} +0 -0
  294. /package/dist/{chunk-DRD2Q7HQ.js.map → chunk-UFS7OXGL.js.map} +0 -0
  295. /package/dist/{chunk-H3HDXD3U.js.map → chunk-UPTZYUYJ.js.map} +0 -0
  296. /package/dist/{chunk-3Z7NPD5T.js.map → chunk-UTYBJR7M.js.map} +0 -0
  297. /package/dist/{chunk-LN4YGHTM.js.map → chunk-UUH4YQOF.js.map} +0 -0
  298. /package/dist/{chunk-6VF75M3X.js.map → chunk-VGUOEDTU.js.map} +0 -0
  299. /package/dist/{chunk-44VFF3BB.js.map → chunk-VILEUJXC.js.map} +0 -0
  300. /package/dist/{chunk-7SI52C65.js.map → chunk-VL7DP3OW.js.map} +0 -0
  301. /package/dist/{chunk-7DHTMOND.js.map → chunk-VQ34TERH.js.map} +0 -0
  302. /package/dist/{chunk-6VMIHVGO.js.map → chunk-VX6OBUDW.js.map} +0 -0
  303. /package/dist/{chunk-EW5KFXHL.js.map → chunk-WDXCNJSF.js.map} +0 -0
  304. /package/dist/{chunk-X6IRLNOO.js.map → chunk-WIWPSQYU.js.map} +0 -0
  305. /package/dist/{chunk-DOCTITOP.js.map → chunk-WRFKZEO6.js.map} +0 -0
  306. /package/dist/{chunk-E6ZDCOHM.js.map → chunk-XBZQRZ6G.js.map} +0 -0
  307. /package/dist/{chunk-7YX23JBA.js.map → chunk-XHYGJVXL.js.map} +0 -0
  308. /package/dist/{chunk-JD4SCARD.js.map → chunk-YN4ZT4CW.js.map} +0 -0
  309. /package/dist/{chunk-YXWAILM4.js.map → chunk-YOI3ELXF.js.map} +0 -0
  310. /package/dist/{chunk-XCAZF7KQ.js.map → chunk-ZA2S2VLL.js.map} +0 -0
  311. /package/dist/{chunk-BEUDU7Y4.js.map → chunk-ZCWIH4LH.js.map} +0 -0
  312. /package/dist/{chunk-V25ZAOSB.js.map → chunk-ZPTISBQU.js.map} +0 -0
  313. /package/dist/{contradiction-scan-AZTGFMPY.js.map → contradiction-scan-HWGEOUDS.js.map} +0 -0
  314. /package/dist/{graph-edge-decay-KSVJGCZW.js.map → graph-edge-decay-D7OESCBR.js.map} +0 -0
  315. /package/dist/{resolution-IDTEBJFS.js.map → resolution-MN36NW5P.js.map} +0 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Decision-record surface contract + handler (issue #1548 Track A PR 2).
3
+ *
4
+ * Rule 39: one gate predicate, checked identically on every surface. Rule 22
5
+ * spirit: one implementation behind three thin wirings. The service holds
6
+ * only a thin delegate that builds a {@link DecisionSurfaceContext} and calls
7
+ * {@link handleCodingDecision}; the handler logic lives here so the
8
+ * access-service god file gains thin wiring only.
9
+ *
10
+ * No orchestrator imports (rule 11 — no shared mutable state). No circular
11
+ * dependency on access-service.ts: validation errors are thrown via
12
+ * `ctx.throwInputError`, which the service wires to EngramAccessInputError.
13
+ */
14
+ import type { CodingKnowledgeConfig, CodingContext, MemoryFile, MemoryFrontmatter, MemoryStatus } from "../types.js";
15
+ import {
16
+ ACTIVE_DECISION_STATUSES,
17
+ DEFAULT_DECISION_STATUS,
18
+ isDecisionStatus,
19
+ parseDecisionRecord,
20
+ serializeDecisionRecord,
21
+ type DecisionRecord,
22
+ type DecisionStatus,
23
+ } from "./decision-records.js";
24
+ import { log } from "../logger.js";
25
+
26
+ // ──────────────────────────────────────────────────────────────────────────
27
+ // Subcommands
28
+ // ──────────────────────────────────────────────────────────────────────────
29
+
30
+ export const DECISION_SUBCOMMANDS = [
31
+ "list",
32
+ "get",
33
+ "record",
34
+ "supersede",
35
+ ] as const;
36
+
37
+ export type DecisionSubcommand = (typeof DECISION_SUBCOMMANDS)[number];
38
+
39
+ const SUBCOMMAND_VALUES = DECISION_SUBCOMMANDS as readonly string[];
40
+
41
+ /**
42
+ * Type guard — narrows an unknown subcommand string to the
43
+ * {@link DecisionSubcommand} union.
44
+ */
45
+ export function isDecisionSubcommand(value: unknown): value is DecisionSubcommand {
46
+ return typeof value === "string" && SUBCOMMAND_VALUES.includes(value);
47
+ }
48
+
49
+ /**
50
+ * Human-readable subcommand list for error messages (rule 51 — list valid
51
+ * options so the caller can correct rather than guess).
52
+ */
53
+ export function formatDecisionSubcommands(): string {
54
+ return DECISION_SUBCOMMANDS.join(", ");
55
+ }
56
+
57
+ // ──────────────────────────────────────────────────────────────────────────
58
+ // Gate predicate — rule 39: one predicate, identical on every surface
59
+ // ──────────────────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * The single decision-record surface gate. Returns `true` only when:
63
+ * 1. `codingKnowledge.enabled` is on (the master Track A gate),
64
+ * 2. `codingKnowledge.decisionRecords` is on (the feature switch), AND
65
+ * 3. A coding context is attached (the session is project/branch scoped —
66
+ * decision records live *in* the coding namespace, rule 42).
67
+ *
68
+ * Every surface — MCP `engram.coding_decision`, HTTP
69
+ * `POST /engram/v1/coding/decisions`, CLI `engram-access decision` — MUST call
70
+ * this predicate (or the handler that embeds it) before dispatching. The
71
+ * tool-visibility gate in the MCP constructor checks conditions 1–2 only
72
+ * (coding context is per-session and cannot be evaluated at construction
73
+ * time); the call-time gate checks all three.
74
+ */
75
+ export function isDecisionRecordSurfaceEnabled(
76
+ config: CodingKnowledgeConfig,
77
+ codingContext: CodingContext | null | undefined,
78
+ ): boolean {
79
+ return (
80
+ config.enabled === true &&
81
+ config.decisionRecords === true &&
82
+ codingContext != null
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Config-only visibility gate — used by the MCP constructor to decide whether
88
+ * to advertise `engram.coding_decision` in `tools/list`. When this returns
89
+ * `false` the tools array is byte-identical to pre-feature (rule 39).
90
+ */
91
+ export function isDecisionRecordSurfaceVisible(
92
+ config: CodingKnowledgeConfig,
93
+ ): boolean {
94
+ return config.enabled === true && config.decisionRecords === true;
95
+ }
96
+
97
+ // ──────────────────────────────────────────────────────────────────────────
98
+ // Surface request / response shapes
99
+ // ──────────────────────────────────────────────────────────────────────────
100
+
101
+ /**
102
+ * Canonical surface request — one shape for all three transports. The
103
+ * `subcommand` field selects which operation runs; the remaining fields are
104
+ * optional depending on the subcommand.
105
+ *
106
+ * `sessionKey` identifies the session whose coding context scopes the
107
+ * operation. `namespace` overrides the coding-scoped namespace (same
108
+ * precedence as `memory_store` — explicit namespace wins).
109
+ */
110
+ export interface DecisionSurfaceRequest {
111
+ subcommand: DecisionSubcommand;
112
+ sessionKey?: string;
113
+ namespace?: string;
114
+ // get / supersede
115
+ id?: string;
116
+ // record
117
+ title?: string;
118
+ status?: string;
119
+ context?: string;
120
+ decision?: string;
121
+ consequences?: string;
122
+ entityRefs?: string[];
123
+ // supersede
124
+ supersedesId?: string;
125
+ }
126
+
127
+ /**
128
+ * Surface response — a discriminated union on `subcommand`. Each surface
129
+ * serializes this to its transport-appropriate shape.
130
+ */
131
+ export type DecisionSurfaceResponse =
132
+ | { subcommand: "list"; records: DecisionSurfaceRecord[]; count: number }
133
+ | { subcommand: "get"; found: boolean; record?: DecisionSurfaceRecord }
134
+ | { subcommand: "record"; memoryId: string; status: string }
135
+ | {
136
+ subcommand: "supersede";
137
+ supersededMemoryId: string;
138
+ replacementMemoryId: string;
139
+ };
140
+
141
+ /**
142
+ * Flattened record projection surfaced to clients. Stored as markdown +
143
+ * frontmatter memory files (category `"decision"`) — this shape is the
144
+ * read-side projection, not the storage format.
145
+ */
146
+ export interface DecisionSurfaceRecord {
147
+ id: string;
148
+ title: string;
149
+ status: string;
150
+ context?: string;
151
+ decision?: string;
152
+ consequences?: string;
153
+ entityRefs: string[];
154
+ supersedes?: string;
155
+ }
156
+
157
+ // ──────────────────────────────────────────────────────────────────────────
158
+ // Handler — the single implementation behind all three surfaces (rule 22)
159
+ // ──────────────────────────────────────────────────────────────────────────
160
+
161
+ /**
162
+ * Structural subset of StorageManager the decision handler reads or writes.
163
+ * Kept narrow so the module stays decoupled from storage.ts and is
164
+ * unit-testable with a stub.
165
+ */
166
+ export interface DecisionSurfaceStorage {
167
+ readonly dir: string;
168
+ /** The resolved namespace — used for catalog write recording. */
169
+ readonly namespace: string;
170
+ readAllMemories(): Promise<readonly MemoryFile[]>;
171
+ getMemoryById(id: string): Promise<MemoryFile | null>;
172
+ writeMemory(
173
+ category: "decision",
174
+ content: string,
175
+ options: {
176
+ confidence?: number;
177
+ tags?: string[];
178
+ source?: string;
179
+ /** Outer memory lifecycle status — set to "archived" for inactive
180
+ * decisions so generic recall/search/maintenance exclude them
181
+ * (review P2: persist inactive decision statuses in frontmatter). */
182
+ status?: MemoryStatus;
183
+ /** Decision-specific lifecycle marker, mirrored from the serialized
184
+ * body so the list/get projection has one authoritative source. */
185
+ structuredAttributes?: Record<string, string>;
186
+ },
187
+ ): Promise<string>;
188
+ writeMemoryFrontmatter(
189
+ memory: MemoryFile,
190
+ patch: Partial<MemoryFrontmatter>,
191
+ ): Promise<unknown>;
192
+ }
193
+
194
+ /**
195
+ * Dependencies the handler borrows from the service. The service constructs
196
+ * this context per call; the handler never touches the orchestrator directly.
197
+ * `throwInputError` lets the handler raise the surface-appropriate error
198
+ * class without importing access-service.ts (no circular dependency).
199
+ */
200
+ export interface DecisionSurfaceContext {
201
+ readonly codingKnowledge: CodingKnowledgeConfig;
202
+ getCodingContext(sessionKey: string): CodingContext | null;
203
+ /** Resolve storage through the SAME namespace path as memory_store
204
+ * (principal ACL + coding overlay + default fallback). The #1522 storage
205
+ * chokepoint records the catalog write automatically on every
206
+ * storage.writeMemory, so the handler does NOT touch the catalog itself. */
207
+ resolveStorage(request: DecisionSurfaceRequest): Promise<DecisionSurfaceStorage>;
208
+ /** Throw the surface-appropriate input-validation error. */
209
+ throwInputError(message: string): never;
210
+ }
211
+
212
+ /**
213
+ * The single shared implementation behind the MCP, HTTP, and CLI
214
+ * decision-record surfaces. All three transports dispatch through the
215
+ * `coding_decision` boundary operation, which calls this function via the
216
+ * service delegate.
217
+ *
218
+ * Gate (rule 39): `codingKnowledge.enabled + decisionRecords + coding
219
+ * context`. Persistence (rule 43): records are written through the storage
220
+ * manager's normal persist pipeline with category `"decision"` — no direct
221
+ * `fs` writes of memory content. Supersede (rule 25): the replacement is
222
+ * written BEFORE the old record's `structuredAttributes.decisionStatus` is
223
+ * set to `"superseded"` — the structuredAttribute is the authoritative
224
+ * lifecycle marker; content is never rewritten.
225
+ */
226
+ export async function handleCodingDecision(
227
+ request: DecisionSurfaceRequest,
228
+ ctx: DecisionSurfaceContext,
229
+ ): Promise<DecisionSurfaceResponse> {
230
+ const codingContext = request.sessionKey
231
+ ? ctx.getCodingContext(request.sessionKey)
232
+ : null;
233
+ if (!isDecisionRecordSurfaceEnabled(ctx.codingKnowledge, codingContext)) {
234
+ ctx.throwInputError(
235
+ "coding_decision requires codingKnowledge.enabled, codingKnowledge.decisionRecords, and an attached coding context",
236
+ );
237
+ }
238
+ switch (request.subcommand) {
239
+ case "list":
240
+ return decisionList(request, ctx);
241
+ case "get":
242
+ return decisionGet(request, ctx);
243
+ case "record":
244
+ return decisionRecord(request, ctx);
245
+ case "supersede":
246
+ return decisionSupersede(request, ctx);
247
+ }
248
+ }
249
+
250
+ async function decisionList(
251
+ request: DecisionSurfaceRequest,
252
+ ctx: DecisionSurfaceContext,
253
+ ): Promise<DecisionSurfaceResponse> {
254
+ const storage = await ctx.resolveStorage(request);
255
+ const memories = await storage.readAllMemories();
256
+ const records: DecisionSurfaceRecord[] = [];
257
+ for (const m of memories) {
258
+ if (m.frontmatter.category !== "decision") continue;
259
+ // Exclude lifecycle-retired memories. Any outer frontmatter.status other
260
+ // than undefined/"active" (archived, superseded, forgotten, rejected,
261
+ // quarantined, pending_review) means the generic memory lifecycle has
262
+ // intervened — hide the decision until that resolves. The decision-specific
263
+ // lifecycle marker lives in structuredAttributes.decisionStatus (review:
264
+ // hide all non-active outer statuses from decisions).
265
+ const memStatus = m.frontmatter.status;
266
+ if (memStatus && memStatus !== "active") continue;
267
+ const parsed = safeParseDecisionRecord(m.content);
268
+ if (!parsed) continue;
269
+ const structStatus = m.frontmatter.structuredAttributes?.decisionStatus;
270
+ const effectiveStatus = structStatus ?? parsed.status;
271
+ records.push({
272
+ id: m.frontmatter.id,
273
+ title: parsed.title,
274
+ status: effectiveStatus,
275
+ entityRefs: parsed.entityRefs,
276
+ supersedes: parsed.supersedes,
277
+ });
278
+ }
279
+ const visible = records.filter((r) =>
280
+ ACTIVE_DECISION_STATUSES.has(r.status as DecisionStatus),
281
+ );
282
+ return { subcommand: "list", records: visible, count: visible.length };
283
+ }
284
+
285
+ async function decisionGet(
286
+ request: DecisionSurfaceRequest,
287
+ ctx: DecisionSurfaceContext,
288
+ ): Promise<DecisionSurfaceResponse> {
289
+ if (!request.id?.trim()) {
290
+ ctx.throwInputError("id is required for the 'get' subcommand");
291
+ }
292
+ const storage = await ctx.resolveStorage(request);
293
+ const memory = await storage.getMemoryById(request.id!);
294
+ if (!memory || memory.frontmatter.category !== "decision") {
295
+ return { subcommand: "get", found: false };
296
+ }
297
+ const parsed = safeParseDecisionRecord(memory.content);
298
+ if (!parsed) {
299
+ return { subcommand: "get", found: false };
300
+ }
301
+ const structStatus = memory.frontmatter.structuredAttributes?.decisionStatus;
302
+ return {
303
+ subcommand: "get",
304
+ found: true,
305
+ record: {
306
+ id: memory.frontmatter.id,
307
+ title: parsed.title,
308
+ status: structStatus ?? parsed.status,
309
+ context: parsed.context,
310
+ decision: parsed.decision,
311
+ consequences: parsed.consequences,
312
+ entityRefs: parsed.entityRefs,
313
+ supersedes: parsed.supersedes,
314
+ },
315
+ };
316
+ }
317
+
318
+ async function decisionRecord(
319
+ request: DecisionSurfaceRequest,
320
+ ctx: DecisionSurfaceContext,
321
+ ): Promise<DecisionSurfaceResponse> {
322
+ if (!request.title?.trim()) {
323
+ ctx.throwInputError("title is required for the 'record' subcommand");
324
+ }
325
+ if (!request.decision?.trim()) {
326
+ ctx.throwInputError("decision is required for the 'record' subcommand");
327
+ }
328
+ const status: DecisionStatus = request.status?.trim()
329
+ ? isDecisionStatus(request.status)
330
+ ? request.status
331
+ : raiseInvalidStatus(request.status, ctx)
332
+ : DEFAULT_DECISION_STATUS;
333
+ const record: DecisionRecord = {
334
+ id: "",
335
+ title: request.title.trim(),
336
+ status,
337
+ context: request.context?.trim() ?? "",
338
+ decision: request.decision.trim(),
339
+ consequences: request.consequences?.trim() ?? "",
340
+ entityRefs: request.entityRefs ?? [],
341
+ };
342
+ const content = serializeDecisionRecord(record);
343
+ const storage = await ctx.resolveStorage(request);
344
+ const isActive = ACTIVE_DECISION_STATUSES.has(status);
345
+ const memoryId = await storage.writeMemory("decision", content, {
346
+ confidence: 1.0,
347
+ tags: ["decision-record"],
348
+ source: "coding-decision",
349
+ // Persist the decision lifecycle in BOTH places so generic
350
+ // recall/search/maintenance (which read frontmatter.status) and the
351
+ // decision list/get projection (which reads structuredAttributes) agree:
352
+ // - structuredAttributes.decisionStatus is the authoritative decision
353
+ // marker, mirrored from the serialized body (one source of truth);
354
+ // - frontmatter.status is set to "archived" for inactive decisions
355
+ // (rejected/superseded) so the outer memory pipeline excludes them
356
+ // from the active corpus exactly like a supersede does (review P2:
357
+ // persist inactive decision statuses in frontmatter).
358
+ structuredAttributes: { decisionStatus: status },
359
+ status: isActive ? undefined : "archived",
360
+ });
361
+ log.info(
362
+ `access-write op=coding_decision/record memoryId=${memoryId} status=${status}`,
363
+ );
364
+ return { subcommand: "record", memoryId, status };
365
+ }
366
+
367
+ async function decisionSupersede(
368
+ request: DecisionSurfaceRequest,
369
+ ctx: DecisionSurfaceContext,
370
+ ): Promise<DecisionSurfaceResponse> {
371
+ // The schema advertises `supersedesId` for MCP/HTTP clients that name it
372
+ // explicitly; treat it as an alias for `id` when `id` is absent (review P2).
373
+ const targetId = request.id?.trim() || request.supersedesId?.trim();
374
+ if (!targetId) {
375
+ ctx.throwInputError(
376
+ "id (or supersedesId) is required for the 'supersede' subcommand (the record being superseded)",
377
+ );
378
+ }
379
+ if (!request.title?.trim()) {
380
+ ctx.throwInputError(
381
+ "title is required for the 'supersede' subcommand (the replacement record)",
382
+ );
383
+ }
384
+ if (!request.decision?.trim()) {
385
+ ctx.throwInputError("decision is required for the 'supersede' subcommand");
386
+ }
387
+ const storage = await ctx.resolveStorage(request);
388
+ const oldMemory = await storage.getMemoryById(targetId);
389
+ if (!oldMemory || oldMemory.frontmatter.category !== "decision") {
390
+ ctx.throwInputError(`decision record not found: ${targetId}`);
391
+ }
392
+ const oldParsed = safeParseDecisionRecord(oldMemory.content);
393
+ if (!oldParsed) {
394
+ ctx.throwInputError(
395
+ `decision record is corrupted and cannot be superseded: ${targetId}`,
396
+ );
397
+ }
398
+ // Rule 25: write the replacement BEFORE mutating the old record's status.
399
+ const replacement: DecisionRecord = {
400
+ id: "",
401
+ title: request.title.trim(),
402
+ status: "accepted",
403
+ context: request.context?.trim() ?? "",
404
+ decision: request.decision.trim(),
405
+ consequences: request.consequences?.trim() ?? "",
406
+ entityRefs: request.entityRefs ?? [],
407
+ supersedes: targetId,
408
+ };
409
+ const replacementContent = serializeDecisionRecord(replacement);
410
+ const replacementId = await storage.writeMemory(
411
+ "decision",
412
+ replacementContent,
413
+ {
414
+ confidence: 1.0,
415
+ tags: ["decision-record"],
416
+ source: "coding-decision",
417
+ // Mirror decisionRecord: persist structuredAttributes.decisionStatus on
418
+ // the replacement so list/get projection and QMD indexing see the
419
+ // authoritative marker (review: supersede omits decisionStatus attrs).
420
+ structuredAttributes: { decisionStatus: "accepted" },
421
+ },
422
+ );
423
+ // Mark the old record superseded: set BOTH frontmatter.status (so
424
+ // recall/search/maintenance exclude it from the active corpus — review P2)
425
+ // AND structuredAttributes.decisionStatus (the decision-specific lifecycle
426
+ // marker used by list/get projection). The content body is not mutated.
427
+ // Rule 25: the replacement is written BEFORE the old record is mutated so
428
+ // a frontmatter-write failure leaves a harmless duplicate, not a missing
429
+ // record. Best-effort: log the failure but don't roll back the replacement
430
+ // (review: cursor partial-write thread).
431
+ try {
432
+ await storage.writeMemoryFrontmatter(oldMemory, {
433
+ status: "archived",
434
+ // Refresh the updated timestamp so the archive/supersede lifecycle event
435
+ // and browse/maintenance sort key reflect when the decision was retired,
436
+ // not when it was originally recorded (review: set updated timestamp when
437
+ // retiring old decisions).
438
+ updated: new Date().toISOString(),
439
+ structuredAttributes: {
440
+ ...(oldMemory.frontmatter.structuredAttributes ?? {}),
441
+ decisionStatus: "superseded",
442
+ },
443
+ });
444
+ } catch (err) {
445
+ log.warn(
446
+ `coding_decision/supersede: replacement ${replacementId} written but old record ${targetId} status update failed — old record will still appear until retried: ${err instanceof Error ? err.message : String(err)}`,
447
+ );
448
+ }
449
+ log.info(
450
+ `access-write op=coding_decision/supersede superseded=${targetId} replacement=${replacementId}`,
451
+ );
452
+ return {
453
+ subcommand: "supersede",
454
+ supersededMemoryId: targetId,
455
+ replacementMemoryId: replacementId,
456
+ };
457
+ }
458
+
459
+ // ──────────────────────────────────────────────────────────────────────────
460
+ // Local helpers
461
+ // ──────────────────────────────────────────────────────────────────────────
462
+
463
+ function safeParseDecisionRecord(content: string): DecisionRecord | null {
464
+ try {
465
+ return parseDecisionRecord(content);
466
+ } catch {
467
+ return null;
468
+ }
469
+ }
470
+
471
+ function raiseInvalidStatus(value: string, ctx: DecisionSurfaceContext): never {
472
+ ctx.throwInputError(
473
+ `invalid decision status "${value}". Valid options: proposed, accepted, superseded, rejected`,
474
+ );
475
+ }
@@ -435,15 +435,8 @@ export async function persistExplicitCapture(
435
435
  expiresAt: candidate.expiresAt,
436
436
  source: source === "inline" ? "explicit-inline" : "explicit",
437
437
  });
438
- // Record the catalog write touch (issue #1499, round 5 codex P2). Explicit
439
- // captures bypass the extraction write path, so without this their namespace
440
- // never updates `lastWriteAt`. An undefined namespace means the DEFAULT root
441
- // (round 6, codex P2), which recordCatalogWrite resolves. The method is an
442
- // optional best-effort hook — guard so Orchestrator-like callers without it
443
- // don't break (rule #33). Best-effort and failure-tolerant.
444
- if (typeof orchestrator.recordCatalogWrite === "function") {
445
- orchestrator.recordCatalogWrite(resolvedNamespace, storage.dir);
446
- }
438
+ // #1522: catalog touch handled at the storage chokepoint the StorageManager's
439
+ // post-write hook records the namespace touch automatically.
447
440
 
448
441
  const created = new Date().toISOString();
449
442
  const event: MemoryLifecycleEvent = {
@@ -559,9 +552,7 @@ export async function queueExplicitCaptureForReview(
559
552
  // `writeMemory` returns an id. If the later pending-review frontmatter update
560
553
  // fails, the memory file is still durable and must not disappear from
561
554
  // writtenSince/maintenance scheduling. Guarded optional hook (rule #33).
562
- if (typeof orchestrator.recordCatalogWrite === "function") {
563
- orchestrator.recordCatalogWrite(queueNamespace, storage.dir);
564
- }
555
+ // #1522: catalog touch handled at the storage chokepoint.
565
556
  }
566
557
  const event: MemoryLifecycleEvent = {
567
558
  eventId: `mle-${randomUUID()}`,
package/src/logger.ts CHANGED
@@ -15,11 +15,20 @@ const NOOP_LOGGER: LoggerBackend = {
15
15
  let _backend: LoggerBackend = NOOP_LOGGER;
16
16
  let _debug = false;
17
17
 
18
+ // Late-bind console.* rather than capturing references at module import
19
+ // time. In production this is behaviour-identical to the pre-bound
20
+ // `.bind(console)` form — `console.info(msg, ...)` resolves `this` to
21
+ // `console` whether called directly or via an arrow wrapper — but it lets
22
+ // harnesses that swap console methods (e.g. runCli in @remnic/cli) route
23
+ // logger output through their capture sinks. With the old pre-bound form,
24
+ // any command that called initLogger() pinned the logger to the original
25
+ // console methods before the harness could swap them, so log.warn /
26
+ // log.info output escaped the capture buffer.
18
27
  const CONSOLE_LOGGER: LoggerBackend = {
19
- info: console.info.bind(console),
20
- warn: console.warn.bind(console),
21
- error: console.error.bind(console),
22
- debug: console.debug.bind(console),
28
+ info: (msg, ...args) => console.info(msg, ...args),
29
+ warn: (msg, ...args) => console.warn(msg, ...args),
30
+ error: (msg, ...args) => console.error(msg, ...args),
31
+ debug: (msg, ...args) => console.debug(msg, ...args),
23
32
  };
24
33
 
25
34
  export function initLogger(backend?: LoggerBackend, debug?: boolean): void {
@@ -539,7 +539,7 @@ test("register does not overwrite prior discoveredBy on an existing record", asy
539
539
 
540
540
  // A record first PRE-REGISTERED via the router's onResolve hook (config) is
541
541
  // UPGRADED to "write" by a later real write touch (round 6, codex P2 — NBPmT):
542
- // `storageFor()` fires registerResolved (config) before recordCatalogWrite runs,
542
+ // `storageFor()` fires registerResolved (config) before the storage chokepoint runs,
543
543
  // so without the upgrade `listNamespaces({ discoveredBy: "write" })` would miss a
544
544
  // genuinely-written namespace. A non-write touch (read/register) still preserves
545
545
  // provenance.
@@ -689,7 +689,7 @@ test("chunked write path contract: markWrite updates lastWriteAt for the namespa
689
689
  const ns = "project-origin-chunked";
690
690
  const storageDir = path.join(memoryDir, "namespaces", namespaceIdentityToken(ns));
691
691
  // This is exactly the call the orchestrator chunked branch now makes
692
- // (markCatalogWrite -> markWrite with discoveredBy "write" + storageDir).
692
+ // (storage chokepoint -> markWrite with discoveredBy "write" + storageDir).
693
693
  await catalog.markWrite(ns, { discoveredBy: "write", storageDir });
694
694
  const record = await catalog.getNamespaceRecord(ns);
695
695
  assert.ok(record?.lastWriteAt, "chunked write must update lastWriteAt");
@@ -5,6 +5,7 @@ import { StorageManager } from "../storage.js";
5
5
  import type { PluginConfig } from "../types.js";
6
6
  import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
7
7
  import { namespaceIdentityToken, normalizeNamespaceIdentity } from "./identity.js";
8
+ import type { NamespaceCatalog } from "./catalog.js";
8
9
 
9
10
  async function exists(p: string): Promise<boolean> {
10
11
  try {
@@ -228,6 +229,9 @@ export class NamespaceStorageRouter {
228
229
  // `whenResolveHooksSettled`). Entries are removed as each hook settles, so the
229
230
  // set holds at most one promise per concurrently-resolving namespace.
230
231
  private readonly pendingResolveHooks = new Set<Promise<unknown>>();
232
+ // Pending post-write catalog touch promises (#1522). Like pendingResolveHooks,
233
+ // lets tests await fire-and-forget write touches deterministically.
234
+ private readonly pendingWriteTouches = new Set<Promise<unknown>>();
231
235
 
232
236
  // Normalized (trimmed) default namespace identity (NH-FH). `storageFor`
233
237
  // normalizes its input, so default-namespace branches must compare against the
@@ -238,6 +242,8 @@ export class NamespaceStorageRouter {
238
242
  constructor(
239
243
  private readonly config: PluginConfig,
240
244
  private readonly hooks: NamespaceStorageRouterHooks = {},
245
+ /** Catalog reference for post-write/read touches (issue #1522 chokepoint). */
246
+ private readonly catalog?: NamespaceCatalog,
241
247
  ) {
242
248
  this.defaultNamespaceIdentity = normalizeNamespaceIdentity(config.defaultNamespace);
243
249
  }
@@ -288,6 +294,9 @@ export class NamespaceStorageRouter {
288
294
  // (used by extraction and shared-promotion paths) strip citations consistently,
289
295
  // matching the behaviour of the primary this.storage instance in the orchestrator.
290
296
  sm.citationTemplate = this.config.inlineSourceAttributionFormat;
297
+ // #1522: install the post-write catalog touch at the chokepoint — every
298
+ // successful write on this StorageManager records the namespace touch.
299
+ this.bindCatalogWriteHook(sm, ns);
291
300
  this.cache.set(ns, sm);
292
301
  this.notifyResolved(ns, root);
293
302
  return sm;
@@ -369,6 +378,78 @@ export class NamespaceStorageRouter {
369
378
  }
370
379
  }
371
380
 
381
+ /**
382
+ * Install the post-write catalog touch hook on an externally-constructed
383
+ * StorageManager (issue #1522). Used by the orchestrator for the legacy
384
+ * default-namespace storage (`this.storage`) that bypasses the router.
385
+ */
386
+ bindCatalogWriteHook(sm: StorageManager, namespace: string): void {
387
+ sm.onCatalogWrite = () => this.touchCatalogWrite(namespace, sm.dir);
388
+ }
389
+
390
+ /**
391
+ * Post-write catalog touch (issue #1522 chokepoint). Called by every
392
+ * StorageManager's post-write hook AFTER a successful write. Best-effort
393
+ * and failure-tolerant — a catalog error MUST NOT affect the primary write
394
+ * (gotcha #13, rule #40). Fire-and-forget by design.
395
+ */
396
+ private touchCatalogWrite(namespace: string, storageDir: string): void {
397
+ if (!this.catalog) return;
398
+ const touch = this.catalog
399
+ .markWrite(namespace, { discoveredBy: "write", storageDir })
400
+ .catch(() => undefined);
401
+ this.pendingWriteTouches.add(touch);
402
+ void touch.then(
403
+ () => { this.pendingWriteTouches.delete(touch); },
404
+ () => { this.pendingWriteTouches.delete(touch); },
405
+ );
406
+ }
407
+
408
+ /**
409
+ * Record a namespace read in the catalog (issue #1522 chokepoint move).
410
+ * Best-effort and failure-tolerant. Used by recall paths so the read touch
411
+ * lives in the storage layer, not at the caller.
412
+ */
413
+ recordRead(namespace: string, storageDir?: string): void {
414
+ if (!this.catalog) return;
415
+ const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);
416
+ void this.catalog
417
+ .markRead(ns, { discoveredBy: "read", storageDir })
418
+ .catch(() => undefined);
419
+ }
420
+ /**
421
+ * Record a namespace write touch in the catalog (issue #1522). Best-effort
422
+ * and failure-tolerant. Used by consolidation/cleanup passes that may mutate
423
+ * the store via delete-only paths (e.g. entity-file merges, TTL cleanup) so
424
+ * the namespace's lastWriteAt stays fresh even when no explicit write went
425
+ * through the storage chokepoint. This is NOT a substitute for the chokepoint
426
+ * — it's a belt-and-suspenders for paths the chokepoint doesn't cover.
427
+ */
428
+ recordWrite(namespace: string, storageDir?: string): void {
429
+ if (!this.catalog) return;
430
+ const ns = normalizeNamespaceIdentity(namespace || this.config.defaultNamespace);
431
+ const touch = this.catalog
432
+ .markWrite(ns, { discoveredBy: "write", storageDir })
433
+ .catch(() => undefined);
434
+ this.pendingWriteTouches.add(touch);
435
+ void touch.then(
436
+ () => { this.pendingWriteTouches.delete(touch); },
437
+ () => { this.pendingWriteTouches.delete(touch); },
438
+ );
439
+ }
440
+
441
+ /**
442
+ * Resolve once every in-flight post-write catalog touch has settled (#1522).
443
+ * Mirrors `whenResolveHooksSettled()`: the StorageManager's post-write hook
444
+ * fires the catalog touch fire-and-forget, so tests asserting lastWriteAt
445
+ * moved should await this instead of racing a timer.
446
+ */
447
+ async whenWriteTouchesSettled(): Promise<void> {
448
+ while (this.pendingWriteTouches.size > 0) {
449
+ await Promise.allSettled([...this.pendingWriteTouches]);
450
+ }
451
+ }
452
+
372
453
  /**
373
454
  * Resolve once every in-flight `onResolve` registration has settled.
374
455
  *