@remnic/core 9.29.1 → 9.30.0

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 (355) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +19 -18
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +20 -19
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +19 -18
  7. package/dist/access-citation-namespace.js +2 -2
  8. package/dist/access-cli.js +45 -44
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-health-types.d.ts +1 -1
  11. package/dist/access-http-lcm-compaction.d.ts +11 -11
  12. package/dist/access-http.d.ts +11 -11
  13. package/dist/access-http.js +25 -24
  14. package/dist/access-identity-continuity-surface.d.ts +8 -8
  15. package/dist/access-identity-continuity-surface.js +19 -18
  16. package/dist/access-lcm-surface.d.ts +10 -10
  17. package/dist/access-lcm-surface.js +19 -18
  18. package/dist/access-mcp.d.ts +11 -11
  19. package/dist/access-mcp.js +23 -22
  20. package/dist/access-memory-search-fanout.d.ts +2 -2
  21. package/dist/{access-namespace-preflight-DCXBkDp0.d.ts → access-namespace-preflight-VfXssvoI.d.ts} +1 -1
  22. package/dist/access-namespace-preflight.d.ts +3 -3
  23. package/dist/access-namespace-preflight.js +19 -18
  24. package/dist/access-observe-write-surface.d.ts +10 -10
  25. package/dist/access-observe-write-surface.js +19 -18
  26. package/dist/access-operations-batch.js +20 -19
  27. package/dist/access-operations.d.ts +11 -11
  28. package/dist/access-operations.js +22 -21
  29. package/dist/access-recall-concurrency.d.ts +11 -11
  30. package/dist/access-recall-concurrency.js +19 -18
  31. package/dist/access-recall-response.d.ts +10 -10
  32. package/dist/access-recall-response.js +19 -18
  33. package/dist/access-recall-surface.d.ts +10 -10
  34. package/dist/access-recall-surface.js +19 -18
  35. package/dist/access-schema.js +3 -3
  36. package/dist/{access-service-DvPW_M5j.d.ts → access-service-B5RwVsTV.d.ts} +7 -7
  37. package/dist/access-service.d.ts +11 -11
  38. package/dist/access-service.js +19 -18
  39. package/dist/access-surface-catalog.d.ts +11 -11
  40. package/dist/access-wearables-meetings-surface.d.ts +4 -3
  41. package/dist/action-confidence.d.ts +1 -1
  42. package/dist/active-memory-bridge.d.ts +1 -1
  43. package/dist/active-recall.d.ts +1 -1
  44. package/dist/active-recall.js +3 -3
  45. package/dist/behavior-learner.d.ts +1 -1
  46. package/dist/behavior-signals.d.ts +1 -1
  47. package/dist/bootstrap.d.ts +8 -8
  48. package/dist/briefing.d.ts +3 -2
  49. package/dist/briefing.js +10 -9
  50. package/dist/buffer-surprise-report.d.ts +1 -1
  51. package/dist/buffer.d.ts +3 -2
  52. package/dist/bulk-import/index.d.ts +3 -3
  53. package/dist/calibration.d.ts +1 -1
  54. package/dist/capabilities.d.ts +1 -1
  55. package/dist/{catalog-BIVgftWu.d.ts → catalog-V7inUQdp.d.ts} +1 -1
  56. package/dist/causal-behavior.d.ts +1 -1
  57. package/dist/causal-consolidation.d.ts +1 -1
  58. package/dist/causal-consolidation.js +11 -10
  59. package/dist/causal-consolidation.js.map +1 -1
  60. package/dist/causal-trajectory-graph.d.ts +1 -1
  61. package/dist/{chunk-FZRQZW4H.js → chunk-2EUVEDF5.js} +2 -2
  62. package/dist/{chunk-PBUZNGMX.js → chunk-4WKA6I65.js} +20 -20
  63. package/dist/{chunk-PBUZNGMX.js.map → chunk-4WKA6I65.js.map} +1 -1
  64. package/dist/{chunk-AIK5W3IW.js → chunk-5TFP4CUL.js} +68 -2
  65. package/dist/chunk-5TFP4CUL.js.map +1 -0
  66. package/dist/{chunk-3JNDOAQ2.js → chunk-5W4ZLBEZ.js} +9 -9
  67. package/dist/{chunk-5ZCUDWD4.js → chunk-62U4J2GN.js} +2 -2
  68. package/dist/{chunk-IGPEJVBY.js → chunk-66772WBP.js} +2 -2
  69. package/dist/{chunk-EP5BVCRI.js → chunk-6BNE62PM.js} +2 -2
  70. package/dist/{chunk-AAP5T6QG.js → chunk-6DNMUYEE.js} +2 -2
  71. package/dist/{chunk-35DL7IOI.js → chunk-6FUPCJUL.js} +7 -7
  72. package/dist/{chunk-JP3YPDAW.js → chunk-6U7F52DE.js} +19 -19
  73. package/dist/{chunk-E4OCKU67.js → chunk-7XC2HX75.js} +2 -2
  74. package/dist/{chunk-OVRQLAYR.js → chunk-AZ5BTEKX.js} +2 -2
  75. package/dist/{chunk-2AXMQWCN.js → chunk-CZRYLSSW.js} +3 -3
  76. package/dist/chunk-FBUE6PD3.js +1769 -0
  77. package/dist/chunk-FBUE6PD3.js.map +1 -0
  78. package/dist/{chunk-PTSNLEXK.js → chunk-FDAMS4PK.js} +2 -2
  79. package/dist/{chunk-WL5EBRDH.js → chunk-FFAZ2XFU.js} +9 -8
  80. package/dist/chunk-FFAZ2XFU.js.map +1 -0
  81. package/dist/{chunk-2GXM5N5A.js → chunk-GIBMDSJ2.js} +2 -2
  82. package/dist/{chunk-E5OBR6CJ.js → chunk-H5CPS5JP.js} +3 -3
  83. package/dist/{chunk-2D7FEUCK.js → chunk-I5NLEENJ.js} +2 -2
  84. package/dist/{chunk-DRBXHRXE.js → chunk-IB26HVAJ.js} +2 -2
  85. package/dist/{chunk-QDKK63Y7.js → chunk-IC6CBHCA.js} +3 -3
  86. package/dist/{chunk-IAEAFLF2.js → chunk-JCBWCYEJ.js} +2 -2
  87. package/dist/{chunk-3PDOVUHC.js → chunk-KXB4VXHY.js} +2 -2
  88. package/dist/{chunk-WU7TTVOD.js → chunk-LBOD4U5B.js} +7 -7
  89. package/dist/{chunk-UBKEQOTD.js → chunk-LGHKJABJ.js} +3 -3
  90. package/dist/{chunk-VSURVXKY.js → chunk-M3C2V3PV.js} +10 -10
  91. package/dist/{chunk-OFHFH655.js → chunk-MJCGLM25.js} +8 -8
  92. package/dist/{chunk-OYJKSZIQ.js → chunk-NHWB6HG2.js} +2 -2
  93. package/dist/{chunk-TC3VNKGX.js → chunk-NM2WP3B7.js} +4 -4
  94. package/dist/{chunk-GAWOEQEQ.js → chunk-P5PXWTW7.js} +2 -2
  95. package/dist/chunk-PH3ZWL3W.js +786 -0
  96. package/dist/chunk-PH3ZWL3W.js.map +1 -0
  97. package/dist/{chunk-FUYEVEX7.js → chunk-PUS63Q3G.js} +24 -22
  98. package/dist/{chunk-FUYEVEX7.js.map → chunk-PUS63Q3G.js.map} +1 -1
  99. package/dist/{chunk-2QMKMVHH.js → chunk-QNWXCJ76.js} +2 -2
  100. package/dist/{chunk-JMQUBRV6.js → chunk-RGIWINDN.js} +340 -771
  101. package/dist/chunk-RGIWINDN.js.map +1 -0
  102. package/dist/{chunk-WRFBW2TT.js → chunk-SPJXKAVF.js} +3 -3
  103. package/dist/{chunk-HKJCMOT6.js → chunk-TH2LSRMG.js} +2 -2
  104. package/dist/{chunk-CW4AGY3G.js → chunk-VI2JQPNQ.js} +2 -2
  105. package/dist/{chunk-OP6F6L3S.js → chunk-W5EG6LE3.js} +2 -2
  106. package/dist/{chunk-DEBHXHJR.js → chunk-WIPEXG6R.js} +2 -2
  107. package/dist/{chunk-SH6YO3LT.js → chunk-WVO4G37W.js} +2 -2
  108. package/dist/{chunk-7L45JFPW.js → chunk-YTQNQ4KB.js} +14 -14
  109. package/dist/{chunk-PKRIQL3V.js → chunk-YTSNAUXN.js} +2 -2
  110. package/dist/{chunk-5QDRGWT2.js → chunk-ZPSVBSOF.js} +4 -4
  111. package/dist/{cli-CxoSOiG0.d.ts → cli-C4GbETPC.d.ts} +5 -5
  112. package/dist/cli.d.ts +13 -13
  113. package/dist/cli.js +36 -35
  114. package/dist/compounding/engine.d.ts +3 -2
  115. package/dist/compounding/engine.js +10 -9
  116. package/dist/compounding/preference-consolidator.d.ts +1 -1
  117. package/dist/compression-optimizer.d.ts +1 -1
  118. package/dist/config.d.ts +1 -1
  119. package/dist/config.js +3 -3
  120. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  121. package/dist/connectors/codex-materialize-runner.js +10 -9
  122. package/dist/connectors/codex-materialize.d.ts +1 -1
  123. package/dist/connectors/index.d.ts +1 -1
  124. package/dist/connectors/index.js +10 -9
  125. package/dist/consolidation-provenance-check.d.ts +3 -2
  126. package/dist/consolidation-undo.d.ts +3 -2
  127. package/dist/contradiction/index.d.ts +3 -2
  128. package/dist/conversation-index/backend.d.ts +1 -1
  129. package/dist/conversation-index/chunker.d.ts +1 -1
  130. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  131. package/dist/conversation-index/indexer.d.ts +1 -1
  132. package/dist/conversation-index/search.d.ts +1 -1
  133. package/dist/corpus-watermark.d.ts +1 -1
  134. package/dist/corpus-watermark.js +12 -11
  135. package/dist/day-summary.d.ts +1 -1
  136. package/dist/delinearize.d.ts +1 -1
  137. package/dist/direct-answer-wiring.d.ts +1 -1
  138. package/dist/direct-answer.d.ts +1 -1
  139. package/dist/embedding-fallback.d.ts +1 -1
  140. package/dist/enrichment/index.d.ts +1 -1
  141. package/dist/entity-retrieval.d.ts +3 -2
  142. package/dist/entity-retrieval.js +10 -9
  143. package/dist/entity-schema.d.ts +1 -1
  144. package/dist/explicit-capture.d.ts +52 -10
  145. package/dist/explicit-capture.js +13 -1
  146. package/dist/extraction-error-classification.d.ts +1 -1
  147. package/dist/extraction-faithfulness.d.ts +1 -1
  148. package/dist/extraction-judge-telemetry.d.ts +1 -1
  149. package/dist/extraction-judge-training.d.ts +1 -1
  150. package/dist/extraction-judge.d.ts +1 -1
  151. package/dist/extraction-liveness.d.ts +1 -1
  152. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  153. package/dist/extraction-source-grounding.d.ts +1 -1
  154. package/dist/extraction.d.ts +1 -1
  155. package/dist/fallback-llm.d.ts +1 -1
  156. package/dist/{forget-HRPLVB3W.js → forget-4UY2EOKH.js} +3 -3
  157. package/dist/graph-dashboard-diff.d.ts +1 -1
  158. package/dist/graph-dashboard-key.d.ts +1 -1
  159. package/dist/graph-dashboard-parser.d.ts +1 -1
  160. package/dist/graph-edge-reinforcement.d.ts +1 -1
  161. package/dist/graph-snapshot.d.ts +1 -1
  162. package/dist/graph.d.ts +1 -1
  163. package/dist/importance.d.ts +1 -1
  164. package/dist/importers/index.d.ts +1 -1
  165. package/dist/in-flight-reads.d.ts +1 -1
  166. package/dist/index.d.ts +19 -19
  167. package/dist/index.js +47 -45
  168. package/dist/intent.d.ts +1 -1
  169. package/dist/lcm/engine.d.ts +1 -1
  170. package/dist/lcm/index.d.ts +1 -1
  171. package/dist/lcm/tools.d.ts +1 -1
  172. package/dist/lifecycle.d.ts +1 -1
  173. package/dist/live-connectors-runner.d.ts +1 -1
  174. package/dist/local-llm.d.ts +1 -1
  175. package/dist/local-model-endpoint.d.ts +1 -1
  176. package/dist/maintenance/memory-governance.d.ts +1 -1
  177. package/dist/maintenance/memory-governance.js +10 -9
  178. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  179. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -9
  180. package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
  181. package/dist/maintenance/rebuild-memory-projection.js +11 -10
  182. package/dist/{maintenance-Cb-PjxQd.d.ts → maintenance-CL30NWtc.d.ts} +3 -3
  183. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  184. package/dist/memory-action-policy.d.ts +1 -1
  185. package/dist/memory-cache.d.ts +1 -1
  186. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  187. package/dist/memory-projection-store.d.ts +1 -1
  188. package/dist/memory-provenance.d.ts +1 -1
  189. package/dist/memory-worth-outcomes.d.ts +3 -2
  190. package/dist/models-json.d.ts +1 -1
  191. package/dist/namespaces/migrate.d.ts +4 -3
  192. package/dist/namespaces/migrate.js +11 -10
  193. package/dist/namespaces/principal.d.ts +1 -1
  194. package/dist/namespaces/search.d.ts +1 -1
  195. package/dist/namespaces/storage.d.ts +4 -3
  196. package/dist/namespaces/storage.js +10 -9
  197. package/dist/native-knowledge.d.ts +1 -1
  198. package/dist/offline-sync-impression-drain.d.ts +1 -1
  199. package/dist/offline-sync-impression-drain.js +5 -5
  200. package/dist/offline-sync.js +2 -2
  201. package/dist/operator-doctor-corpus.d.ts +1 -1
  202. package/dist/operator-doctor-corpus.js +13 -12
  203. package/dist/operator-doctor-replica.d.ts +1 -1
  204. package/dist/operator-toolkit.d.ts +4 -3
  205. package/dist/operator-toolkit.js +18 -17
  206. package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
  207. package/dist/orchestration/maintenance.d.ts +5 -4
  208. package/dist/orchestration/maintenance.js +15 -14
  209. package/dist/{orchestrator-5GKqb_HL.d.ts → orchestrator-CKTCRwWV.d.ts} +7 -7
  210. package/dist/orchestrator.d.ts +8 -8
  211. package/dist/orchestrator.js +45 -44
  212. package/dist/patterns-cli.d.ts +1 -1
  213. package/dist/{pipeline-3Qi1TpRo.d.ts → pipeline-B7wj4rRZ.d.ts} +1 -1
  214. package/dist/policy-runtime.d.ts +1 -1
  215. package/dist/proactive-contention.d.ts +1 -1
  216. package/dist/provenance.d.ts +1 -1
  217. package/dist/{purge-YSGX5BOG.js → purge-533FZLS4.js} +16 -9
  218. package/dist/purge-533FZLS4.js.map +1 -0
  219. package/dist/qmd-preflight.d.ts +2 -2
  220. package/dist/{qmd-D9JqzzRX.d.ts → qmd-q9tg4dHp.d.ts} +1 -1
  221. package/dist/qmd-recall-cache.d.ts +1 -1
  222. package/dist/qmd.d.ts +2 -2
  223. package/dist/recall-concurrency-config.d.ts +1 -1
  224. package/dist/recall-disclosure-escalation.d.ts +1 -1
  225. package/dist/recall-explain-renderer.d.ts +1 -1
  226. package/dist/recall-memory-map.d.ts +1 -1
  227. package/dist/recall-planner-llm.d.ts +1 -1
  228. package/dist/recall-state.d.ts +1 -1
  229. package/dist/recall-state.js +2 -2
  230. package/dist/recall-tag-filter.d.ts +1 -1
  231. package/dist/recall-timings.d.ts +1 -1
  232. package/dist/recall-xray-cli.d.ts +1 -1
  233. package/dist/recall-xray-renderer.d.ts +1 -1
  234. package/dist/recall-xray.d.ts +1 -1
  235. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  236. package/dist/replay/normalizers/claude.d.ts +1 -1
  237. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  238. package/dist/replay/normalizers/shared.d.ts +1 -1
  239. package/dist/replay/runner.d.ts +1 -1
  240. package/dist/replay/types.d.ts +1 -1
  241. package/dist/replica-divergence.d.ts +1 -1
  242. package/dist/replica-peers-config.d.ts +1 -1
  243. package/dist/resolve-auth-token.d.ts +1 -1
  244. package/dist/resume-bundles.js +4 -4
  245. package/dist/retrieval-agents.d.ts +2 -2
  246. package/dist/retrieval-tiers.d.ts +1 -1
  247. package/dist/routing/engine.d.ts +1 -1
  248. package/dist/routing/store.d.ts +1 -1
  249. package/dist/salvage-envelope.d.ts +1 -1
  250. package/dist/{scope-profiles-C4g6_UdP.d.ts → scope-profiles-CWDyglPM.d.ts} +1 -1
  251. package/dist/search/embed-helper.d.ts +1 -1
  252. package/dist/search/factory.d.ts +1 -1
  253. package/dist/search/index.d.ts +1 -1
  254. package/dist/search/lancedb-backend.d.ts +1 -1
  255. package/dist/search/meilisearch-backend.d.ts +1 -1
  256. package/dist/search/noop-backend.d.ts +1 -1
  257. package/dist/search/orama-backend.d.ts +1 -1
  258. package/dist/search/port.d.ts +1 -1
  259. package/dist/search/remote-backend.d.ts +1 -1
  260. package/dist/secure-store/index.js +2 -2
  261. package/dist/{semantic-consolidation-BrxqnzTW.d.ts → semantic-consolidation-BUE3deLn.d.ts} +1 -1
  262. package/dist/semantic-consolidation.d.ts +2 -2
  263. package/dist/semantic-consolidation.js +11 -10
  264. package/dist/semantic-rule-promotion.js +10 -9
  265. package/dist/semantic-rule-verifier.d.ts +1 -1
  266. package/dist/semantic-rule-verifier.js +10 -9
  267. package/dist/{service-Bi1JgxdP.d.ts → service-CXgM0bQG.d.ts} +2 -2
  268. package/dist/session-observer-bands.d.ts +1 -1
  269. package/dist/session-observer-state.d.ts +1 -1
  270. package/dist/shared-context/manager.d.ts +1 -1
  271. package/dist/signal.d.ts +1 -1
  272. package/dist/{storage-CcH6yPk2.d.ts → storage-DysJq-XH.d.ts} +163 -27
  273. package/dist/storage.d.ts +3 -2
  274. package/dist/storage.js +12 -10
  275. package/dist/summarizer.d.ts +1 -1
  276. package/dist/summarizer.js +3 -3
  277. package/dist/summary-snapshot.d.ts +1 -1
  278. package/dist/summary-snapshot.js +2 -2
  279. package/dist/temporal-supersession.d.ts +3 -2
  280. package/dist/temporal-timeline-recall.d.ts +1 -1
  281. package/dist/temporal-validity.d.ts +1 -1
  282. package/dist/threading.d.ts +1 -1
  283. package/dist/tier-migration.d.ts +3 -2
  284. package/dist/tier-routing.d.ts +1 -1
  285. package/dist/topics.d.ts +1 -1
  286. package/dist/transcript.d.ts +1 -1
  287. package/dist/trust-score-stage.d.ts +1 -1
  288. package/dist/trust-score.d.ts +1 -1
  289. package/dist/{types-Dg0w9ADr.d.ts → types-aPPzhnjT.d.ts} +1 -1
  290. package/dist/types.d.ts +1 -1
  291. package/dist/utility-runtime.d.ts +1 -1
  292. package/dist/utils/serialize-mutations.d.ts +11 -7
  293. package/dist/utils/serialize-mutations.js +1 -1
  294. package/dist/verified-recall.js +10 -9
  295. package/dist/write-envelope.d.ts +1 -1
  296. package/package.json +2 -2
  297. package/src/access-observe-write-surface.ts +1 -1
  298. package/src/access-service-coding-write.test.ts +40 -0
  299. package/src/explicit-capture.test.ts +1110 -1
  300. package/src/explicit-capture.ts +534 -135
  301. package/src/maintenance/purge.test.ts +157 -1
  302. package/src/maintenance/purge.ts +15 -9
  303. package/src/secure-store/secure-fs.ts +78 -1
  304. package/src/storage/content-hash-index.ts +43 -17
  305. package/src/storage/tombstone-blocked-capture-index.ts +562 -0
  306. package/src/storage/tombstone-blocked-capture-mutation.ts +284 -0
  307. package/src/storage/tombstone-blocked-capture-sync.ts +872 -0
  308. package/src/storage-fact-hash-index-defer.test.ts +2900 -57
  309. package/src/storage.ts +266 -265
  310. package/src/testing/subjects/tombstone-blocked-capture.test.ts +181 -0
  311. package/src/utils/serialize-mutations.test.ts +4 -1
  312. package/src/utils/serialize-mutations.ts +26 -27
  313. package/dist/chunk-55MDBZLC.js +0 -490
  314. package/dist/chunk-55MDBZLC.js.map +0 -1
  315. package/dist/chunk-AIK5W3IW.js.map +0 -1
  316. package/dist/chunk-JMQUBRV6.js.map +0 -1
  317. package/dist/chunk-WL5EBRDH.js.map +0 -1
  318. package/dist/purge-YSGX5BOG.js.map +0 -1
  319. /package/dist/{chunk-FZRQZW4H.js.map → chunk-2EUVEDF5.js.map} +0 -0
  320. /package/dist/{chunk-3JNDOAQ2.js.map → chunk-5W4ZLBEZ.js.map} +0 -0
  321. /package/dist/{chunk-5ZCUDWD4.js.map → chunk-62U4J2GN.js.map} +0 -0
  322. /package/dist/{chunk-IGPEJVBY.js.map → chunk-66772WBP.js.map} +0 -0
  323. /package/dist/{chunk-EP5BVCRI.js.map → chunk-6BNE62PM.js.map} +0 -0
  324. /package/dist/{chunk-AAP5T6QG.js.map → chunk-6DNMUYEE.js.map} +0 -0
  325. /package/dist/{chunk-35DL7IOI.js.map → chunk-6FUPCJUL.js.map} +0 -0
  326. /package/dist/{chunk-JP3YPDAW.js.map → chunk-6U7F52DE.js.map} +0 -0
  327. /package/dist/{chunk-E4OCKU67.js.map → chunk-7XC2HX75.js.map} +0 -0
  328. /package/dist/{chunk-OVRQLAYR.js.map → chunk-AZ5BTEKX.js.map} +0 -0
  329. /package/dist/{chunk-2AXMQWCN.js.map → chunk-CZRYLSSW.js.map} +0 -0
  330. /package/dist/{chunk-PTSNLEXK.js.map → chunk-FDAMS4PK.js.map} +0 -0
  331. /package/dist/{chunk-2GXM5N5A.js.map → chunk-GIBMDSJ2.js.map} +0 -0
  332. /package/dist/{chunk-E5OBR6CJ.js.map → chunk-H5CPS5JP.js.map} +0 -0
  333. /package/dist/{chunk-2D7FEUCK.js.map → chunk-I5NLEENJ.js.map} +0 -0
  334. /package/dist/{chunk-DRBXHRXE.js.map → chunk-IB26HVAJ.js.map} +0 -0
  335. /package/dist/{chunk-QDKK63Y7.js.map → chunk-IC6CBHCA.js.map} +0 -0
  336. /package/dist/{chunk-IAEAFLF2.js.map → chunk-JCBWCYEJ.js.map} +0 -0
  337. /package/dist/{chunk-3PDOVUHC.js.map → chunk-KXB4VXHY.js.map} +0 -0
  338. /package/dist/{chunk-WU7TTVOD.js.map → chunk-LBOD4U5B.js.map} +0 -0
  339. /package/dist/{chunk-UBKEQOTD.js.map → chunk-LGHKJABJ.js.map} +0 -0
  340. /package/dist/{chunk-VSURVXKY.js.map → chunk-M3C2V3PV.js.map} +0 -0
  341. /package/dist/{chunk-OFHFH655.js.map → chunk-MJCGLM25.js.map} +0 -0
  342. /package/dist/{chunk-OYJKSZIQ.js.map → chunk-NHWB6HG2.js.map} +0 -0
  343. /package/dist/{chunk-TC3VNKGX.js.map → chunk-NM2WP3B7.js.map} +0 -0
  344. /package/dist/{chunk-GAWOEQEQ.js.map → chunk-P5PXWTW7.js.map} +0 -0
  345. /package/dist/{chunk-2QMKMVHH.js.map → chunk-QNWXCJ76.js.map} +0 -0
  346. /package/dist/{chunk-WRFBW2TT.js.map → chunk-SPJXKAVF.js.map} +0 -0
  347. /package/dist/{chunk-HKJCMOT6.js.map → chunk-TH2LSRMG.js.map} +0 -0
  348. /package/dist/{chunk-CW4AGY3G.js.map → chunk-VI2JQPNQ.js.map} +0 -0
  349. /package/dist/{chunk-OP6F6L3S.js.map → chunk-W5EG6LE3.js.map} +0 -0
  350. /package/dist/{chunk-DEBHXHJR.js.map → chunk-WIPEXG6R.js.map} +0 -0
  351. /package/dist/{chunk-SH6YO3LT.js.map → chunk-WVO4G37W.js.map} +0 -0
  352. /package/dist/{chunk-7L45JFPW.js.map → chunk-YTQNQ4KB.js.map} +0 -0
  353. /package/dist/{chunk-PKRIQL3V.js.map → chunk-YTSNAUXN.js.map} +0 -0
  354. /package/dist/{chunk-5QDRGWT2.js.map → chunk-ZPSVBSOF.js.map} +0 -0
  355. /package/dist/{forget-HRPLVB3W.js.map → forget-4UY2EOKH.js.map} +0 -0
@@ -1,11 +1,17 @@
1
1
  import { composeMemoryEnvelope, TAG_LIMITS } from "./write-envelope.js";
2
2
  import { resolveNamespaceCapabilities } from "./capabilities.js";
3
- import { randomUUID } from "node:crypto";
3
+ import { createHash, randomUUID } from "node:crypto";
4
4
  import type { Orchestrator } from "./orchestrator.js";
5
+ import { log } from "./logger.js";
5
6
  import { isSafeRouteNamespace } from "./routing/engine.js";
6
7
  import { sanitizeMemoryContent } from "./sanitize.js";
7
- import { ContentHashIndex } from "./storage.js";
8
8
  import type { CaptureMode, MemoryCategory, MemoryLifecycleEvent, PluginConfig } from "./types.js";
9
+ import {
10
+ buildExplicitCaptureDedupKey,
11
+ isTombstoneBlockedCaptureWriteLockBusy,
12
+ normalizeExplicitCaptureContent,
13
+ } from "./storage/tombstone-blocked-capture-index.js";
14
+ import type { StorageManager } from "./storage.js";
9
15
 
10
16
  export type ExplicitCaptureInput = {
11
17
  content: string;
@@ -107,13 +113,6 @@ function asTrimmed(value: string | undefined): string | undefined {
107
113
  return trimmed && trimmed.length > 0 ? trimmed : undefined;
108
114
  }
109
115
 
110
- function normalizeCaptureContent(value: string): string {
111
- return value
112
- .toLowerCase()
113
- .replace(/\s+/g, " ")
114
- .trim();
115
- }
116
-
117
116
  function redactSecrets(value: string): string {
118
117
  let redacted = value;
119
118
  for (const { pattern, replacement } of SECRET_REDACTION_PATTERNS) {
@@ -153,9 +152,13 @@ function sanitizeReviewMetadata(value: string | undefined): string | undefined {
153
152
  }
154
153
 
155
154
  function sanitizeReviewTags(tags: string[] | undefined): string[] {
156
- return Array.from(new Set((tags ?? [])
157
- .map((tag) => sanitizeReviewMetadata(tag))
158
- .filter((tag): tag is string => typeof tag === "string" && tag.length > 0)));
155
+ return Array.from(
156
+ new Set(
157
+ (tags ?? [])
158
+ .map((tag) => sanitizeReviewMetadata(tag))
159
+ .filter((tag): tag is string => typeof tag === "string" && tag.length > 0)
160
+ )
161
+ );
159
162
  }
160
163
 
161
164
  function normalizeExplicitCaptureError(error: unknown): string {
@@ -164,9 +167,13 @@ function normalizeExplicitCaptureError(error: unknown): string {
164
167
  return rendered.length > 0 ? rendered : "explicit capture failed";
165
168
  }
166
169
 
170
+ function isUnsupportedExplicitCaptureNamespaceError(error: unknown): boolean {
171
+ return normalizeExplicitCaptureError(error).startsWith("unsupported namespace:");
172
+ }
173
+
167
174
  function resolveExplicitCaptureReviewNamespace(
168
175
  orchestrator: Orchestrator,
169
- namespace: string | undefined,
176
+ namespace: string | undefined
170
177
  ): string | undefined {
171
178
  const normalized = asTrimmed(namespace);
172
179
  if (!normalized) return undefined;
@@ -175,7 +182,7 @@ function resolveExplicitCaptureReviewNamespace(
175
182
 
176
183
  function resolveExplicitCaptureNamespace(
177
184
  orchestrator: Orchestrator,
178
- namespace: string | undefined,
185
+ namespace: string | undefined
179
186
  ): string | undefined {
180
187
  const normalized = asTrimmed(namespace);
181
188
  if (!normalized) return undefined;
@@ -185,11 +192,15 @@ function resolveExplicitCaptureNamespace(
185
192
  }
186
193
  return normalized;
187
194
  }
188
- const allowed = new Set([
189
- orchestrator.config.defaultNamespace,
190
- orchestrator.config.sharedNamespace,
191
- ...orchestrator.config.namespacePolicies.map((policy) => policy.name),
192
- ].map((value) => value.trim()).filter(Boolean));
195
+ const allowed = new Set(
196
+ [
197
+ orchestrator.config.defaultNamespace,
198
+ orchestrator.config.sharedNamespace,
199
+ ...orchestrator.config.namespacePolicies.map((policy) => policy.name),
200
+ ]
201
+ .map((value) => value.trim())
202
+ .filter(Boolean)
203
+ );
193
204
  if (!allowed.has(normalized)) {
194
205
  throw new Error(`unsupported namespace: ${normalized}`);
195
206
  }
@@ -217,10 +228,7 @@ function parseExplicitCaptureTtl(ttl: string | undefined): string | undefined {
217
228
  }
218
229
 
219
230
  const multiplier =
220
- unit === "m" ? 60_000
221
- : unit === "h" ? 60 * 60_000
222
- : unit === "d" ? 24 * 60 * 60_000
223
- : 7 * 24 * 60 * 60_000;
231
+ unit === "m" ? 60_000 : unit === "h" ? 60 * 60_000 : unit === "d" ? 24 * 60 * 60_000 : 7 * 24 * 60 * 60_000;
224
232
  return new Date(Date.now() + amount * multiplier).toISOString();
225
233
  }
226
234
 
@@ -233,7 +241,7 @@ function parseInlineConfidence(value: string): number {
233
241
  return Number.isFinite(parsed) ? parsed : Number.NaN;
234
242
  }
235
243
 
236
- function parseInlineNote(block: string): ExplicitCaptureInput | null {
244
+ function parseInlineNote(block: string): ExplicitCaptureInput {
237
245
  const lines = block.replace(/\r/g, "").split("\n");
238
246
  const note: Partial<ExplicitCaptureInput> = {};
239
247
  let idx = 0;
@@ -301,18 +309,21 @@ function parseInlineNote(block: string): ExplicitCaptureInput | null {
301
309
  }
302
310
  }
303
311
 
304
- return asTrimmed(note.content) ? (note as ExplicitCaptureInput) : null;
312
+ return { content: "", ...note };
305
313
  }
306
314
 
307
- export function parseInlineExplicitCaptureNotes(text: string): ExplicitCaptureInput[] {
315
+ function parseInlineExplicitCaptureCandidates(text: string): ExplicitCaptureInput[] {
308
316
  const notes: ExplicitCaptureInput[] = [];
309
317
  for (const match of text.matchAll(INLINE_NOTE_RE)) {
310
- const parsed = parseInlineNote(match[1] ?? "");
311
- if (parsed) notes.push(parsed);
318
+ notes.push(parseInlineNote(match[1] ?? ""));
312
319
  }
313
320
  return notes;
314
321
  }
315
322
 
323
+ export function parseInlineExplicitCaptureNotes(text: string): ExplicitCaptureInput[] {
324
+ return parseInlineExplicitCaptureCandidates(text).filter((note) => asTrimmed(note.content));
325
+ }
326
+
316
327
  export function hasInlineExplicitCaptureMarkup(text: string): boolean {
317
328
  return INLINE_NOTE_MARKUP_RE.test(text);
318
329
  }
@@ -323,7 +334,7 @@ export function stripInlineExplicitCaptureNotes(text: string): string {
323
334
 
324
335
  export function validateExplicitCaptureInput(
325
336
  input: ExplicitCaptureInput,
326
- mode: ExplicitCaptureValidationMode = "strict_explicit",
337
+ mode: ExplicitCaptureValidationMode = "strict_explicit"
327
338
  ): ValidExplicitCapture {
328
339
  const content = asTrimmed(input.content);
329
340
  if (!content) throw new Error("content is required");
@@ -400,37 +411,81 @@ async function findDuplicateExplicitCapture(
400
411
  orchestrator: Orchestrator,
401
412
  resolvedNamespace: string | undefined,
402
413
  candidate: ValidExplicitCapture,
403
- ): Promise<string | null> {
404
- const storage = await orchestrator.getStorage(resolvedNamespace);
405
- if (
406
- candidate.category === "fact"
407
- && typeof (storage as { hasFactContentHash?: (content: string) => Promise<boolean> }).hasFactContentHash === "function"
408
- ) {
414
+ storageOverride?: StorageManager
415
+ ): Promise<{ id: string; tombstoneBlocked: boolean } | null> {
416
+ const storage = storageOverride ?? (await orchestrator.getStorage(resolvedNamespace));
417
+ // Tombstone-blocked rows are absent from the active fact hash index. An
418
+ // authoritative miss can therefore skip the corpus scan only after the
419
+ // targeted blocked-row index reports a miss and remains authoritative.
420
+ let authoritativeFactHashMiss = false;
421
+ if (candidate.category === "fact" && typeof storage.hasFactContentHash === "function") {
409
422
  try {
410
- const hasHash = await (storage as { hasFactContentHash: (content: string) => Promise<boolean> }).hasFactContentHash(
411
- candidate.content,
412
- );
423
+ const hasHash = await storage.hasFactContentHash(candidate.content);
413
424
  if (!hasHash) {
414
425
  const authoritative =
415
- typeof (storage as { isFactContentHashAuthoritative?: () => Promise<boolean> | boolean }).isFactContentHashAuthoritative
416
- === "function"
417
- ? await (storage as { isFactContentHashAuthoritative: () => Promise<boolean> | boolean })
418
- .isFactContentHashAuthoritative()
426
+ typeof storage.isFactContentHashAuthoritative === "function"
427
+ ? await storage.isFactContentHashAuthoritative()
419
428
  : false;
420
- if (authoritative) return null;
429
+ authoritativeFactHashMiss = authoritative;
421
430
  }
422
431
  } catch (err) {
423
- // Fail open: hash index is only an optimization, so fall back to the full corpus scan.
432
+ // Fail open: hash indexes are only an optimization, so fall back to the
433
+ // full corpus scan when either index cannot answer.
424
434
  void err;
425
435
  }
426
436
  }
427
- const existing = await storage.readAllMemories();
428
- const normalizedCandidate = normalizeCaptureContent(candidate.content);
437
+ if (authoritativeFactHashMiss) {
438
+ const checkBlockedIndex = storage.checkTombstoneBlockedExplicitCapture;
439
+ if (typeof checkBlockedIndex === "function") {
440
+ try {
441
+ const result = await checkBlockedIndex.call(
442
+ storage,
443
+ candidate.content,
444
+ candidate.category,
445
+ candidate.sourceConnector
446
+ );
447
+ if (!result.has && result.authoritative) return null;
448
+ } catch (err) {
449
+ // Fail open: an unavailable targeted index must not hide a durable
450
+ // pending-review duplicate.
451
+ void err;
452
+ }
453
+ } else {
454
+ const hasBlockedIndex = storage.hasTombstoneBlockedExplicitCapture;
455
+ if (typeof hasBlockedIndex === "function") {
456
+ try {
457
+ const hasBlocked = await hasBlockedIndex.call(
458
+ storage,
459
+ candidate.content,
460
+ candidate.category,
461
+ candidate.sourceConnector
462
+ );
463
+ if (!hasBlocked) {
464
+ const isIndexAuthoritative =
465
+ typeof storage.isTombstoneBlockedExplicitCaptureIndexAuthoritative === "function"
466
+ ? await storage.isTombstoneBlockedExplicitCaptureIndexAuthoritative()
467
+ : false;
468
+ if (isIndexAuthoritative) return null;
469
+ }
470
+ } catch (err) {
471
+ // Fail open: an unavailable targeted index must not hide a durable
472
+ // pending-review duplicate.
473
+ void err;
474
+ }
475
+ }
476
+ }
477
+ }
478
+ const hotMemories = await storage.readAllMemories();
479
+ const coldMemories = typeof storage.readAllColdMemories === "function" ? await storage.readAllColdMemories() : [];
480
+ const existing = [...hotMemories, ...coldMemories];
481
+ const normalizedCandidate = normalizeExplicitCaptureContent(candidate.content);
429
482
  const match = existing.find((memory) => {
430
483
  const status = memory.frontmatter.status ?? "active";
431
- if (status !== "active") return false;
484
+ if (status !== "active" && (status !== "pending_review" || !memory.frontmatter.blockedBy)) {
485
+ return false;
486
+ }
432
487
  if (memory.frontmatter.category !== candidate.category) return false;
433
- if (normalizeCaptureContent(memory.content) !== normalizedCandidate) return false;
488
+ if (normalizeExplicitCaptureContent(memory.content) !== normalizedCandidate) return false;
434
489
  // Connector-aware dedup: same content from different connectors is NOT
435
490
  // a duplicate. Operator (undefined) and connector-tagged memories are
436
491
  // always distinct. Normalize empty strings to undefined.
@@ -439,23 +494,27 @@ async function findDuplicateExplicitCapture(
439
494
  if (existingConnector !== newConnector) return false;
440
495
  return true;
441
496
  });
442
- return match?.frontmatter.id ?? null;
497
+ return match
498
+ ? {
499
+ id: match.frontmatter.id,
500
+ tombstoneBlocked: match.frontmatter.status === "pending_review" && Boolean(match.frontmatter.blockedBy),
501
+ }
502
+ : null;
443
503
  }
444
-
445
- export async function persistExplicitCapture(
504
+ async function persistExplicitCaptureUnlocked(
446
505
  orchestrator: Orchestrator,
447
506
  candidate: ValidExplicitCapture,
448
507
  source: ExplicitCaptureSource,
508
+ resolvedNamespace: string | undefined,
509
+ storage: StorageManager
449
510
  ): Promise<{ id: string; duplicateOf?: string; tombstoneBlocked?: boolean }> {
450
- const resolvedNamespace = candidate.namespacePreResolved
451
- ? asTrimmed(candidate.namespace)
452
- : resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);
453
- const duplicateOf = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate);
454
- if (duplicateOf) {
455
- return { id: duplicateOf, duplicateOf };
511
+ const duplicate = await findDuplicateExplicitCapture(orchestrator, resolvedNamespace, candidate, storage);
512
+ if (duplicate) {
513
+ return duplicate.tombstoneBlocked
514
+ ? { id: duplicate.id, duplicateOf: duplicate.id, tombstoneBlocked: true }
515
+ : { id: duplicate.id, duplicateOf: duplicate.id };
456
516
  }
457
517
 
458
- const storage = await orchestrator.getStorage(resolvedNamespace);
459
518
  // #1645 (review thread yG-): surface the tombstone block so callers
460
519
  // (memory_store tool, access-service HTTP/MCP) can report the capture as
461
520
  // queued for review instead of a successfully stored active memory.
@@ -471,9 +530,10 @@ export async function persistExplicitCapture(
471
530
  ...(candidate.sourceConnector ? { sourceConnector: candidate.sourceConnector } : {}),
472
531
  ...(candidate.sourceReason ? { sourceReason: candidate.sourceReason } : {}),
473
532
  },
474
- { source: source === "inline" ? "explicit-inline" : "explicit" },
533
+ { source: source === "inline" ? "explicit-inline" : "explicit" }
475
534
  );
476
- const { id, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
535
+ const { id, duplicateOf, tombstoneBlocked } = await storage.writeSealedMemory(captureEnvelope);
536
+ if (duplicateOf) return { id, duplicateOf, tombstoneBlocked };
477
537
  // #1522: catalog touch handled at the storage chokepoint — the StorageManager's
478
538
  // post-write hook records the namespace touch automatically.
479
539
 
@@ -481,7 +541,7 @@ export async function persistExplicitCapture(
481
541
  const event: MemoryLifecycleEvent = {
482
542
  eventId: `mle-${randomUUID()}`,
483
543
  memoryId: id,
484
- eventType: "explicit_capture_accepted",
544
+ eventType: tombstoneBlocked ? "explicit_capture_queued" : "explicit_capture_accepted",
485
545
  timestamp: created,
486
546
  actor: explicitCaptureActor(source),
487
547
  reasonCode: candidate.sourceReason,
@@ -489,7 +549,22 @@ export async function persistExplicitCapture(
489
549
  };
490
550
  await storage.appendMemoryLifecycleEvents([event]);
491
551
 
492
- return { id, tombstoneBlocked };
552
+ return { id, duplicateOf, tombstoneBlocked };
553
+ }
554
+
555
+ export async function persistExplicitCapture(
556
+ orchestrator: Orchestrator,
557
+ candidate: ValidExplicitCapture,
558
+ source: ExplicitCaptureSource
559
+ ): Promise<{ id: string; duplicateOf?: string; tombstoneBlocked?: boolean }> {
560
+ const resolvedNamespace = candidate.namespacePreResolved
561
+ ? asTrimmed(candidate.namespace)
562
+ : resolveExplicitCaptureNamespace(orchestrator, candidate.namespace);
563
+ const storage = await orchestrator.getStorage(resolvedNamespace);
564
+ const persist = () => persistExplicitCaptureUnlocked(orchestrator, candidate, source, resolvedNamespace, storage);
565
+ if (typeof storage.withTombstoneBlockedCaptureWriteLock !== "function") return await persist();
566
+ const lockIdentity = buildExplicitCaptureDedupKey(candidate.content, candidate.category, candidate.sourceConnector);
567
+ return await storage.withTombstoneBlockedCaptureWriteLock(persist, lockIdentity);
493
568
  }
494
569
 
495
570
  function buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason: string): string {
@@ -501,14 +576,7 @@ function buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason:
501
576
  const safeTtl = sanitizeReviewMetadata(input.ttl);
502
577
  const safeSourceReason = sanitizeReviewMetadata(input.sourceReason);
503
578
  const safeTags = sanitizeReviewTags(input.tags);
504
- const lines = [
505
- "Explicit capture queued for review.",
506
- "",
507
- `Reason: ${reason}`,
508
- "",
509
- "Submitted content:",
510
- safeContent,
511
- ];
579
+ const lines = ["Explicit capture queued for review.", "", `Reason: ${reason}`, "", "Submitted content:", safeContent];
512
580
  const metadata = [
513
581
  safeCategory ? `Requested category: ${safeCategory}` : undefined,
514
582
  safeNamespace ? `Requested namespace: ${safeNamespace}` : undefined,
@@ -523,20 +591,43 @@ function buildExplicitCaptureReviewContent(input: ExplicitCaptureInput, reason:
523
591
  return lines.join("\n");
524
592
  }
525
593
 
594
+ function explicitCaptureReviewNamespaces(
595
+ orchestrator: Orchestrator,
596
+ queueNamespace: string | undefined
597
+ ): Array<string | undefined> {
598
+ const config = orchestrator.config;
599
+ return [asTrimmed(queueNamespace) ?? asTrimmed(config?.defaultNamespace)];
600
+ }
601
+
526
602
  async function findQueuedExplicitCaptureDuplicate(
527
603
  orchestrator: Orchestrator,
528
604
  namespace: string | undefined,
529
605
  content: string,
530
- sourceConnector?: string,
606
+ sourceConnector?: string
531
607
  ): Promise<string | null> {
532
- const storage = await orchestrator.getStorage(namespace);
533
- const existing = await storage.readAllMemories();
534
- const normalized = normalizeCaptureContent(content);
608
+ const storages = Array.from(
609
+ new Set(
610
+ await Promise.all(
611
+ explicitCaptureReviewNamespaces(orchestrator, namespace).map((reviewNamespace) =>
612
+ orchestrator.getStorage(reviewNamespace)
613
+ )
614
+ )
615
+ )
616
+ );
617
+ const memorySets = await Promise.all(
618
+ storages.map(async (storage) => {
619
+ const hotMemories = await storage.readAllMemories();
620
+ const coldMemories = typeof storage.readAllColdMemories === "function" ? await storage.readAllColdMemories() : [];
621
+ return [...hotMemories, ...coldMemories];
622
+ })
623
+ );
624
+ const existing = memorySets.flat();
625
+ const normalized = normalizeExplicitCaptureContent(content);
535
626
  const match = existing.find((memory) => {
536
627
  const status = memory.frontmatter.status ?? "active";
537
628
  if (status !== "pending_review") return false;
538
629
  if (!(memory.frontmatter.tags ?? []).includes("queued-review")) return false;
539
- if (normalizeCaptureContent(memory.content) !== normalized) return false;
630
+ if (normalizeExplicitCaptureContent(memory.content) !== normalized) return false;
540
631
  const existingConnector = memory.frontmatter.sourceConnector?.trim() || undefined;
541
632
  const newConnector = sourceConnector?.trim() || undefined;
542
633
  if (existingConnector !== newConnector) return false;
@@ -545,84 +636,108 @@ async function findQueuedExplicitCaptureDuplicate(
545
636
  return match?.frontmatter.id ?? null;
546
637
  }
547
638
 
639
+ export type ExplicitCaptureReviewQueueOptions = {
640
+ /** A server-authorized namespace resolved from the authenticated session. */
641
+ resolvedNamespace?: string;
642
+ };
643
+
548
644
  export async function queueExplicitCaptureForReview(
549
645
  orchestrator: Orchestrator,
550
646
  input: ExplicitCaptureInput,
551
647
  source: ExplicitCaptureSource,
552
648
  error: unknown,
649
+ options: ExplicitCaptureReviewQueueOptions = {}
553
650
  ): Promise<{ id: string; duplicateOf?: string }> {
554
651
  const reason = sanitizeReviewText(normalizeExplicitCaptureError(error), "explicit capture failed");
555
652
  const requestedNamespace = asTrimmed(input.namespace);
653
+ const resolvedNamespace = asTrimmed(options.resolvedNamespace);
556
654
  // A caller-pre-authorized namespace (e.g. a session-owned project overlay
557
655
  // from the access service) routes directly; otherwise apply the static
558
656
  // policy allow-list guard (#1434).
559
- const queueNamespace = (input as { namespacePreResolved?: boolean }).namespacePreResolved
560
- ? requestedNamespace
561
- : resolveExplicitCaptureReviewNamespace(orchestrator, requestedNamespace);
657
+ const queueNamespace =
658
+ resolvedNamespace ??
659
+ ((input as { namespacePreResolved?: boolean }).namespacePreResolved
660
+ ? requestedNamespace
661
+ : resolveExplicitCaptureReviewNamespace(orchestrator, requestedNamespace));
562
662
  const content = buildExplicitCaptureReviewContent(input, reason);
563
- const duplicateOf = await findQueuedExplicitCaptureDuplicate(orchestrator, queueNamespace, content, input.sourceConnector);
564
- if (duplicateOf) {
565
- return { id: duplicateOf, duplicateOf };
566
- }
567
-
568
663
  const requestedCategory = asTrimmed(input.category);
569
- const reviewCategory = requestedCategory && INLINE_ALLOWED_CATEGORIES.has(requestedCategory as MemoryCategory)
570
- ? requestedCategory as MemoryCategory
571
- : "fact";
664
+ const reviewCategory =
665
+ requestedCategory && INLINE_ALLOWED_CATEGORIES.has(requestedCategory as MemoryCategory)
666
+ ? (requestedCategory as MemoryCategory)
667
+ : "fact";
572
668
  const requestedTags = sanitizeReviewTags(input.tags);
573
669
  const storage = await orchestrator.getStorage(queueNamespace);
574
- const reviewEnvelope = composeMemoryEnvelope(
575
- {
670
+ const lockIdentity = buildExplicitCaptureDedupKey(content, reviewCategory, input.sourceConnector);
671
+ const queue = async (): Promise<{ id: string; duplicateOf?: string }> => {
672
+ const duplicateOf = await findQueuedExplicitCaptureDuplicate(
673
+ orchestrator,
674
+ queueNamespace,
576
675
  content,
577
- category: reviewCategory,
578
- confidence: 0.2,
579
- tags: Array.from(new Set([...EXPLICIT_CAPTURE_REVIEW_TAGS, ...requestedTags])),
580
- ...(sanitizeReviewMetadata(input.entityRef) ? { entityRef: sanitizeReviewMetadata(input.entityRef) } : {}),
581
- ...(input.sourceConnector ? { sourceConnector: input.sourceConnector } : {}),
582
- },
583
- { source: source === "inline" ? "explicit-inline-review" : "explicit-review" },
584
- // The review queue is the FALLBACK for a capture that already failed
585
- // primary validation — it must never be un-queueable. The tag list is
586
- // machine-assembled (requested tags + the two fixed review tags), so 49+
587
- // requested tags would push past the 50-tag limit and strict compose
588
- // would throw, losing the capture (#2014 review round). Salvage clamps
589
- // instead; the fixed review tags sort first in the assembly above, so
590
- // they always survive the clamp.
591
- { salvage: true },
592
- );
593
- const { id: id } = await storage.writeSealedMemory(reviewEnvelope);
594
- try {
595
- const created = await storage.getMemoryById(id);
596
- if (created) {
597
- await storage.writeMemoryFrontmatter(created, {
598
- status: "pending_review",
599
- updated: new Date().toISOString(),
600
- }, {
601
- actor: explicitCaptureActor(source),
602
- reasonCode: reason,
603
- ruleVersion: "explicit-capture.v1",
604
- });
676
+ input.sourceConnector
677
+ );
678
+ if (duplicateOf) {
679
+ return { id: duplicateOf, duplicateOf };
605
680
  }
606
- } finally {
607
- // Record the catalog write touch (issue #1499, round 5/6 codex P2; NIhUg).
608
- // A queued review capture writes memory to the namespace's root (the DEFAULT
609
- // root when undefined), so its `lastWriteAt` must reflect the write once
610
- // `writeMemory` returns an id. If the later pending-review frontmatter update
611
- // fails, the memory file is still durable and must not disappear from
612
- // writtenSince/maintenance scheduling. Guarded optional hook (rule #33).
613
- // #1522: catalog touch handled at the storage chokepoint.
614
- }
615
- const event: MemoryLifecycleEvent = {
616
- eventId: `mle-${randomUUID()}`,
617
- memoryId: id,
618
- eventType: "explicit_capture_queued",
619
- timestamp: new Date().toISOString(),
620
- actor: explicitCaptureActor(source),
621
- reasonCode: reason,
622
- ruleVersion: "explicit-capture.v1",
681
+
682
+ const reviewEnvelope = composeMemoryEnvelope(
683
+ {
684
+ content,
685
+ category: reviewCategory,
686
+ confidence: 0.2,
687
+ tags: Array.from(new Set([...EXPLICIT_CAPTURE_REVIEW_TAGS, ...requestedTags])),
688
+ ...(sanitizeReviewMetadata(input.entityRef) ? { entityRef: sanitizeReviewMetadata(input.entityRef) } : {}),
689
+ ...(input.sourceConnector ? { sourceConnector: input.sourceConnector } : {}),
690
+ },
691
+ { source: source === "inline" ? "explicit-inline-review" : "explicit-review" },
692
+ // The review queue is the FALLBACK for a capture that already failed
693
+ // primary validation — it must never be un-queueable. The tag list is
694
+ // machine-assembled (requested tags + the two fixed review tags), so 49+
695
+ // requested tags would push past the 50-tag limit and strict compose
696
+ // would throw, losing the capture (#2014 review round). Salvage clamps
697
+ // instead; the fixed review tags sort first in the assembly above, so
698
+ // they always survive the clamp.
699
+ { salvage: true }
700
+ );
701
+ const { id } = await storage.writeSealedMemory(reviewEnvelope);
702
+ try {
703
+ const created = await storage.getMemoryById(id);
704
+ if (created) {
705
+ await storage.writeMemoryFrontmatter(
706
+ created,
707
+ {
708
+ status: "pending_review",
709
+ updated: new Date().toISOString(),
710
+ },
711
+ {
712
+ actor: explicitCaptureActor(source),
713
+ reasonCode: reason,
714
+ ruleVersion: "explicit-capture.v1",
715
+ }
716
+ );
717
+ }
718
+ } finally {
719
+ // Record the catalog write touch (issue #1499, round 5/6 codex P2; NIhUg).
720
+ // A queued review capture writes memory to the namespace's root (the DEFAULT
721
+ // root when undefined), so its `lastWriteAt` must reflect the write once
722
+ // `writeMemory` returns an id. If the later pending-review frontmatter update
723
+ // fails, the memory file is still durable and must not disappear from
724
+ // writtenSince/maintenance scheduling. Guarded optional hook (rule #33).
725
+ // #1522: catalog touch handled at the storage chokepoint.
726
+ }
727
+ const event: MemoryLifecycleEvent = {
728
+ eventId: `mle-${randomUUID()}`,
729
+ memoryId: id,
730
+ eventType: "explicit_capture_queued",
731
+ timestamp: new Date().toISOString(),
732
+ actor: explicitCaptureActor(source),
733
+ reasonCode: reason,
734
+ ruleVersion: "explicit-capture.v1",
735
+ };
736
+ await storage.appendMemoryLifecycleEvents([event]);
737
+ return { id };
623
738
  };
624
- await storage.appendMemoryLifecycleEvents([event]);
625
- return { id };
739
+ if (typeof storage.withTombstoneBlockedCaptureWriteLock !== "function") return await queue();
740
+ return await storage.withTombstoneBlockedCaptureWriteLock(queue, lockIdentity);
626
741
  }
627
742
 
628
743
  export function shouldSkipImplicitExtraction(cfg: Pick<PluginConfig, "captureMode">): boolean {
@@ -632,3 +747,287 @@ export function shouldSkipImplicitExtraction(cfg: Pick<PluginConfig, "captureMod
632
747
  export function shouldProcessInlineExplicitCapture(cfg: Pick<PluginConfig, "captureMode">): boolean {
633
748
  return cfg.captureMode !== "implicit";
634
749
  }
750
+
751
+ export interface InlineExplicitCaptureProcessRequest {
752
+ content: string;
753
+ captureMode: CaptureMode;
754
+ dedupeKeys?: readonly string[];
755
+ namespace?: string;
756
+ namespacePreResolved?: boolean;
757
+ reviewNamespace?: string;
758
+ reviewNamespacePreResolved?: boolean;
759
+ sourceConnector?: string;
760
+ }
761
+
762
+ export interface InlineExplicitCaptureProcessResult {
763
+ content: string;
764
+ processed: number;
765
+ accepted: number;
766
+ queued: number;
767
+ duplicates: number;
768
+ failed: number;
769
+ }
770
+
771
+ export interface InlineExplicitCaptureProcessorOptions {
772
+ sourceConnector?: string;
773
+ maxDedupeKeys?: number;
774
+ }
775
+
776
+ const DEFAULT_INLINE_CAPTURE_DEDUPE_KEY_LIMIT = 1024;
777
+
778
+ export class InlineExplicitCaptureProcessor {
779
+ private readonly observedKeys = new Set<string>();
780
+ private readonly observedKeyOrder: string[] = [];
781
+ private readonly maxDedupeKeys: number;
782
+ private readonly processingTails = new Map<string, Promise<void>>();
783
+
784
+ constructor(
785
+ private readonly orchestrator: Orchestrator,
786
+ private readonly options: InlineExplicitCaptureProcessorOptions = {}
787
+ ) {
788
+ const requestedLimit = options.maxDedupeKeys ?? DEFAULT_INLINE_CAPTURE_DEDUPE_KEY_LIMIT;
789
+ this.maxDedupeKeys =
790
+ Number.isFinite(requestedLimit) && requestedLimit > 0
791
+ ? Math.max(1, Math.floor(requestedLimit))
792
+ : DEFAULT_INLINE_CAPTURE_DEDUPE_KEY_LIMIT;
793
+ }
794
+
795
+ private enqueue<T>(key: string, operation: () => Promise<T>): Promise<T> {
796
+ const previous = this.processingTails.get(key) ?? Promise.resolve();
797
+ let release!: () => void;
798
+ const current = new Promise<void>((resolve) => {
799
+ release = resolve;
800
+ });
801
+ this.processingTails.set(key, current);
802
+ return previous.then(operation).finally(() => {
803
+ if (this.processingTails.get(key) === current) this.processingTails.delete(key);
804
+ release();
805
+ });
806
+ }
807
+
808
+ private buildDedupeKeys(
809
+ dedupeKeys: readonly string[] | undefined,
810
+ input: ExplicitCaptureInput,
811
+ namespacePreResolved: boolean,
812
+ fallbackReviewNamespace?: string
813
+ ): { replayKeys: string[]; validationFailureKey: string } {
814
+ let fallbackNamespace: string | undefined;
815
+ let effectiveNamespace = asTrimmed(input.namespace);
816
+ if (!namespacePreResolved) {
817
+ try {
818
+ effectiveNamespace = resolveExplicitCaptureNamespace(this.orchestrator, effectiveNamespace);
819
+ } catch {
820
+ fallbackNamespace = asTrimmed(fallbackReviewNamespace) ?? this.orchestrator.config.defaultNamespace;
821
+ effectiveNamespace = fallbackNamespace;
822
+ }
823
+ }
824
+ const requestedNamespace = asTrimmed(input.namespace);
825
+ const namespaceScope =
826
+ fallbackNamespace === undefined
827
+ ? effectiveNamespace ?? this.orchestrator.config.defaultNamespace
828
+ : `${fallbackNamespace}\u0000requested:${requestedNamespace ?? ""}`;
829
+ const sourceConnectorScope = asTrimmed(input.sourceConnector) ?? "";
830
+ const deliveryKeys = Array.from(
831
+ new Set((dedupeKeys ?? []).map((key) => asTrimmed(key)).filter((key): key is string => key !== undefined))
832
+ ).sort();
833
+ const noteHash = createHash("sha256")
834
+ .update(
835
+ JSON.stringify({
836
+ content: normalizeExplicitCaptureContent(input.content),
837
+ category: asTrimmed(input.category) ?? "fact",
838
+ })
839
+ )
840
+ .digest("hex");
841
+ const validationTags = Array.from(
842
+ new Set((input.tags ?? []).map((tag) => asTrimmed(tag)).filter((tag): tag is string => tag !== undefined))
843
+ ).sort();
844
+ const validationHash = createHash("sha256")
845
+ .update(
846
+ JSON.stringify({
847
+ content: asTrimmed(input.content) ?? "",
848
+ category: asTrimmed(input.category) ?? "fact",
849
+ confidence:
850
+ input.confidence === undefined
851
+ ? "default"
852
+ : Number.isFinite(input.confidence)
853
+ ? input.confidence
854
+ : "invalid",
855
+ namespace: asTrimmed(input.namespace),
856
+ tags: validationTags,
857
+ entityRef: asTrimmed(input.entityRef),
858
+ sourceReason: asTrimmed(input.sourceReason),
859
+ ttl: asTrimmed(input.ttl),
860
+ deliveryKeys,
861
+ })
862
+ )
863
+ .digest("hex");
864
+ const identityPrefix = `${namespaceScope}\u0000${sourceConnectorScope}\u0000`;
865
+ const fallbackKey = `${identityPrefix}fallback:inline-memory-note:${noteHash}`;
866
+ return {
867
+ replayKeys:
868
+ deliveryKeys.length > 0
869
+ ? deliveryKeys.map((key) => `${identityPrefix}${key}:inline-memory-note:${noteHash}`)
870
+ : [fallbackKey],
871
+ validationFailureKey: `${identityPrefix}validation:inline-memory-note:${validationHash}`,
872
+ };
873
+ }
874
+
875
+ private isNamespacePolicyAuthorized(namespace: string | undefined): boolean {
876
+ try {
877
+ resolveExplicitCaptureNamespace(this.orchestrator, namespace);
878
+ return true;
879
+ } catch {
880
+ return false;
881
+ }
882
+ }
883
+
884
+ private resolveReviewFallbackNamespace(
885
+ request: InlineExplicitCaptureProcessRequest,
886
+ input: ExplicitCaptureInput
887
+ ): string | undefined {
888
+ if (request.namespacePreResolved === true || this.isNamespacePolicyAuthorized(input.namespace)) return undefined;
889
+ return request.reviewNamespacePreResolved === true
890
+ ? asTrimmed(request.reviewNamespace)
891
+ : this.orchestrator.config.defaultNamespace;
892
+ }
893
+
894
+
895
+ private remember(keys: readonly string[]): void {
896
+ for (const key of keys) {
897
+ if (this.observedKeys.has(key)) continue;
898
+ this.observedKeys.add(key);
899
+ this.observedKeyOrder.push(key);
900
+ }
901
+ while (this.observedKeyOrder.length > this.maxDedupeKeys) {
902
+ const expired = this.observedKeyOrder.shift();
903
+ if (expired) this.observedKeys.delete(expired);
904
+ }
905
+ }
906
+
907
+ private async processNote(
908
+ request: InlineExplicitCaptureProcessRequest,
909
+ input: ExplicitCaptureInput,
910
+ replayKeys: readonly string[],
911
+ validationFailureKey: string
912
+ ): Promise<Pick<InlineExplicitCaptureProcessResult, "processed" | "accepted" | "queued" | "duplicates" | "failed">> {
913
+ const serializationKey = replayKeys.at(-1) ?? validationFailureKey;
914
+ return this.enqueue(serializationKey, async () => {
915
+ if (replayKeys.some((key) => this.observedKeys.has(key)) || this.observedKeys.has(validationFailureKey)) {
916
+ return { processed: 0, accepted: 0, queued: 0, duplicates: 1, failed: 0 };
917
+ }
918
+
919
+ const classifyPersisted = (
920
+ persisted: Awaited<ReturnType<typeof persistExplicitCapture>>
921
+ ): Pick<InlineExplicitCaptureProcessResult, "processed" | "accepted" | "queued" | "duplicates" | "failed"> => {
922
+ this.remember(replayKeys);
923
+ if (persisted.duplicateOf) {
924
+ return { processed: 1, accepted: 0, queued: 0, duplicates: 1, failed: 0 };
925
+ }
926
+ if (persisted.tombstoneBlocked) {
927
+ this.orchestrator.requestQmdMaintenanceForTool("inline.memory_note.review");
928
+ return { processed: 1, accepted: 0, queued: 1, duplicates: 0, failed: 0 };
929
+ }
930
+ this.orchestrator.requestQmdMaintenanceForTool("inline.memory_note");
931
+ return { processed: 1, accepted: 1, queued: 0, duplicates: 0, failed: 0 };
932
+ };
933
+ const persistCandidate = async (candidate: ValidExplicitCapture) =>
934
+ classifyPersisted(await persistExplicitCapture(this.orchestrator, candidate, "inline"));
935
+
936
+ let validationSucceeded = false;
937
+ let candidate: ValidExplicitCapture | undefined;
938
+ try {
939
+ candidate = validateExplicitCaptureInput(input);
940
+ if (request.namespacePreResolved === true) candidate.namespacePreResolved = true;
941
+ validationSucceeded = true;
942
+ return await persistCandidate(candidate);
943
+ } catch (error) {
944
+ let captureError: unknown = error;
945
+ if (validationSucceeded && candidate && isTombstoneBlockedCaptureWriteLockBusy(error)) {
946
+ try {
947
+ return await persistCandidate(candidate);
948
+ } catch (retryError) {
949
+ if (isTombstoneBlockedCaptureWriteLockBusy(retryError)) {
950
+ log.warn(
951
+ `explicit inline capture deferred after tombstone write lock contention: ${normalizeExplicitCaptureError(retryError)}`
952
+ );
953
+ return { processed: 0, accepted: 0, queued: 0, duplicates: 0, failed: 1 };
954
+ }
955
+ captureError = retryError;
956
+ }
957
+ }
958
+ if (validationSucceeded && !isUnsupportedExplicitCaptureNamespaceError(captureError)) {
959
+ log.warn(`explicit inline capture persistence failed: ${normalizeExplicitCaptureError(captureError)}`);
960
+ return { processed: 0, accepted: 0, queued: 0, duplicates: 0, failed: 1 };
961
+ }
962
+
963
+ const queueInput = request.namespacePreResolved === true ? { ...input, namespacePreResolved: true } : input;
964
+ const reviewNamespace = this.resolveReviewFallbackNamespace(request, input);
965
+ try {
966
+ const review = await queueExplicitCaptureForReview(
967
+ this.orchestrator,
968
+ queueInput,
969
+ "inline",
970
+ captureError,
971
+ reviewNamespace ? { resolvedNamespace: reviewNamespace } : undefined
972
+ );
973
+ this.remember(validationSucceeded ? replayKeys : [validationFailureKey]);
974
+ if (review.duplicateOf) {
975
+ return { processed: 1, accepted: 0, queued: 0, duplicates: 1, failed: 0 };
976
+ }
977
+ this.orchestrator.requestQmdMaintenanceForTool("inline.memory_note.review");
978
+ return { processed: 1, accepted: 0, queued: 1, duplicates: 0, failed: 0 };
979
+ } catch (queueError) {
980
+ log.warn(
981
+ `explicit inline capture rejected: ${normalizeExplicitCaptureError(captureError)}; review queue fallback failed: ${normalizeExplicitCaptureError(queueError)}`
982
+ );
983
+ return { processed: 0, accepted: 0, queued: 0, duplicates: 0, failed: 1 };
984
+ }
985
+ }
986
+ });
987
+ }
988
+
989
+ async process(request: InlineExplicitCaptureProcessRequest): Promise<InlineExplicitCaptureProcessResult> {
990
+ if (!shouldProcessInlineExplicitCapture({ captureMode: request.captureMode })) {
991
+ return { content: request.content, processed: 0, accepted: 0, queued: 0, duplicates: 0, failed: 0 };
992
+ }
993
+ if (request.namespacePreResolved === true && !asTrimmed(request.namespace)) {
994
+ throw new Error("namespacePreResolved requires a resolved namespace");
995
+ }
996
+ if (request.reviewNamespacePreResolved === true && !asTrimmed(request.reviewNamespace)) {
997
+ throw new Error("reviewNamespacePreResolved requires a resolved namespace");
998
+ }
999
+
1000
+ const notes = parseInlineExplicitCaptureCandidates(request.content);
1001
+ const content = hasInlineExplicitCaptureMarkup(request.content)
1002
+ ? stripInlineExplicitCaptureNotes(request.content)
1003
+ : request.content;
1004
+ const sourceConnector = asTrimmed(request.sourceConnector) ?? asTrimmed(this.options.sourceConnector);
1005
+ let processed = 0;
1006
+ let accepted = 0;
1007
+ let queued = 0;
1008
+ let duplicates = 0;
1009
+ let failed = 0;
1010
+
1011
+ for (const note of notes) {
1012
+ const input: ExplicitCaptureInput = {
1013
+ ...note,
1014
+ ...(request.namespace !== undefined ? { namespace: request.namespace } : {}),
1015
+ ...(sourceConnector ? { sourceConnector } : {}),
1016
+ };
1017
+ const { replayKeys, validationFailureKey } = this.buildDedupeKeys(
1018
+ request.dedupeKeys,
1019
+ input,
1020
+ request.namespacePreResolved === true,
1021
+ this.resolveReviewFallbackNamespace(request, input)
1022
+ );
1023
+ const result = await this.processNote(request, input, replayKeys, validationFailureKey);
1024
+ processed += result.processed;
1025
+ accepted += result.accepted;
1026
+ queued += result.queued;
1027
+ duplicates += result.duplicates;
1028
+ failed += result.failed;
1029
+ }
1030
+
1031
+ return { content, processed, accepted, queued, duplicates, failed };
1032
+ }
1033
+ }