@remnic/core 9.3.689 → 9.3.691

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 (322) hide show
  1. package/dist/access-boundary.d.ts +7 -6
  2. package/dist/access-boundary.js +19 -18
  3. package/dist/access-cli.js +111 -47
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +6 -5
  6. package/dist/access-http.js +25 -24
  7. package/dist/access-mcp.d.ts +13 -5
  8. package/dist/access-mcp.js +22 -21
  9. package/dist/access-operations.d.ts +13 -7
  10. package/dist/access-operations.js +23 -20
  11. package/dist/access-schema.js +3 -3
  12. package/dist/{access-service-DmCHJ4cH.d.ts → access-service-CKnFdxH-.d.ts} +63 -3
  13. package/dist/access-service.d.ts +6 -5
  14. package/dist/access-service.js +18 -17
  15. package/dist/access-surface-catalog.d.ts +6 -5
  16. package/dist/access-surface-catalog.js +2 -0
  17. package/dist/access-surface-catalog.js.map +1 -1
  18. package/dist/action-confidence.d.ts +1 -1
  19. package/dist/active-memory-bridge.d.ts +1 -1
  20. package/dist/active-recall.d.ts +1 -1
  21. package/dist/active-recall.js +1 -1
  22. package/dist/adapters/index.js +4 -4
  23. package/dist/adapters/registry.js +2 -2
  24. package/dist/{auto-sync-NUQWSFQD.js → auto-sync-RWXT6P5M.js} +5 -5
  25. package/dist/behavior-learner.d.ts +1 -1
  26. package/dist/behavior-signals.d.ts +1 -1
  27. package/dist/bootstrap.d.ts +5 -4
  28. package/dist/briefing.d.ts +2 -1
  29. package/dist/briefing.js +6 -5
  30. package/dist/buffer-surprise-report.d.ts +1 -1
  31. package/dist/buffer.d.ts +2 -1
  32. package/dist/calibration.d.ts +1 -1
  33. package/dist/calibration.js +2 -2
  34. package/dist/capabilities.d.ts +1 -1
  35. package/dist/{catalog-COqWZlZ6.d.ts → catalog-BEWLjdOr.d.ts} +2 -2
  36. package/dist/causal-behavior.d.ts +1 -1
  37. package/dist/causal-consolidation.d.ts +1 -1
  38. package/dist/causal-consolidation.js +9 -8
  39. package/dist/causal-consolidation.js.map +1 -1
  40. package/dist/{chunk-GTDH3IUH.js → chunk-2D27ODNX.js} +2 -2
  41. package/dist/{chunk-3K37VPM3.js → chunk-3BRA3N5A.js} +2 -2
  42. package/dist/{chunk-4FE2K57M.js → chunk-3VVH6ZQW.js} +2 -2
  43. package/dist/{chunk-FIVDN2SM.js → chunk-4RGWCRIJ.js} +4 -4
  44. package/dist/{chunk-AGJH5ISO.js → chunk-5KBNNUXS.js} +2 -2
  45. package/dist/{chunk-OV4D5T7V.js → chunk-66HROLW5.js} +2 -2
  46. package/dist/{chunk-Z2M6YTAJ.js → chunk-7UVDIWLL.js} +34 -5
  47. package/dist/chunk-7UVDIWLL.js.map +1 -0
  48. package/dist/{chunk-QANVLERJ.js → chunk-ADUAF5EV.js} +2 -2
  49. package/dist/{chunk-LLONI6PY.js → chunk-AFWEUTMS.js} +2 -2
  50. package/dist/{chunk-W63OY3J7.js → chunk-D3DNGQNQ.js} +3 -3
  51. package/dist/{chunk-T422SYM6.js → chunk-DCWIQFNA.js} +5 -5
  52. package/dist/{chunk-NGFEWFNK.js → chunk-DQHZA7Y3.js} +1 -1
  53. package/dist/chunk-DQHZA7Y3.js.map +1 -0
  54. package/dist/{chunk-OBXTMFZQ.js → chunk-ECZPWSB5.js} +2 -2
  55. package/dist/{chunk-BFVPIKDY.js → chunk-GCOSQBQ2.js} +3 -3
  56. package/dist/{chunk-SAEZIIID.js → chunk-IPJD7WJH.js} +2 -2
  57. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  58. package/dist/{chunk-MDYG7VI7.js → chunk-IREVVFTM.js} +10 -1
  59. package/dist/{chunk-MDYG7VI7.js.map → chunk-IREVVFTM.js.map} +1 -1
  60. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  61. package/dist/{chunk-RHXFYIHA.js → chunk-K7I24ZO2.js} +42 -27
  62. package/dist/chunk-K7I24ZO2.js.map +1 -0
  63. package/dist/{chunk-XU7363OX.js → chunk-KO6OL4I7.js} +617 -23
  64. package/dist/chunk-KO6OL4I7.js.map +1 -0
  65. package/dist/{chunk-UTYBJR7M.js → chunk-MCV2DD6F.js} +2 -2
  66. package/dist/{chunk-3FC6LW6T.js → chunk-MRFVMSTR.js} +2 -2
  67. package/dist/{chunk-WDXCNJSF.js → chunk-MXZWKOOR.js} +2 -2
  68. package/dist/{chunk-QSQW54U5.js → chunk-NCRDCTEJ.js} +33 -10
  69. package/dist/chunk-NCRDCTEJ.js.map +1 -0
  70. package/dist/{chunk-6O6A6YUO.js → chunk-NUZ3N445.js} +7 -7
  71. package/dist/{chunk-UHUZXWDX.js → chunk-OOO7ZKOD.js} +16 -5
  72. package/dist/chunk-OOO7ZKOD.js.map +1 -0
  73. package/dist/{chunk-GS55WYRL.js → chunk-QFG23CRE.js} +2 -2
  74. package/dist/{chunk-UDJLF3BO.js → chunk-QLRYXOAD.js} +2 -2
  75. package/dist/chunk-QNG35H27.js +2190 -0
  76. package/dist/chunk-QNG35H27.js.map +1 -0
  77. package/dist/{chunk-A4HH2EWA.js → chunk-QVG4LAQO.js} +2 -2
  78. package/dist/{chunk-4N3TFFPH.js → chunk-SAC5V6JT.js} +2 -2
  79. package/dist/chunk-SVOZFLIQ.js +16 -0
  80. package/dist/chunk-SVOZFLIQ.js.map +1 -0
  81. package/dist/{chunk-JZBFL7RI.js → chunk-TJ5IIQIR.js} +468 -2265
  82. package/dist/chunk-TJ5IIQIR.js.map +1 -0
  83. package/dist/{chunk-UXLZOVCN.js → chunk-TSGREOYY.js} +38 -6
  84. package/dist/chunk-TSGREOYY.js.map +1 -0
  85. package/dist/{chunk-B4XVLHJA.js → chunk-U2OBZSGG.js} +2 -2
  86. package/dist/{chunk-STOEE37X.js → chunk-UOCLRM74.js} +2 -2
  87. package/dist/{chunk-A7UZR6RA.js → chunk-UTUYAPLY.js} +14 -1
  88. package/dist/chunk-UTUYAPLY.js.map +1 -0
  89. package/dist/{chunk-JPETDXED.js → chunk-UUMYB3FR.js} +2 -2
  90. package/dist/{chunk-BLIWOONZ.js → chunk-VFVG4NS2.js} +33 -20
  91. package/dist/chunk-VFVG4NS2.js.map +1 -0
  92. package/dist/{chunk-OWFY6NGQ.js → chunk-VH2DHBHG.js} +2 -2
  93. package/dist/chunk-VH2DHBHG.js.map +1 -0
  94. package/dist/{chunk-WIHPNY65.js → chunk-W4ELOUNX.js} +2 -2
  95. package/dist/{chunk-VX6OBUDW.js → chunk-WIKNVFBS.js} +2 -2
  96. package/dist/{chunk-2SNKUSQC.js → chunk-X5J3JZR3.js} +4 -4
  97. package/dist/{chunk-PH3HOKYW.js → chunk-ZLXGFAML.js} +2 -2
  98. package/dist/{cli-D8nZ2MPH.d.ts → cli-CrSW_KRq.d.ts} +3 -3
  99. package/dist/cli.d.ts +7 -6
  100. package/dist/cli.js +40 -38
  101. package/dist/compounding/engine.d.ts +2 -1
  102. package/dist/compounding/engine.js +6 -5
  103. package/dist/compounding/preference-consolidator.d.ts +1 -1
  104. package/dist/compression-optimizer.d.ts +1 -1
  105. package/dist/config.d.ts +1 -1
  106. package/dist/config.js +1 -1
  107. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  108. package/dist/connectors/codex-materialize-runner.js +6 -5
  109. package/dist/connectors/codex-materialize.d.ts +1 -1
  110. package/dist/connectors/index.d.ts +1 -1
  111. package/dist/connectors/index.js +6 -5
  112. package/dist/consolidation-provenance-check.d.ts +2 -1
  113. package/dist/consolidation-undo.d.ts +2 -1
  114. package/dist/contradiction/index.d.ts +2 -1
  115. package/dist/contradiction/index.js +4 -4
  116. package/dist/conversation-index/backend.d.ts +1 -1
  117. package/dist/conversation-index/backend.js +2 -2
  118. package/dist/conversation-index/chunker.d.ts +1 -1
  119. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  120. package/dist/conversation-index/indexer.d.ts +1 -1
  121. package/dist/conversation-index/search.d.ts +1 -1
  122. package/dist/day-summary.d.ts +1 -1
  123. package/dist/delinearize.d.ts +1 -1
  124. package/dist/direct-answer-wiring.d.ts +1 -1
  125. package/dist/direct-answer.d.ts +1 -1
  126. package/dist/embedding-fallback.d.ts +1 -1
  127. package/dist/enrichment/index.d.ts +1 -1
  128. package/dist/entity-retrieval.d.ts +2 -1
  129. package/dist/entity-retrieval.js +6 -5
  130. package/dist/entity-schema.d.ts +1 -1
  131. package/dist/event-time.d.ts +35 -0
  132. package/dist/event-time.js +325 -0
  133. package/dist/event-time.js.map +1 -0
  134. package/dist/explicit-capture.d.ts +5 -4
  135. package/dist/extraction-judge-telemetry.d.ts +1 -1
  136. package/dist/extraction-judge-training.d.ts +1 -1
  137. package/dist/extraction-judge.d.ts +1 -1
  138. package/dist/extraction-judge.js +3 -3
  139. package/dist/extraction.d.ts +1 -1
  140. package/dist/extraction.js +3 -3
  141. package/dist/fallback-llm.d.ts +1 -1
  142. package/dist/fallback-llm.js +2 -2
  143. package/dist/identity-continuity.d.ts +1 -1
  144. package/dist/importance.d.ts +1 -1
  145. package/dist/index.d.ts +10 -9
  146. package/dist/index.js +78 -75
  147. package/dist/index.js.map +1 -1
  148. package/dist/intent.d.ts +1 -1
  149. package/dist/lcm/engine.d.ts +1 -1
  150. package/dist/lcm/engine.js +2 -2
  151. package/dist/lcm/index.d.ts +1 -1
  152. package/dist/lcm/index.js +2 -2
  153. package/dist/lcm/tools.d.ts +1 -1
  154. package/dist/lifecycle.d.ts +1 -1
  155. package/dist/live-connectors-runner.d.ts +1 -1
  156. package/dist/local-llm.d.ts +1 -1
  157. package/dist/maintenance/memory-governance.d.ts +1 -1
  158. package/dist/maintenance/memory-governance.js +6 -5
  159. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -5
  160. package/dist/maintenance/rebuild-memory-projection.js +7 -6
  161. package/dist/mcp-memory-inspector-app.d.ts +6 -5
  162. package/dist/memory-action-policy.d.ts +1 -1
  163. package/dist/memory-cache.d.ts +1 -1
  164. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  165. package/dist/memory-projection-store.d.ts +1 -1
  166. package/dist/memory-provenance.d.ts +1 -1
  167. package/dist/memory-worth-outcomes.d.ts +2 -1
  168. package/dist/models-json.d.ts +1 -1
  169. package/dist/namespaces/migrate.d.ts +3 -2
  170. package/dist/namespaces/migrate.js +11 -10
  171. package/dist/namespaces/principal.d.ts +1 -1
  172. package/dist/namespaces/search.d.ts +1 -1
  173. package/dist/namespaces/search.js +4 -4
  174. package/dist/namespaces/storage.d.ts +3 -2
  175. package/dist/namespaces/storage.js +6 -5
  176. package/dist/native-knowledge.d.ts +1 -1
  177. package/dist/operator-toolkit.d.ts +2 -1
  178. package/dist/operator-toolkit.js +15 -13
  179. package/dist/{orchestrator-CA6ouzBn.d.ts → orchestrator-DvswGK76.d.ts} +147 -3
  180. package/dist/orchestrator.d.ts +5 -4
  181. package/dist/orchestrator.js +37 -35
  182. package/dist/patterns-cli.d.ts +1 -1
  183. package/dist/policy-runtime.d.ts +1 -1
  184. package/dist/provenance.d.ts +1 -1
  185. package/dist/qmd-recall-cache.d.ts +1 -1
  186. package/dist/qmd.d.ts +1 -1
  187. package/dist/recall-disclosure-escalation.d.ts +1 -1
  188. package/dist/recall-explain-renderer.d.ts +1 -1
  189. package/dist/recall-explain-renderer.js +3 -3
  190. package/dist/recall-planner-llm.d.ts +1 -1
  191. package/dist/recall-planner-llm.js +2 -2
  192. package/dist/recall-state.d.ts +1 -1
  193. package/dist/recall-tag-filter.d.ts +1 -1
  194. package/dist/recall-xray-cli.d.ts +1 -1
  195. package/dist/recall-xray-cli.js +4 -4
  196. package/dist/recall-xray-renderer.d.ts +1 -1
  197. package/dist/recall-xray-renderer.js +3 -3
  198. package/dist/recall-xray.d.ts +1 -1
  199. package/dist/recall-xray.js +2 -2
  200. package/dist/resolve-auth-token.d.ts +1 -1
  201. package/dist/resume-bundles.js +2 -2
  202. package/dist/retrieval-agents.d.ts +1 -1
  203. package/dist/retrieval-tiers.d.ts +1 -1
  204. package/dist/routing/engine.d.ts +1 -1
  205. package/dist/routing/store.d.ts +1 -1
  206. package/dist/schemas.d.ts +10 -10
  207. package/dist/search/embed-helper.d.ts +1 -1
  208. package/dist/search/factory.d.ts +1 -1
  209. package/dist/search/factory.js +3 -3
  210. package/dist/search/index.d.ts +1 -1
  211. package/dist/search/index.js +3 -3
  212. package/dist/search/lancedb-backend.d.ts +1 -1
  213. package/dist/search/meilisearch-backend.d.ts +1 -1
  214. package/dist/search/noop-backend.d.ts +1 -1
  215. package/dist/search/orama-backend.d.ts +1 -1
  216. package/dist/search/port.d.ts +1 -1
  217. package/dist/search/remote-backend.d.ts +1 -1
  218. package/dist/{semantic-consolidation-CWch5uM7.d.ts → semantic-consolidation-D5U0Mrdb.d.ts} +1 -1
  219. package/dist/semantic-consolidation.d.ts +2 -2
  220. package/dist/semantic-consolidation.js +7 -6
  221. package/dist/semantic-rule-promotion.js +6 -5
  222. package/dist/semantic-rule-verifier.d.ts +1 -1
  223. package/dist/semantic-rule-verifier.js +6 -5
  224. package/dist/session-observer-bands.d.ts +1 -1
  225. package/dist/session-observer-state.d.ts +1 -1
  226. package/dist/shared-context/manager.d.ts +1 -1
  227. package/dist/signal.d.ts +1 -1
  228. package/dist/storage.d.ts +6 -28
  229. package/dist/storage.js +8 -6
  230. package/dist/structured-attributes.d.ts +29 -0
  231. package/dist/structured-attributes.js +8 -0
  232. package/dist/structured-attributes.js.map +1 -0
  233. package/dist/summarizer.d.ts +1 -1
  234. package/dist/summarizer.js +3 -3
  235. package/dist/summary-snapshot.d.ts +1 -1
  236. package/dist/temporal-supersession.d.ts +2 -1
  237. package/dist/temporal-supersession.js +2 -2
  238. package/dist/temporal-validity.d.ts +22 -2
  239. package/dist/temporal-validity.js +3 -1
  240. package/dist/threading.d.ts +1 -1
  241. package/dist/tier-migration.d.ts +2 -1
  242. package/dist/tier-routing.d.ts +1 -1
  243. package/dist/topics.d.ts +1 -1
  244. package/dist/transcript.d.ts +1 -1
  245. package/dist/transfer/autodetect.js +1 -1
  246. package/dist/transfer/backup.js +1 -1
  247. package/dist/transfer/capsule-export.js +2 -2
  248. package/dist/{types-Dm5xxVrr.d.ts → types-2NoBoRBM.d.ts} +38 -0
  249. package/dist/types.d.ts +1 -1
  250. package/dist/types.js +1 -1
  251. package/dist/utility-runtime.d.ts +1 -1
  252. package/dist/verified-recall.js +6 -5
  253. package/package.json +2 -2
  254. package/src/access-boundary.ts +2 -1
  255. package/src/access-cli.test.ts +40 -0
  256. package/src/access-cli.ts +90 -2
  257. package/src/access-http.ts +35 -6
  258. package/src/access-mcp.ts +34 -0
  259. package/src/access-operations.ts +45 -0
  260. package/src/access-service.ts +60 -0
  261. package/src/access-surface-catalog.test.ts +1 -1
  262. package/src/access-surface-catalog.ts +2 -0
  263. package/src/bitemporal-frontmatter.test.ts +177 -0
  264. package/src/cli.ts +18 -0
  265. package/src/coding/architecture-card.test.ts +544 -0
  266. package/src/coding/architecture-card.ts +687 -0
  267. package/src/coding/architecture-surfaces.test.ts +579 -0
  268. package/src/coding/architecture-surfaces.ts +457 -0
  269. package/src/config.ts +13 -0
  270. package/src/event-time.test.ts +514 -0
  271. package/src/event-time.ts +548 -0
  272. package/src/maintenance/namespace-maintenance-fanout.test.ts +595 -0
  273. package/src/maintenance/namespace-maintenance-fanout.ts +318 -0
  274. package/src/maintenance/namespace-planner.ts +74 -16
  275. package/src/operator-toolkit.ts +25 -0
  276. package/src/orchestrator.ts +171 -1
  277. package/src/storage.ts +40 -20
  278. package/src/structured-attributes.ts +39 -0
  279. package/src/temporal-validity.ts +34 -0
  280. package/src/types.ts +38 -0
  281. package/dist/chunk-A7UZR6RA.js.map +0 -1
  282. package/dist/chunk-BLIWOONZ.js.map +0 -1
  283. package/dist/chunk-JZBFL7RI.js.map +0 -1
  284. package/dist/chunk-NGFEWFNK.js.map +0 -1
  285. package/dist/chunk-OWFY6NGQ.js.map +0 -1
  286. package/dist/chunk-QSQW54U5.js.map +0 -1
  287. package/dist/chunk-RHXFYIHA.js.map +0 -1
  288. package/dist/chunk-UHUZXWDX.js.map +0 -1
  289. package/dist/chunk-UXLZOVCN.js.map +0 -1
  290. package/dist/chunk-XU7363OX.js.map +0 -1
  291. package/dist/chunk-Z2M6YTAJ.js.map +0 -1
  292. /package/dist/{auto-sync-NUQWSFQD.js.map → auto-sync-RWXT6P5M.js.map} +0 -0
  293. /package/dist/{chunk-GTDH3IUH.js.map → chunk-2D27ODNX.js.map} +0 -0
  294. /package/dist/{chunk-3K37VPM3.js.map → chunk-3BRA3N5A.js.map} +0 -0
  295. /package/dist/{chunk-4FE2K57M.js.map → chunk-3VVH6ZQW.js.map} +0 -0
  296. /package/dist/{chunk-FIVDN2SM.js.map → chunk-4RGWCRIJ.js.map} +0 -0
  297. /package/dist/{chunk-AGJH5ISO.js.map → chunk-5KBNNUXS.js.map} +0 -0
  298. /package/dist/{chunk-OV4D5T7V.js.map → chunk-66HROLW5.js.map} +0 -0
  299. /package/dist/{chunk-QANVLERJ.js.map → chunk-ADUAF5EV.js.map} +0 -0
  300. /package/dist/{chunk-LLONI6PY.js.map → chunk-AFWEUTMS.js.map} +0 -0
  301. /package/dist/{chunk-W63OY3J7.js.map → chunk-D3DNGQNQ.js.map} +0 -0
  302. /package/dist/{chunk-T422SYM6.js.map → chunk-DCWIQFNA.js.map} +0 -0
  303. /package/dist/{chunk-OBXTMFZQ.js.map → chunk-ECZPWSB5.js.map} +0 -0
  304. /package/dist/{chunk-BFVPIKDY.js.map → chunk-GCOSQBQ2.js.map} +0 -0
  305. /package/dist/{chunk-SAEZIIID.js.map → chunk-IPJD7WJH.js.map} +0 -0
  306. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  307. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  308. /package/dist/{chunk-UTYBJR7M.js.map → chunk-MCV2DD6F.js.map} +0 -0
  309. /package/dist/{chunk-3FC6LW6T.js.map → chunk-MRFVMSTR.js.map} +0 -0
  310. /package/dist/{chunk-WDXCNJSF.js.map → chunk-MXZWKOOR.js.map} +0 -0
  311. /package/dist/{chunk-6O6A6YUO.js.map → chunk-NUZ3N445.js.map} +0 -0
  312. /package/dist/{chunk-GS55WYRL.js.map → chunk-QFG23CRE.js.map} +0 -0
  313. /package/dist/{chunk-UDJLF3BO.js.map → chunk-QLRYXOAD.js.map} +0 -0
  314. /package/dist/{chunk-A4HH2EWA.js.map → chunk-QVG4LAQO.js.map} +0 -0
  315. /package/dist/{chunk-4N3TFFPH.js.map → chunk-SAC5V6JT.js.map} +0 -0
  316. /package/dist/{chunk-B4XVLHJA.js.map → chunk-U2OBZSGG.js.map} +0 -0
  317. /package/dist/{chunk-STOEE37X.js.map → chunk-UOCLRM74.js.map} +0 -0
  318. /package/dist/{chunk-JPETDXED.js.map → chunk-UUMYB3FR.js.map} +0 -0
  319. /package/dist/{chunk-WIHPNY65.js.map → chunk-W4ELOUNX.js.map} +0 -0
  320. /package/dist/{chunk-VX6OBUDW.js.map → chunk-WIKNVFBS.js.map} +0 -0
  321. /package/dist/{chunk-2SNKUSQC.js.map → chunk-X5J3JZR3.js.map} +0 -0
  322. /package/dist/{chunk-PH3HOKYW.js.map → chunk-ZLXGFAML.js.map} +0 -0
@@ -4,10 +4,7 @@ import {
4
4
  loadSyncState,
5
5
  syncWearableSource,
6
6
  wearableSourceLabel
7
- } from "./chunk-AGJH5ISO.js";
8
- import {
9
- migrateFromEngram
10
- } from "./chunk-7HYPN2GC.js";
7
+ } from "./chunk-5KBNNUXS.js";
11
8
  import {
12
9
  ensureContradictionScanCron,
13
10
  ensureDaySummaryCron,
@@ -17,9 +14,12 @@ import {
17
14
  ensureProceduralMiningCron,
18
15
  graphEdgeDecayCadenceToCronExpr
19
16
  } from "./chunk-666A3MOW.js";
17
+ import {
18
+ migrateFromEngram
19
+ } from "./chunk-7HYPN2GC.js";
20
20
  import {
21
21
  LcmEngine
22
- } from "./chunk-2SNKUSQC.js";
22
+ } from "./chunk-X5J3JZR3.js";
23
23
  import {
24
24
  chunkTranscriptEntries
25
25
  } from "./chunk-TECVW3JP.js";
@@ -29,7 +29,7 @@ import {
29
29
  import {
30
30
  CompoundingEngine,
31
31
  defaultTierMigrationCycleBudget
32
- } from "./chunk-STOEE37X.js";
32
+ } from "./chunk-UOCLRM74.js";
33
33
  import {
34
34
  SharedContextManager
35
35
  } from "./chunk-UFS7OXGL.js";
@@ -38,6 +38,15 @@ import {
38
38
  applyUtilityRankingRuntimeDelta,
39
39
  loadUtilityRuntimeValues
40
40
  } from "./chunk-LXOM6IQU.js";
41
+ import {
42
+ applyTemporalSupersession,
43
+ normalizeSupersessionKey,
44
+ shouldFilterSupersededFromRecall
45
+ } from "./chunk-MRFVMSTR.js";
46
+ import {
47
+ isValidAsOf,
48
+ isValidityExpiredNow
49
+ } from "./chunk-IREVVFTM.js";
41
50
  import {
42
51
  TierMigrationExecutor
43
52
  } from "./chunk-VOUOLGIP.js";
@@ -52,19 +61,11 @@ import {
52
61
  } from "./chunk-UHGBNIOS.js";
53
62
  import {
54
63
  HourlySummarizer
55
- } from "./chunk-A4HH2EWA.js";
64
+ } from "./chunk-QVG4LAQO.js";
56
65
  import {
57
66
  buildTargetedFactRecallSection,
58
67
  shouldRecallTargetedFactEvidence
59
68
  } from "./chunk-R5DB26G6.js";
60
- import {
61
- applyTemporalSupersession,
62
- normalizeSupersessionKey,
63
- shouldFilterSupersededFromRecall
64
- } from "./chunk-3FC6LW6T.js";
65
- import {
66
- isValidAsOf
67
- } from "./chunk-MDYG7VI7.js";
68
69
  import {
69
70
  SessionObserverState
70
71
  } from "./chunk-DKTSR7EK.js";
@@ -140,6 +141,18 @@ import {
140
141
  import {
141
142
  classifyMemoryKind
142
143
  } from "./chunk-YAZNBMNF.js";
144
+ import {
145
+ ExtractionEngine
146
+ } from "./chunk-MXZWKOOR.js";
147
+ import {
148
+ parseMemoryActionEligibilityContext
149
+ } from "./chunk-ROZJACKP.js";
150
+ import {
151
+ ModelRegistry
152
+ } from "./chunk-PWFWCGOO.js";
153
+ import {
154
+ LocalLlmClient
155
+ } from "./chunk-UPTZYUYJ.js";
143
156
  import {
144
157
  buildFocusedListRecallSection,
145
158
  shouldRecallFocusedListEvidence
@@ -160,19 +173,7 @@ import {
160
173
  getVerdictKind,
161
174
  judgeFactDurability,
162
175
  validateProcedureExtraction
163
- } from "./chunk-OBXTMFZQ.js";
164
- import {
165
- ExtractionEngine
166
- } from "./chunk-WDXCNJSF.js";
167
- import {
168
- parseMemoryActionEligibilityContext
169
- } from "./chunk-ROZJACKP.js";
170
- import {
171
- ModelRegistry
172
- } from "./chunk-PWFWCGOO.js";
173
- import {
174
- LocalLlmClient
175
- } from "./chunk-UPTZYUYJ.js";
176
+ } from "./chunk-ECZPWSB5.js";
176
177
  import {
177
178
  tryDirectAnswer
178
179
  } from "./chunk-OQH5XUH3.js";
@@ -183,7 +184,7 @@ import {
183
184
  buildEntityRecallSection,
184
185
  entityRecentTranscriptLookbackHours,
185
186
  readRecentEntityTranscriptEntries
186
- } from "./chunk-B4XVLHJA.js";
187
+ } from "./chunk-U2OBZSGG.js";
187
188
  import {
188
189
  formatDaySummaryMemories
189
190
  } from "./chunk-EO5QWINU.js";
@@ -196,14 +197,14 @@ import {
196
197
  import {
197
198
  compareVerifiedEpisodeResults,
198
199
  searchVerifiedEpisodes
199
- } from "./chunk-4FE2K57M.js";
200
+ } from "./chunk-3VVH6ZQW.js";
200
201
  import {
201
202
  ThreadingManager
202
203
  } from "./chunk-QRDOSYOR.js";
203
204
  import {
204
205
  compareVerifiedSemanticRuleResults,
205
206
  searchVerifiedSemanticRules
206
- } from "./chunk-PH3HOKYW.js";
207
+ } from "./chunk-ZLXGFAML.js";
207
208
  import {
208
209
  searchWorkProductLedgerEntries
209
210
  } from "./chunk-ZRWB5D4H.js";
@@ -214,10 +215,15 @@ import {
214
215
  PolicyRuntimeManager
215
216
  } from "./chunk-33JBK2XP.js";
216
217
  import {
217
- NamespaceStorageRouter,
218
- resolveDefaultNamespaceRoot,
219
- resolveNamespaceStorageRoot
220
- } from "./chunk-WIHPNY65.js";
218
+ NamespaceCatalog,
219
+ planNamespaceMaintenance,
220
+ runNamespaceMaintenanceBatchPlan,
221
+ runNamespaceMaintenanceFanout,
222
+ summarizeNamespaceMaintenanceHealth
223
+ } from "./chunk-QNG35H27.js";
224
+ import {
225
+ NamespaceStorageRouter
226
+ } from "./chunk-W4ELOUNX.js";
221
227
  import {
222
228
  isAboveImportanceThreshold,
223
229
  scoreImportance
@@ -262,7 +268,7 @@ import {
262
268
  materializeAfterSemanticConsolidation,
263
269
  parseConsolidationResponse,
264
270
  parseOperatorAwareConsolidationResponse
265
- } from "./chunk-GS55WYRL.js";
271
+ } from "./chunk-QFG23CRE.js";
266
272
  import {
267
273
  buildBehaviorSignalsForMemory,
268
274
  dedupeBehaviorSignalsByMemoryAndHash
@@ -289,7 +295,7 @@ import {
289
295
  FallbackLlmClient,
290
296
  fallbackLlmRuntimeContextFromConfig,
291
297
  gatewayTaskChainOptions
292
- } from "./chunk-FIVDN2SM.js";
298
+ } from "./chunk-4RGWCRIJ.js";
293
299
  import {
294
300
  compileCorrectionRule,
295
301
  correctionsFilePath,
@@ -320,18 +326,18 @@ import {
320
326
  } from "./chunk-FF4KLI5W.js";
321
327
  import {
322
328
  buildXraySnapshot
323
- } from "./chunk-LLONI6PY.js";
329
+ } from "./chunk-AFWEUTMS.js";
324
330
  import {
325
331
  objectiveStateStoreOverrideForNamespace,
326
332
  searchObjectiveStateSnapshots
327
333
  } from "./chunk-6HMYUWXR.js";
328
334
  import {
329
335
  NamespaceSearchRouter
330
- } from "./chunk-SAEZIIID.js";
336
+ } from "./chunk-IPJD7WJH.js";
331
337
  import {
332
338
  createConversationIndexRuntime,
333
339
  createSearchBackend
334
- } from "./chunk-JPETDXED.js";
340
+ } from "./chunk-UUMYB3FR.js";
335
341
  import {
336
342
  NoopSearchBackend
337
343
  } from "./chunk-CYEPCZN5.js";
@@ -346,12 +352,12 @@ import {
346
352
  import {
347
353
  parseQmdExplain
348
354
  } from "./chunk-K43PI6DQ.js";
349
- import {
350
- shouldSkipImplicitExtraction
351
- } from "./chunk-U33LWTQQ.js";
352
355
  import {
353
356
  ProfilingCollector
354
357
  } from "./chunk-PNLCEFE4.js";
358
+ import {
359
+ shouldSkipImplicitExtraction
360
+ } from "./chunk-U33LWTQQ.js";
355
361
  import {
356
362
  buildProcedurePersistBody
357
363
  } from "./chunk-QDW3E4RD.js";
@@ -363,16 +369,15 @@ import {
363
369
  } from "./chunk-YN4ZT4CW.js";
364
370
  import {
365
371
  buildChainFollowupGenerator
366
- } from "./chunk-OV4D5T7V.js";
372
+ } from "./chunk-66HROLW5.js";
367
373
  import {
368
374
  ContentHashIndex,
369
375
  StorageManager,
370
376
  compareEntityTimestamps,
371
377
  fingerprintEntityStructuredFacts,
372
378
  normalizeAttributePairs,
373
- parseEntityFile,
374
- stripAttributesSuffix
375
- } from "./chunk-BLIWOONZ.js";
379
+ parseEntityFile
380
+ } from "./chunk-VFVG4NS2.js";
376
381
  import {
377
382
  isValidTranscriptDate,
378
383
  loadSpeakerRegistry,
@@ -383,6 +388,9 @@ import {
383
388
  import {
384
389
  assertPathInsideRoot
385
390
  } from "./chunk-5GPPACXK.js";
391
+ import {
392
+ stripAttributesSuffix
393
+ } from "./chunk-SVOZFLIQ.js";
386
394
  import {
387
395
  attachCitation,
388
396
  hasCitationForTemplate,
@@ -390,10 +398,7 @@ import {
390
398
  } from "./chunk-J6A3CX5N.js";
391
399
  import {
392
400
  confidenceTier
393
- } from "./chunk-NGFEWFNK.js";
394
- import {
395
- displayErrorDetail
396
- } from "./chunk-6KYMPV2O.js";
401
+ } from "./chunk-DQHZA7Y3.js";
397
402
  import {
398
403
  buildQmdRecallCacheKey,
399
404
  getCachedQmdRecall,
@@ -421,7 +426,6 @@ import {
421
426
  parseFlexibleIsoTimestamp
422
427
  } from "./chunk-P7FMDTKL.js";
423
428
  import {
424
- ALL_CATEGORY_DIRS,
425
429
  RECALL_FALLBACK_DIRS,
426
430
  categoryDirName
427
431
  } from "./chunk-VS2IYZRU.js";
@@ -455,19 +459,19 @@ import {
455
459
  } from "./chunk-PVGDJXVK.js";
456
460
 
457
461
  // src/orchestrator.ts
458
- import path4 from "path";
462
+ import path2 from "path";
459
463
  import os from "os";
460
- import { createHash as createHash2, randomBytes } from "crypto";
464
+ import { createHash, randomBytes } from "crypto";
461
465
  import { existsSync, readFileSync } from "fs";
462
466
  import {
463
- lstat as lstat3,
464
- mkdir as mkdir4,
465
- readdir as readdir3,
466
- readFile as readFile4,
467
- realpath as realpath2,
468
- stat as stat3,
469
- unlink as unlink2,
470
- writeFile as writeFile4
467
+ lstat,
468
+ mkdir as mkdir2,
469
+ readdir,
470
+ readFile as readFile2,
471
+ realpath,
472
+ stat,
473
+ unlink,
474
+ writeFile as writeFile2
471
475
  } from "fs/promises";
472
476
 
473
477
  // src/procedural/procedure-recall.ts
@@ -1409,2084 +1413,161 @@ Install it alongside Remnic:
1409
1413
  * and/or day. Includes pending_review candidates — the whole point of
1410
1414
  * review mode is seeing what's queued.
1411
1415
  */
1412
- async transcriptMemories(options = {}) {
1413
- if (options.date !== void 0 && !isValidTranscriptDate(options.date)) {
1414
- throw new WearablesInputError(`invalid date '${options.date}' \u2014 expected YYYY-MM-DD`);
1415
- }
1416
- if (options.source !== void 0) assertValidSourceId(options.source);
1417
- const limit = clampLimit(
1418
- options.limit,
1419
- MEMORY_LIST_DEFAULT_LIMIT,
1420
- MEMORY_LIST_MAX_LIMIT,
1421
- "limit"
1422
- );
1423
- const storage = await this.deps.getStorage();
1424
- const memories = await storage.readAllMemories();
1425
- const results = [];
1426
- for (const memory of memories) {
1427
- const source = memory.frontmatter.source;
1428
- if (typeof source !== "string" || !source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`)) {
1429
- continue;
1430
- }
1431
- const attrs = memory.frontmatter.structuredAttributes ?? {};
1432
- const sourceId = attrs.wearableSource;
1433
- if (options.source !== void 0) {
1434
- if (sourceId !== options.source && source !== wearableSourceLabel(options.source) && source !== `${wearableSourceLabel(options.source)}:native`) {
1435
- continue;
1436
- }
1437
- }
1438
- if (options.date !== void 0 && attrs.wearableDate !== options.date) {
1439
- continue;
1440
- }
1441
- results.push({
1442
- id: memory.frontmatter.id,
1443
- source: sourceId ?? source,
1444
- date: attrs.wearableDate,
1445
- conversationId: attrs.wearableConversationId,
1446
- status: memory.frontmatter.status,
1447
- content: memory.content,
1448
- created: memory.frontmatter.created
1449
- });
1450
- }
1451
- results.sort((a, b) => {
1452
- if (a.created > b.created) return -1;
1453
- if (a.created < b.created) return 1;
1454
- if (a.id < b.id) return -1;
1455
- if (a.id > b.id) return 1;
1456
- return 0;
1457
- });
1458
- return results.slice(0, limit);
1459
- }
1460
- // -- speakers -------------------------------------------------------------
1461
- async listSpeakers() {
1462
- const storage = await this.deps.getStorage();
1463
- return loadSpeakerRegistry(storage.dir);
1464
- }
1465
- async setSpeaker(sourceId, speakerKey, name, opts = {}) {
1466
- if (typeof name !== "string" || name.trim().length === 0) {
1467
- throw new WearablesInputError("speaker name must be a non-empty string");
1468
- }
1469
- if (typeof speakerKey !== "string" || speakerKey.trim().length === 0) {
1470
- throw new WearablesInputError("speaker key must be a non-empty string");
1471
- }
1472
- const storage = await this.deps.getStorage();
1473
- const registry = await loadSpeakerRegistry(storage.dir);
1474
- registry.speakers[speakerRegistryKey(sourceId, speakerKey.trim())] = {
1475
- name: name.trim(),
1476
- ...opts.isSelf === true ? { isSelf: true } : {},
1477
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1478
- };
1479
- await saveSpeakerRegistry(storage.dir, registry);
1480
- return registry;
1481
- }
1482
- async setSelfName(name) {
1483
- if (typeof name !== "string" || name.trim().length === 0) {
1484
- throw new WearablesInputError("self name must be a non-empty string");
1485
- }
1486
- const storage = await this.deps.getStorage();
1487
- const registry = await loadSpeakerRegistry(storage.dir);
1488
- registry.selfName = name.trim();
1489
- await saveSpeakerRegistry(storage.dir, registry);
1490
- return registry;
1491
- }
1492
- async removeSpeaker(sourceId, speakerKey) {
1493
- const storage = await this.deps.getStorage();
1494
- const registry = await loadSpeakerRegistry(storage.dir);
1495
- const key = speakerRegistryKey(sourceId, speakerKey.trim());
1496
- if (!(key in registry.speakers)) {
1497
- throw new WearablesInputError(`no speaker override stored for '${key}'`);
1498
- }
1499
- delete registry.speakers[key];
1500
- await saveSpeakerRegistry(storage.dir, registry);
1501
- return registry;
1502
- }
1503
- // -- corrections ----------------------------------------------------------
1504
- async listCorrections() {
1505
- const storage = await this.deps.getStorage();
1506
- return {
1507
- fromConfig: this.deps.config.corrections,
1508
- fromState: await loadCorrectionsFile(storage.dir),
1509
- stateFilePath: correctionsFilePath(storage.dir)
1510
- };
1511
- }
1512
- async addCorrection(rule) {
1513
- compileCorrectionRule(rule, "correction");
1514
- const storage = await this.deps.getStorage();
1515
- const rules = await loadCorrectionsFile(storage.dir);
1516
- const duplicate = rules.some(
1517
- (existing) => existing.match === rule.match && existing.replace === rule.replace && existing.regex === true === (rule.regex === true)
1518
- );
1519
- if (duplicate) {
1520
- throw new WearablesInputError(
1521
- `an identical correction rule already exists (match: ${JSON.stringify(rule.match)})`
1522
- );
1523
- }
1524
- rules.push(rule);
1525
- await saveCorrectionsFile(storage.dir, rules);
1526
- }
1527
- async removeCorrection(index) {
1528
- if (!Number.isInteger(index) || index < 0) {
1529
- throw new WearablesInputError(`invalid correction index '${index}'`);
1530
- }
1531
- const storage = await this.deps.getStorage();
1532
- const rules = await loadCorrectionsFile(storage.dir);
1533
- if (index >= rules.length) {
1534
- throw new WearablesInputError(
1535
- `correction index ${index} is out of range (have ${rules.length} state rule${rules.length === 1 ? "" : "s"})`
1536
- );
1537
- }
1538
- const [removed] = rules.splice(index, 1);
1539
- await saveCorrectionsFile(storage.dir, rules);
1540
- return removed;
1541
- }
1542
- };
1543
- function clampLimit(value, fallback, max, label) {
1544
- if (value === void 0) return fallback;
1545
- if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1 || value > max) {
1546
- throw new WearablesInputError(
1547
- `invalid ${label} '${value}' \u2014 expected an integer between 1 and ${max}`
1548
- );
1549
- }
1550
- return value;
1551
- }
1552
- function locateTranscriptPath(hitPath) {
1553
- const normalized = hitPath.replace(/\\/g, "/");
1554
- const match = normalized.match(
1555
- /(?:^|\/)wearables\/([a-z][a-z0-9-]{0,63})\/(\d{4}-\d{2}-\d{2})\.md$/
1556
- );
1557
- if (!match) return null;
1558
- if (!isValidTranscriptDate(match[2])) return null;
1559
- return { source: match[1], date: match[2] };
1560
- }
1561
- function extractSnippet(body, index, matchLength) {
1562
- const start = Math.max(0, index - 80);
1563
- const end = Math.min(body.length, index + matchLength + 80);
1564
- const prefix = start > 0 ? "\u2026" : "";
1565
- const suffix = end < body.length ? "\u2026" : "";
1566
- return `${prefix}${body.slice(start, end).replace(/\s+/g, " ").trim()}${suffix}`;
1567
- }
1568
-
1569
- // src/namespaces/catalog.ts
1570
- import path2 from "path";
1571
- import { randomUUID } from "crypto";
1572
- import {
1573
- appendFile,
1574
- lstat,
1575
- mkdir as mkdir2,
1576
- open,
1577
- readdir,
1578
- readFile as readFile2,
1579
- realpath,
1580
- rename,
1581
- stat,
1582
- unlink,
1583
- utimes,
1584
- writeFile as writeFile2
1585
- } from "fs/promises";
1586
- var NAMESPACE_KINDS = [
1587
- "default",
1588
- "self",
1589
- "shared",
1590
- "project",
1591
- "branch",
1592
- "team-project",
1593
- "explicit",
1594
- "legacy"
1595
- ];
1596
- var NAMESPACE_DISCOVERY_SOURCES = [
1597
- "config",
1598
- "write",
1599
- "read",
1600
- "scan",
1601
- "migration"
1602
- ];
1603
- var CATALOG_FILE = "namespaces.jsonl";
1604
- var STATE_DIR = "state";
1605
- var REBUILD_LOCK_FILE = "namespaces.rebuild.lock";
1606
- var REBUILD_LOCK_STALE_MS = 3e4;
1607
- var REBUILD_LOCK_MAX_WAIT_MS = 5e3;
1608
- var REBUILD_LOCK_POLL_MS = 50;
1609
- var REBUILD_LOCK_HEARTBEAT_MS = 1e4;
1610
- var MEMORY_DATA_CHILDREN = [
1611
- ...ALL_CATEGORY_DIRS,
1612
- "entities",
1613
- "artifacts",
1614
- "identity",
1615
- "config",
1616
- "summaries",
1617
- "profile.md",
1618
- "state"
1619
- ];
1620
- function isCatalogEnabled(config) {
1621
- if (config.namespacesEnabled !== true) return false;
1622
- return config.namespaceCatalogEnabled !== false;
1623
- }
1624
- var FILE_MEMORY_DATA_CHILDREN = /* @__PURE__ */ new Set(["profile.md"]);
1625
- function isNotFoundError(err) {
1626
- return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
1627
- }
1628
- async function inspectMemoryDataMarker(rootDir, child) {
1629
- const childPath = path2.join(rootDir, child);
1630
- let entry;
1631
- try {
1632
- entry = await lstat(childPath);
1633
- } catch (err) {
1634
- return isNotFoundError(err) ? { state: "absent" } : { state: "invalid", detail: `${child}: ${err instanceof Error ? err.message : String(err)}` };
1635
- }
1636
- if (entry.isSymbolicLink()) return { state: "invalid", detail: `${child}: symlink` };
1637
- if (FILE_MEMORY_DATA_CHILDREN.has(child)) {
1638
- return entry.isFile() ? { state: "valid" } : { state: "invalid", detail: `${child}: expected file` };
1639
- }
1640
- if (!entry.isDirectory()) return { state: "invalid", detail: `${child}: expected directory` };
1641
- try {
1642
- const rootReal = await realpath(rootDir);
1643
- const childReal = await realpath(childPath);
1644
- return isPathInside(rootReal, childReal) ? { state: "valid" } : { state: "invalid", detail: `${child}: escapes namespace root` };
1645
- } catch (err) {
1646
- return { state: "invalid", detail: `${child}: ${err instanceof Error ? err.message : String(err)}` };
1647
- }
1648
- }
1649
- async function inspectMemoryDataRoot(rootDir) {
1650
- let hasData = false;
1651
- for (const child of MEMORY_DATA_CHILDREN) {
1652
- const marker = await inspectMemoryDataMarker(rootDir, child);
1653
- if (marker.state === "invalid") {
1654
- return { hasData: false, invalidMarker: marker.detail };
1655
- }
1656
- if (marker.state === "valid") {
1657
- hasData = true;
1658
- }
1659
- }
1660
- return { hasData };
1661
- }
1662
- async function hasMemoryData(rootDir) {
1663
- return (await inspectMemoryDataRoot(rootDir)).hasData;
1664
- }
1665
- function isValidIsoTimestamp(value) {
1666
- const ms = Date.parse(value);
1667
- return Number.isFinite(ms);
1668
- }
1669
- function isNamespaceKind(value) {
1670
- return typeof value === "string" && NAMESPACE_KINDS.includes(value);
1671
- }
1672
- function isNamespaceDiscoverySource(value) {
1673
- return typeof value === "string" && NAMESPACE_DISCOVERY_SOURCES.includes(value);
1674
- }
1675
- function coerceRecord(value) {
1676
- if (typeof value !== "object" || value === null) return null;
1677
- const v = value;
1678
- if (typeof v.namespace !== "string") return null;
1679
- const namespace = normalizeNamespaceIdentity(v.namespace);
1680
- if (namespace.length === 0) return null;
1681
- if (typeof v.identityToken !== "string" || v.identityToken.length === 0) return null;
1682
- const expectedIdentityToken = namespaceIdentityToken(namespace);
1683
- if (v.identityToken !== expectedIdentityToken) return null;
1684
- if (typeof v.storageDir !== "string" || v.storageDir.length === 0) return null;
1685
- if (typeof v.createdAt !== "string" || v.createdAt.length === 0) return null;
1686
- if (!isValidIsoTimestamp(v.createdAt)) return null;
1687
- const kind = v.kind === void 0 ? "explicit" : isNamespaceKind(v.kind) ? v.kind : null;
1688
- if (!kind) return null;
1689
- const discoveredBy = v.discoveredBy === void 0 ? "scan" : isNamespaceDiscoverySource(v.discoveredBy) ? v.discoveredBy : null;
1690
- if (!discoveredBy) return null;
1691
- const record = {
1692
- namespace,
1693
- identityToken: expectedIdentityToken,
1694
- kind,
1695
- createdAt: v.createdAt,
1696
- storageDir: v.storageDir,
1697
- discoveredBy
1698
- };
1699
- if (typeof v.principal === "string") record.principal = v.principal;
1700
- if (typeof v.projectId === "string") record.projectId = v.projectId;
1701
- if (typeof v.branch === "string") record.branch = v.branch;
1702
- if (typeof v.parentNamespace === "string") record.parentNamespace = v.parentNamespace;
1703
- if (typeof v.lastReadAt === "string" && isValidIsoTimestamp(v.lastReadAt)) {
1704
- record.lastReadAt = v.lastReadAt;
1705
- }
1706
- if (typeof v.lastWriteAt === "string" && isValidIsoTimestamp(v.lastWriteAt)) {
1707
- record.lastWriteAt = v.lastWriteAt;
1708
- }
1709
- if (v.lastMaintenanceAt && typeof v.lastMaintenanceAt === "object") {
1710
- const out = {};
1711
- for (const [k, val] of Object.entries(v.lastMaintenanceAt)) {
1712
- if (typeof val === "string" && isValidIsoTimestamp(val)) out[k] = val;
1713
- }
1714
- if (Object.keys(out).length > 0) record.lastMaintenanceAt = out;
1715
- }
1716
- return record;
1717
- }
1718
- function laterIso(a, b) {
1719
- if (!a) return b;
1720
- if (!b) return a;
1721
- const am = Date.parse(a);
1722
- const bm = Date.parse(b);
1723
- if (!Number.isFinite(am)) return b;
1724
- if (!Number.isFinite(bm)) return a;
1725
- return bm > am ? b : a;
1726
- }
1727
- function mergeNewerTouchFields(base, fresh) {
1728
- const merged = { ...base };
1729
- const lr = laterIso(base.lastReadAt, fresh.lastReadAt);
1730
- if (lr) merged.lastReadAt = lr;
1731
- const lw = laterIso(base.lastWriteAt, fresh.lastWriteAt);
1732
- if (lw) merged.lastWriteAt = lw;
1733
- if (base.lastMaintenanceAt || fresh.lastMaintenanceAt) {
1734
- const jobs = { ...base.lastMaintenanceAt ?? {} };
1735
- for (const [job, ts] of Object.entries(fresh.lastMaintenanceAt ?? {})) {
1736
- const latest = laterIso(jobs[job], ts);
1737
- if (latest) jobs[job] = latest;
1738
- }
1739
- if (Object.keys(jobs).length > 0) merged.lastMaintenanceAt = jobs;
1740
- }
1741
- return merged;
1742
- }
1743
- function serializeRecord(record) {
1744
- const ordered = {};
1745
- const source = record;
1746
- for (const key of Object.keys(source).sort()) {
1747
- const value = source[key];
1748
- if (value === void 0) continue;
1749
- if (key === "lastMaintenanceAt" && value && typeof value === "object") {
1750
- const sortedJobs = {};
1751
- for (const jobKey of Object.keys(value).sort()) {
1752
- sortedJobs[jobKey] = value[jobKey];
1753
- }
1754
- ordered[key] = sortedJobs;
1755
- continue;
1756
- }
1757
- ordered[key] = value;
1758
- }
1759
- return JSON.stringify(ordered);
1760
- }
1761
- function inferKind(namespace, config) {
1762
- if (namespace === normalizeNamespaceIdentity(config.defaultNamespace)) return "default";
1763
- if (namespace === normalizeNamespaceIdentity(config.sharedNamespace)) return "shared";
1764
- if (config.namespacePolicies.some((p) => normalizeNamespaceIdentity(p.name) === namespace)) {
1765
- return "explicit";
1766
- }
1767
- if (/-branch-|^project-[^-]+-branch-/.test(namespace) || namespace.includes("-branch-")) {
1768
- return "branch";
1769
- }
1770
- if (/^team-.*-project-/.test(namespace) || /^team-.*project-/.test(namespace)) {
1771
- return "team-project";
1772
- }
1773
- if (/^project-/.test(namespace) || /-project-/.test(namespace)) {
1774
- return "project";
1775
- }
1776
- return "explicit";
1777
- }
1778
- var NamespaceCatalog = class {
1779
- constructor(config) {
1780
- this.config = config;
1781
- this.memoryDir = config.memoryDir;
1782
- this.stateDir = path2.join(this.memoryDir, STATE_DIR);
1783
- this.catalogPath = path2.join(this.stateDir, CATALOG_FILE);
1784
- this.rebuildLockPath = path2.join(this.stateDir, REBUILD_LOCK_FILE);
1785
- this.defaultNamespaceIdentity = normalizeNamespaceIdentity(config.defaultNamespace);
1786
- }
1787
- config;
1788
- memoryDir;
1789
- stateDir;
1790
- catalogPath;
1791
- rebuildLockPath;
1792
- // Per-INSTANCE lock owner id (round 6, codex P2 — NBsGP). The rebuild lock
1793
- // file records this id, not just `process.pid`, so two NamespaceCatalog
1794
- // instances in the SAME process sharing a memoryDir are NOT mistaken for each
1795
- // other: a touch on instance B must still wait for instance A's rebuild lock
1796
- // (different owner id, same PID) instead of skipping as "self-held".
1797
- lockOwnerId = randomUUID();
1798
- // Serialized write chain that recovers from rejection (CLAUDE.md rule #40)
1799
- // so a single failed append cannot permanently poison subsequent writes.
1800
- writeChain = Promise.resolve();
1801
- // Test-only seam (round 7 — NEZkA): fires inside a touch's HELD-lock critical
1802
- // section, after the lock is acquired but BEFORE the read→merge→append. A
1803
- // deterministic concurrency test installs a hook here to widen the (otherwise
1804
- // microscopic) window and prove that a cross-process rebuild CANNOT run its
1805
- // load→rename while a touch holds the lock. Never set in production code.
1806
- onTouchCriticalSectionForTest;
1807
- // Test-only seam (round 7 — NEZkA): fires inside a mutating rebuild's HELD-lock
1808
- // critical section, after the final cross-process re-merge `loadCompacted()` and
1809
- // BEFORE the atomic `rename()`. This is the EXACT window in which a check-then-
1810
- // append touch (the old bug) would clobber its append. A deterministic test
1811
- // installs a hook here to attempt a cross-instance touch in this window and
1812
- // assert the held mutex blocks it. Never set in production code.
1813
- onRebuildBeforeRenameForTest;
1814
- // Test-only seam (NFgCT, codex P2): fires AFTER the lockless disk scan but
1815
- // BEFORE the rebuild acquires the cross-process file lock for its final
1816
- // load→merge→rename window. A deterministic test installs a hook here to attempt
1817
- // a cross-instance touch DURING the scan window and assert it is NOT blocked or
1818
- // dropped — proving the scan no longer holds the mutex. Never set in production.
1819
- onRebuildAfterScanForTest;
1820
- // Test-only seam (NG7Bg, codex P2): fires inside `breakStaleRebuildLock` AFTER it
1821
- // has judged the lock stale and captured its identity, but BEFORE the final
1822
- // re-validation+unlink. A deterministic test installs a hook here to REPLACE the
1823
- // lock file (a fresh holder created a new lock in the race window) and assert the
1824
- // break is skipped — the replacement's active lock is not deleted. Never set in
1825
- // production.
1826
- onBeforeBreakStaleUnlinkForTest;
1827
- // Normalized (trimmed) default namespace identity (NH-FH, cursor Medium).
1828
- // Catalog records key namespaces by their NORMALIZED identity
1829
- // (`normalizeNamespaceIdentity`), but several default-namespace exemptions and
1830
- // memoryDir-ownership checks compared against the RAW `config.defaultNamespace`.
1831
- // If the configured default name carries surrounding whitespace the record key
1832
- // is trimmed while the comparison string is not, so the default row is
1833
- // misclassified, dropped at read time, or given the wrong storage root. Compare
1834
- // against this normalized form everywhere instead.
1835
- defaultNamespaceIdentity;
1836
- /** Whether the catalog is active (namespaces enabled and catalog not opted out). */
1837
- get enabled() {
1838
- return isCatalogEnabled(this.config);
1839
- }
1840
- // ── Public enumeration API ──────────────────────────────────────────────
1841
- /**
1842
- * Sanitize a record at the enumeration boundary (round 5, cursor Medium + codex
1843
- * P2; round 6 — NDXHe). Reads return whatever is in `namespaces.jsonl` after
1844
- * schema checks only, so a tampered or pre-fix row could surface unsafe data to
1845
- * maintenance/QMD until a rewrite occurs. Two distinct defenses:
1846
- *
1847
- * 1. UNSAFE NAMESPACE NAME (NGZqr, codex P2): an unsafe non-default namespace
1848
- * (e.g. `../evil`, a name with separators, or >64 chars) is REJECTED outright
1849
- * — return `null` so the caller drops it. The disk SCAN and the hot touch
1850
- * path both reject such names with the SAME default-exempt `isSafeRouteNamespace`
1851
- * gate, so the read boundary MUST agree, or `listNamespaces()`/`getNamespaceRecord()`
1852
- * would expose a namespace those paths reject (note `isStorageDirForNamespace`
1853
- * can still build a tokenized root even for `../evil`, so storageDir sanitation
1854
- * alone does not catch it). The default namespace is exempt (it may be a
1855
- * non-route literal), matching every other validation site.
1856
- *
1857
- * 2. UNSAFE storageDir: for an otherwise-valid namespace, apply the SAME contract
1858
- * as the write path — full containment (`isContainedStorageDir`: lexical +
1859
- * symlink/realpath) AND namespace ownership (`isStorageDirForNamespace`). When
1860
- * a record fails EITHER check we substitute the trusted resolved-and-safe root
1861
- * for that namespace (rule 42: read and write stay symmetric).
1862
- */
1863
- async sanitizeRecordForRead(record) {
1864
- if (record.namespace !== this.defaultNamespaceIdentity && !isSafeRouteNamespace(record.namespace)) {
1865
- return null;
1866
- }
1867
- if (await this.isContainedStorageDir(record.storageDir) && await this.isStorageDirForNamespace(record.namespace, record.storageDir)) {
1868
- return record;
1869
- }
1870
- const safe = await this.resolveSafeStorageDir(record.namespace);
1871
- return { ...record, storageDir: safe };
1872
- }
1873
- storageRootOwnershipRank(record, resolvedStorageDir, configured) {
1874
- if (resolvedStorageDir === path2.resolve(this.memoryDir)) {
1875
- return record.namespace === this.defaultNamespaceIdentity ? 0 : 3;
1876
- }
1877
- const leaf = path2.basename(resolvedStorageDir);
1878
- const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
1879
- if (tokenOwnsRoot && configured.has(record.namespace)) {
1880
- return 0;
1881
- }
1882
- if (record.namespace === leaf) return 1;
1883
- if (tokenOwnsRoot) return 2;
1884
- return 3;
1885
- }
1886
- configuredNamespaceIdentities() {
1887
- return new Set(
1888
- [
1889
- this.config.defaultNamespace,
1890
- this.config.sharedNamespace,
1891
- ...this.config.namespacePolicies.map((p) => p.name)
1892
- ].map((n) => normalizeNamespaceIdentity(n)).filter((n) => n.length > 0)
1893
- );
1894
- }
1895
- preferStorageRootOwner(current, candidate, resolvedStorageDir, configured) {
1896
- const currentRank = this.storageRootOwnershipRank(current, resolvedStorageDir, configured);
1897
- const candidateRank = this.storageRootOwnershipRank(candidate, resolvedStorageDir, configured);
1898
- if (candidateRank < currentRank) return candidate;
1899
- if (candidateRank > currentRank) return current;
1900
- const byName = candidate.namespace.localeCompare(current.namespace);
1901
- if (byName < 0) return candidate;
1902
- if (byName > 0) return current;
1903
- return candidate.identityToken.localeCompare(current.identityToken) < 0 ? candidate : current;
1904
- }
1905
- dropDuplicateStorageRootAliases(records) {
1906
- const byStorageDir = /* @__PURE__ */ new Map();
1907
- const configured = this.configuredNamespaceIdentities();
1908
- for (const record of records) {
1909
- const resolvedStorageDir = path2.resolve(record.storageDir);
1910
- const current = byStorageDir.get(resolvedStorageDir);
1911
- if (!current) {
1912
- byStorageDir.set(resolvedStorageDir, record);
1913
- continue;
1914
- }
1915
- const owner = this.preferStorageRootOwner(current, record, resolvedStorageDir, configured);
1916
- const alias = owner === current ? record : current;
1917
- byStorageDir.set(resolvedStorageDir, mergeNewerTouchFields(owner, alias));
1918
- }
1919
- return [...byStorageDir.values()];
1920
- }
1921
- async loadSanitizedRecords() {
1922
- const records = await this.loadCompacted();
1923
- const sanitized = await Promise.all(
1924
- [...records.values()].map((r) => this.sanitizeRecordForRead(r))
1925
- );
1926
- return this.dropDuplicateStorageRootAliases(
1927
- sanitized.filter((r) => r !== null)
1928
- );
1929
- }
1930
- async listNamespaces(filter) {
1931
- if (!this.enabled) return [];
1932
- let out = await this.loadSanitizedRecords();
1933
- if (filter?.kind) out = out.filter((r) => r.kind === filter.kind);
1934
- if (filter?.discoveredBy) out = out.filter((r) => r.discoveredBy === filter.discoveredBy);
1935
- if (filter?.writtenSince) {
1936
- const sinceMs = filter.writtenSince.getTime();
1937
- out = out.filter((r) => {
1938
- if (!r.lastWriteAt) return false;
1939
- const ms = Date.parse(r.lastWriteAt);
1940
- return Number.isFinite(ms) && ms >= sinceMs;
1941
- });
1942
- }
1943
- return out.sort((a, b) => {
1944
- const byName = a.namespace.localeCompare(b.namespace);
1945
- if (byName !== 0) return byName;
1946
- return a.identityToken.localeCompare(b.identityToken);
1947
- });
1948
- }
1949
- async getNamespaceRecord(namespace) {
1950
- if (!this.enabled) return null;
1951
- const ns = normalizeNamespaceIdentity(namespace);
1952
- return (await this.loadSanitizedRecords()).find((record) => record.namespace === ns) ?? null;
1953
- }
1954
- // ── Touch API (cheap, failure-tolerant) ─────────────────────────────────
1955
- async markRead(namespace, metadata) {
1956
- await this.touch(namespace, "read", metadata);
1957
- }
1958
- async markWrite(namespace, metadata) {
1959
- await this.touch(namespace, "write", metadata);
1960
- }
1961
- async markMaintenance(namespace, jobName, at) {
1962
- if (typeof jobName !== "string" || jobName.trim().length === 0) {
1963
- throw new Error("markMaintenance requires a non-empty jobName");
1964
- }
1965
- await this.touch(namespace, "maintenance", { at }, jobName.trim());
1966
- }
1967
- /**
1968
- * Register namespaces known purely from config (default, shared, explicit
1969
- * policies). Source `config`. Cheap and idempotent.
1970
- */
1971
- async registerConfiguredNamespaces() {
1972
- if (!this.enabled) return;
1973
- const names = /* @__PURE__ */ new Set([
1974
- this.config.defaultNamespace,
1975
- this.config.sharedNamespace,
1976
- ...this.config.namespacePolicies.map((p) => p.name)
1977
- ]);
1978
- for (const ns of names) {
1979
- if (!ns) continue;
1980
- if (normalizeNamespaceIdentity(ns) !== this.defaultNamespaceIdentity && !isSafeRouteNamespace(ns)) {
1981
- continue;
1982
- }
1983
- try {
1984
- await this.register(ns, { discoveredBy: "config" });
1985
- } catch {
1986
- }
1987
- }
1988
- }
1989
- /**
1990
- * Register a namespace whose storage was just resolved by the router. Used as
1991
- * the router's integration hook (`discoveredBy: config`). Storage dir is
1992
- * provided so we do not re-resolve it. Failure-tolerant. Returns whether the
1993
- * registration actually APPENDED (round 6, codex P2 — NEFoX), so the router's
1994
- * resolve-hook dedup only marks a namespace notified when it truly persisted —
1995
- * a dropped append (disabled catalog or rebuild-lock-timeout drop) returns
1996
- * `false` and is retried on the next resolve.
1997
- */
1998
- async registerResolved(namespace, storageDir) {
1999
- if (!this.enabled) return false;
2000
- return this.register(namespace, { discoveredBy: "config", storageDir });
2001
- }
2002
- /**
2003
- * Generic register/touch without changing read/write timestamps unless the
2004
- * source implies it. Validates the namespace and resolves a storage dir.
2005
- * Returns whether the touch actually appended.
2006
- */
2007
- async register(namespace, metadata) {
2008
- return this.touch(namespace, "register", metadata);
2009
- }
2010
- validateNamespace(namespace) {
2011
- const ns = normalizeNamespaceIdentity(namespace);
2012
- if (ns.length === 0) throw new Error("empty namespace");
2013
- if (ns !== this.defaultNamespaceIdentity && !isSafeRouteNamespace(ns)) {
2014
- throw new Error(`unsafe namespace: ${ns}`);
2015
- }
2016
- return ns;
2017
- }
2018
- /**
2019
- * Resolve the on-disk storage dir for a namespace WITHOUT trusting caller
2020
- * input. The default namespace may use the legacy memoryDir root; everything
2021
- * else lives under `<memoryDir>/namespaces/<token>`. Containment is enforced
2022
- * by rejecting separators/parent-refs in the token.
2023
- */
2024
- resolveStorageDir(namespace) {
2025
- if (normalizeNamespaceIdentity(namespace) === this.defaultNamespaceIdentity) {
2026
- return this.memoryDir;
2027
- }
2028
- const token = namespaceIdentityToken(namespace);
2029
- return this.namespaceTokenDir(token);
2030
- }
2031
- namespaceTokenDir(token) {
2032
- if (token.length === 0 || token.includes("/") || token.includes("\\") || token.includes("..") || path2.isAbsolute(token)) {
2033
- throw new Error(`unsafe namespace token: ${token}`);
2034
- }
2035
- return path2.join(this.memoryDir, "namespaces", token);
2036
- }
2037
- /**
2038
- * Whether a candidate storage dir is LEXICALLY contained: it is either the
2039
- * legacy default root (`memoryDir`) or a strict descendant of
2040
- * `<memoryDir>/namespaces/`. The router legitimately resolves a namespace to
2041
- * EITHER the tokenized dir or a legacy raw-name dir under `namespaces/`, so we
2042
- * accept any contained child rather than a single exact token path. This is a
2043
- * pure string check — symlink escape is checked separately via realpath.
2044
- */
2045
- isLexicallyContained(candidate) {
2046
- const resolved = path2.resolve(candidate);
2047
- if (resolved === path2.resolve(this.memoryDir)) return true;
2048
- const nsBase = path2.resolve(path2.join(this.memoryDir, "namespaces"));
2049
- const rel = path2.relative(nsBase, resolved);
2050
- return rel.length > 0 && !rel.startsWith("..") && !path2.isAbsolute(rel);
2051
- }
2052
- /**
2053
- * Whether a candidate storage dir satisfies the catalog containment contract,
2054
- * including SYMLINK-escape rejection (round 5, codex P2). A lexically-contained
2055
- * path that is actually a symlink to an outside directory would let maintenance
2056
- * or QMD follow it outside `memoryDir`. We mirror `rebuildFromDisk`'s posture:
2057
- * the path must be lexically contained AND, if it exists on disk, neither the
2058
- * path itself a symlink nor its realpath escaping the memory root. Non-existent
2059
- * paths pass the realpath stage (nothing to follow yet) but still must be
2060
- * lexically contained.
2061
- */
2062
- async isContainedStorageDir(candidate) {
2063
- if (!this.isLexicallyContained(candidate)) return false;
2064
- if (path2.resolve(candidate) === path2.resolve(this.memoryDir)) return true;
2065
- let memoryReal;
2066
- try {
2067
- memoryReal = await realpath(this.memoryDir);
2068
- } catch {
2069
- memoryReal = path2.resolve(this.memoryDir);
2070
- }
2071
- if (await this.hasSymlinkedAncestor(candidate)) return false;
2072
- try {
2073
- const stat4 = await lstat(candidate);
2074
- if (stat4.isSymbolicLink()) return false;
2075
- if (!stat4.isDirectory()) return false;
2076
- } catch {
2077
- return this.isNearestExistingAncestorContained(candidate, memoryReal);
2078
- }
2079
- try {
2080
- const real = await realpath(candidate);
2081
- return isPathInside(memoryReal, real);
2082
- } catch {
2083
- return false;
2084
- }
2085
- }
2086
- /**
2087
- * Reject a candidate whose path crosses a SYMLINKED ancestor strictly between
2088
- * memoryDir and the leaf (codex NVuq5). `realpath`-based containment accepts a
2089
- * symlinked `<memoryDir>/namespaces` that currently resolves back inside
2090
- * memoryDir, but the disk scanner rejects such a root and a later retarget would
2091
- * escape the memory tree — so refuse it here too. The leaf itself is
2092
- * symlink-checked by the caller; this walks only the intermediate ancestors.
2093
- */
2094
- async hasSymlinkedAncestor(candidate) {
2095
- const stopAt = path2.resolve(this.memoryDir);
2096
- let dir = path2.dirname(path2.resolve(candidate));
2097
- const root = path2.parse(dir).root;
2098
- while (dir !== stopAt && dir !== root && dir !== path2.dirname(dir)) {
2099
- try {
2100
- if ((await lstat(dir)).isSymbolicLink()) return true;
2101
- } catch {
2102
- }
2103
- dir = path2.dirname(dir);
2104
- }
2105
- return false;
2106
- }
2107
- /**
2108
- * Walk up from a not-yet-existing candidate to the nearest ancestor that exists
2109
- * on disk and verify its realpath stays inside `memoryReal` (round 6, codex P2
2110
- * — NDo79). Rejects a non-existent leaf whose existing parent chain escapes
2111
- * memoryDir via a symlink. Stops at memoryDir's resolved root.
2112
- *
2113
- * The nearest existing ancestor must also be a DIRECTORY (NHIdt, codex P2): if
2114
- * an existing parent such as `<memoryDir>/namespaces` is a regular FILE (or
2115
- * socket/fifo), `realpath(parent)` still succeeds and resolves inside memoryDir,
2116
- * so a containment-only check would ACCEPT a leaf that can never be created — you
2117
- * cannot mkdir a child under a file. We `lstat` the nearest existing ancestor and
2118
- * reject when it is not a directory, mirroring the leaf non-directory rejection
2119
- * (NF21i) and the disk scan, so every containment consumer agrees.
2120
- */
2121
- async isNearestExistingAncestorContained(candidate, memoryReal) {
2122
- let dir = path2.resolve(candidate);
2123
- const root = path2.parse(dir).root;
2124
- for (; ; ) {
2125
- const parent = path2.dirname(dir);
2126
- if (parent === dir || dir === root) return false;
2127
- let real;
2128
- try {
2129
- real = await realpath(parent);
2130
- } catch {
2131
- dir = parent;
2132
- continue;
2133
- }
2134
- if (!(isPathInside(memoryReal, real) || real === memoryReal)) return false;
2135
- try {
2136
- const stat4 = await lstat(real);
2137
- return stat4.isDirectory();
2138
- } catch {
2139
- return false;
2140
- }
2141
- }
2142
- }
2143
- /**
2144
- * Resolve the storage dir to persist for a touch, validating any caller-
2145
- * provided `metadata.storageDir` against the catalog containment contract
2146
- * (round 4 + round 5, codex P2). `markWrite`/`registerResolved` accept an
2147
- * explicit storageDir, but persisting it verbatim would let a bad hook or
2148
- * external consumer write an arbitrary path — including one outside `memoryDir`
2149
- * or a symlink that escapes it — into the catalog, handing maintenance/QMD an
2150
- * unsafe root. We accept an explicit (or previously-stored) dir ONLY when it
2151
- * stays contained under memoryDir (lexically AND via realpath); otherwise we
2152
- * drop it and fall back to the trusted resolved dir.
2153
- */
2154
- async resolveTouchStorageDir(namespace, explicit, existingDir) {
2155
- if (explicit !== void 0 && await this.isContainedStorageDir(explicit) && await this.isStorageDirForNamespace(namespace, explicit)) {
2156
- return explicit;
2157
- }
2158
- if (existingDir !== void 0 && await this.isContainedStorageDir(existingDir) && await this.isStorageDirForNamespace(namespace, existingDir)) {
2159
- return existingDir;
2160
- }
2161
- return this.resolveSafeStorageDir(namespace);
2162
- }
2163
- /**
2164
- * Whether `candidate` is a legitimate storage root FOR `namespace` (round 6,
2165
- * codex P2 — NDATT). Accepts the namespace's router-resolved root, its canonical
2166
- * lexical tokenized dir, and (for the default namespace only) memoryDir. This
2167
- * prevents a contained-but-CROSS-NAMESPACE path — another namespace's tree, or
2168
- * memoryDir for a non-default namespace — from being persisted as this
2169
- * namespace's root. Compared on resolved (absolute) paths.
2170
- */
2171
- async isStorageDirForNamespace(namespace, candidate) {
2172
- const resolvedCandidate = path2.resolve(candidate);
2173
- const valid = /* @__PURE__ */ new Set();
2174
- try {
2175
- valid.add(path2.resolve(this.namespaceTokenDir(namespaceIdentityToken(namespace))));
2176
- } catch {
2177
- }
2178
- try {
2179
- valid.add(path2.resolve(this.namespaceTokenDir(namespace)));
2180
- } catch {
2181
- }
2182
- try {
2183
- valid.add(path2.resolve(await resolveNamespaceStorageRoot(this.config, namespace)));
2184
- } catch {
2185
- }
2186
- if (normalizeNamespaceIdentity(namespace) === this.defaultNamespaceIdentity) {
2187
- valid.add(path2.resolve(this.memoryDir));
2188
- try {
2189
- valid.add(path2.resolve(await resolveDefaultNamespaceRoot(this.config)));
2190
- } catch {
2191
- }
2192
- }
2193
- return valid.has(resolvedCandidate);
2194
- }
2195
- /**
2196
- * Resolve the canonical storage dir for a namespace as the LIVE ROUTER would,
2197
- * but NEVER return a path that escapes the memory root.
2198
- *
2199
- * Router alignment (round 4, cursor Medium): a read/register touch with no
2200
- * explicit storageDir previously used the lexical `resolveStorageDir`, which
2201
- * always picks `<memoryDir>/namespaces/<token>` (or `memoryDir` for the
2202
- * default). That diverges from `NamespaceStorageRouter`, which can route to a
2203
- * legacy raw-name dir or a migrated default root — so a recall touch could
2204
- * record a contained-but-WRONG root that maintenance/rebuild then targets. We
2205
- * now delegate to the shared `resolveNamespaceStorageRoot` (the very helper the
2206
- * router uses) so the catalog records the same on-disk root the router serves.
2207
- *
2208
- * Containment (round 5, codex P2): the resolved path can still be a symlink
2209
- * escaping memoryDir, so we run the full (lexical + realpath) containment
2210
- * contract. When it FAILS we fall back to a NAMESPACE-SPECIFIC safe root, NOT
2211
- * a blanket `memoryDir`. Recording `memoryDir` for a non-default namespace
2212
- * would point enumeration/maintenance at the DEFAULT namespace's tree (round 5,
2213
- * cursor/codex Medium/P2) — a cross-namespace fanout error. The correct safe
2214
- * root is the namespace's own lexical tokenized dir
2215
- * (`<memoryDir>/namespaces/<token>`), which is always contained and is that
2216
- * namespace's canonical location (we record the lexical PATH as metadata; we do
2217
- * not follow the escaping symlink). Only the default namespace — or a token so
2218
- * unsafe even the lexical dir cannot be built — falls back to `memoryDir`.
2219
- */
2220
- async resolveSafeStorageDir(namespace) {
2221
- let resolved;
2222
- try {
2223
- resolved = await resolveNamespaceStorageRoot(this.config, namespace);
2224
- } catch {
2225
- return this.safeFallbackStorageDir(namespace);
2226
- }
2227
- if (await this.isContainedStorageDir(resolved)) return resolved;
2228
- return this.safeFallbackStorageDir(namespace);
2229
- }
2230
- /**
2231
- * The namespace-specific contained fallback root, used when the router-resolved
2232
- * root fails containment (round 5, cursor/codex Medium/P2).
2233
- *
2234
- * Preference order:
2235
- * 1. The namespace's OWN lexical tokenized dir (`namespaces/<token>`) — so a
2236
- * non-default namespace is NOT pointed at the DEFAULT namespace's `memoryDir`
2237
- * tree (which would misdirect maintenance fanout). Returned only when the
2238
- * token dir itself stays CONTAINED (it is not a symlink, and its realpath
2239
- * does not escape memoryDir — e.g. via a symlinked `namespaces/` parent).
2240
- * 2. `memoryDir` as a LAST resort — for the default namespace, an unsafe token
2241
- * that cannot build a contained path, OR the irreparable case where the
2242
- * token dir's realpath escapes the root (so even its lexical path resolves
2243
- * outside). NF21m note (codex P2): we deliberately do NOT record the lexical
2244
- * token dir in that irreparable case — its realpath escapes memoryDir, and
2245
- * the NDo79 contract REQUIRES that an escaping path is never persisted (a
2246
- * later mkdir/maintenance/QMD op would follow it outside the root). Since no
2247
- * contained namespace-specific path exists, containment wins: `memoryDir` is
2248
- * the only safe root left. A namespace whose token dir's realpath escapes is
2249
- * an irreparable on-disk state; recording the contained default root is
2250
- * strictly safer than persisting an escaping one. The common case where the
2251
- * token dir IS contained is handled by branch 1, so a healthy non-default
2252
- * namespace never reaches `memoryDir`.
2253
- */
2254
- async safeFallbackStorageDir(namespace) {
2255
- if (normalizeNamespaceIdentity(namespace) === this.defaultNamespaceIdentity) return this.memoryDir;
2256
- let tokenDir;
2257
- try {
2258
- tokenDir = this.namespaceTokenDir(namespaceIdentityToken(namespace));
2259
- } catch {
2260
- return this.memoryDir;
2261
- }
2262
- if (await this.isContainedStorageDir(tokenDir)) return tokenDir;
2263
- return this.memoryDir;
2264
- }
2265
- /**
2266
- * Re-check, NOW, whether a namespace's storage root currently EXISTS on disk
2267
- * with the SAME safety the directory scan uses (NFJV8, codex P2).
2268
- *
2269
- * The rebuild's final re-merge runs under the held lock and folds the freshly
2270
- * re-read log (`latest`) into the scanned `rebuilt` set. A namespace present in
2271
- * `latest` (a live touch row) but ABSENT from `rebuilt` is normally PURGED as
2272
- * deleted (the NATqU "disk scan is authoritative" rule). But there is a TOCTOU
2273
- * window: a dynamic namespace can be CREATED on disk AFTER `rebuildFromDisk()`
2274
- * already enumerated `namespaces/` but BEFORE this re-merge. The scan snapshot
2275
- * missed its new root, yet a gateway `markWrite` already appended a row for it.
2276
- * Blindly purging that row would rewrite the catalog WITHOUT a live namespace
2277
- * that now has data on disk, so `writtenSince`/maintenance/QMD consumers miss
2278
- * it until another touch or rebuild.
2279
- *
2280
- * So before purging, we re-resolve the namespace's safe storage root (the same
2281
- * router-aligned, containment-checked path the scan would have catalogued) and
2282
- * confirm it is a real, contained, non-symlink directory that actually holds
2283
- * memory data RIGHT NOW. If so the namespace was created-after-scan and is LIVE
2284
- * — KEEP its row. This is the precise inverse of NATqU and does NOT reintroduce
2285
- * it: a touch on a REMOVED root re-checks as ABSENT (no data on disk) and is
2286
- * still purged; only a root that EXISTS on a fresh re-check is kept.
2287
- *
2288
- * Mirrors the per-entry scan checks (symlink rejection + realpath containment +
2289
- * `hasMemoryData`) so a symlinked/escaping root is never resurrected.
2290
- */
2291
- async liveStorageRootExistsForRebuild(namespace, memoryReal) {
2292
- let root;
2293
- try {
2294
- root = await this.resolveSafeStorageDir(namespace);
2295
- } catch {
2296
- return false;
2297
- }
2298
- if (normalizeNamespaceIdentity(namespace) !== this.defaultNamespaceIdentity && path2.resolve(root) === path2.resolve(this.memoryDir)) {
2299
- return false;
2300
- }
2301
- let stat4;
2302
- try {
2303
- stat4 = await lstat(root);
2304
- } catch {
2305
- return false;
2306
- }
2307
- if (stat4.isSymbolicLink()) return false;
2308
- if (!stat4.isDirectory()) return false;
2309
- try {
2310
- const real = await realpath(root);
2311
- if (memoryReal && !isPathInside(memoryReal, real)) return false;
2312
- } catch {
2313
- return false;
2314
- }
2315
- return hasMemoryData(root);
2316
- }
2317
- /**
2318
- * Record a namespace touch. Returns whether the touch actually APPENDED to the
2319
- * log (round 6, codex P2 — NEFoX): a disabled catalog or a dropped append (the
2320
- * NAUf7 rebuild-lock-timeout drop) returns `false`, so callers (e.g. the router
2321
- * resolve-hook dedup) can avoid marking a dropped registration as completed and
2322
- * suppressing its retry.
2323
- */
2324
- async touch(namespace, kind, metadata, jobName) {
2325
- if (!this.enabled) return false;
2326
- const ns = this.validateNamespace(namespace);
2327
- const nowIso = (metadata?.at ?? /* @__PURE__ */ new Date()).toISOString();
2328
- return this.queueCritical(
2329
- async () => this.withHeldCatalogLock(async (acquired) => {
2330
- if (!acquired) return false;
2331
- if (this.onTouchCriticalSectionForTest) {
2332
- await this.onTouchCriticalSectionForTest();
2333
- }
2334
- const records = await this.loadCompacted();
2335
- const existing = records.get(ns);
2336
- const storageDir = await this.resolveTouchStorageDir(
2337
- ns,
2338
- metadata?.storageDir,
2339
- existing?.storageDir
2340
- );
2341
- const record = existing ? { ...existing } : {
2342
- namespace: ns,
2343
- identityToken: namespaceIdentityToken(ns),
2344
- kind: metadata?.kind ?? inferKind(ns, this.config),
2345
- createdAt: nowIso,
2346
- storageDir,
2347
- discoveredBy: metadata?.discoveredBy ?? (kind === "register" ? "config" : kind === "maintenance" ? "scan" : kind)
2348
- };
2349
- record.storageDir = storageDir;
2350
- if (metadata?.kind) record.kind = metadata.kind;
2351
- if (metadata?.principal !== void 0) record.principal = metadata.principal;
2352
- if (metadata?.projectId !== void 0) record.projectId = metadata.projectId;
2353
- if (metadata?.branch !== void 0) record.branch = metadata.branch;
2354
- if (metadata?.parentNamespace !== void 0)
2355
- record.parentNamespace = metadata.parentNamespace;
2356
- if (kind === "write" && existing && record.discoveredBy === "config") {
2357
- record.discoveredBy = "write";
2358
- }
2359
- if (kind === "read") record.lastReadAt = nowIso;
2360
- if (kind === "write") record.lastWriteAt = nowIso;
2361
- if (kind === "maintenance" && jobName) {
2362
- record.lastMaintenanceAt = { ...record.lastMaintenanceAt ?? {}, [jobName]: nowIso };
2363
- }
2364
- await this.appendUnchained(record);
2365
- return true;
2366
- })
2367
- );
2368
- }
2369
- // ── Rebuild from disk ────────────────────────────────────────────────────
2370
- async rebuildFromDisk(options) {
2371
- const dryRun = options?.dryRun === true;
2372
- if (!this.enabled) {
2373
- return { dryRun, records: [], skipped: [], applied: false };
2374
- }
2375
- if (dryRun) {
2376
- return this.queueCritical(async () => this.rebuildInsideChain(dryRun, false));
2377
- }
2378
- return this.queueCritical(async () => this.rebuildInsideChain(dryRun, true));
2379
- }
2380
- /**
2381
- * Body of `rebuildFromDisk`, run inside a single `queueCritical` turn. MUST
2382
- * only be invoked from within the serialized chain so the load and the
2383
- * rewrite are atomic with respect to concurrent touches (in-process).
2384
- *
2385
- * `wantMutate` is true for an `--apply` (the caller intends to rewrite). The
2386
- * cross-process file lock is acquired LATE — only around the final
2387
- * load→merge→rename window (NFgCT, codex P2) — never across the disk scan, so a
2388
- * long scan does not force concurrent gateway touches to wait (and drop their
2389
- * append). Whether the rewrite actually happened is reported via the result's
2390
- * `applied`: true only when `wantMutate` AND the lock was acquired.
2391
- */
2392
- async rebuildInsideChain(dryRun, wantMutate) {
2393
- const existing = await this.loadCompacted();
2394
- const skipped = [];
2395
- const rebuilt = /* @__PURE__ */ new Map();
2396
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2397
- let memoryReal = null;
2398
- try {
2399
- memoryReal = await realpath(this.memoryDir);
2400
- } catch {
2401
- memoryReal = this.memoryDir;
2402
- }
2403
- const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
2404
- const configured = new Set(
2405
- [
2406
- this.config.defaultNamespace,
2407
- this.config.sharedNamespace,
2408
- ...this.config.namespacePolicies.map((p) => p.name)
2409
- ].map((n) => normalizeNamespaceIdentity(n)).filter((n) => n.length > 0)
2410
- );
2411
- const resolvedDefaultRoot = await resolveDefaultNamespaceRoot(this.config);
2412
- const defaultStorageDir = await this.isContainedStorageDir(resolvedDefaultRoot) ? resolvedDefaultRoot : this.memoryDir;
2413
- const legacyDefaultHasData = defaultStorageDir === this.memoryDir;
2414
- for (const ns of configured) {
2415
- if (!ns) continue;
2416
- if (ns !== defaultNs && !isSafeRouteNamespace(ns)) {
2417
- let token;
2418
- try {
2419
- token = namespaceIdentityToken(ns);
2420
- } catch {
2421
- token = ns;
2422
- }
2423
- skipped.push({ token, reason: "unsafe", detail: ns });
2424
- continue;
2425
- }
2426
- let storageDir;
2427
- if (ns === defaultNs) {
2428
- storageDir = defaultStorageDir;
2429
- } else {
2430
- try {
2431
- storageDir = await resolveNamespaceStorageRoot(this.config, ns);
2432
- } catch {
2433
- storageDir = this.namespaceTokenDir(namespaceIdentityToken(ns));
2434
- }
2435
- }
2436
- if (!await this.isContainedStorageDir(storageDir)) {
2437
- if (ns === defaultNs) {
2438
- storageDir = this.memoryDir;
2439
- } else {
2440
- skipped.push({ token: namespaceIdentityToken(ns), reason: "escape", detail: storageDir });
2441
- continue;
2442
- }
2443
- }
2444
- rebuilt.set(
2445
- ns,
2446
- this.mergeForRebuild(existing.get(ns), {
2447
- namespace: ns,
2448
- identityToken: namespaceIdentityToken(ns),
2449
- kind: inferKind(ns, this.config),
2450
- createdAt: existing.get(ns)?.createdAt ?? nowIso,
2451
- storageDir,
2452
- discoveredBy: "config"
2453
- })
2454
- );
2455
- }
2456
- const namespacesDir = path2.join(this.memoryDir, "namespaces");
2457
- let entries = [];
2458
- let namespacesDirSafe = true;
2459
- try {
2460
- const rootStat = await lstat(namespacesDir);
2461
- if (rootStat.isSymbolicLink()) {
2462
- namespacesDirSafe = false;
2463
- } else {
2464
- const realNamespacesDir = await realpath(namespacesDir);
2465
- if (memoryReal && !isPathInside(memoryReal, realNamespacesDir)) {
2466
- namespacesDirSafe = false;
2467
- }
2468
- }
2469
- } catch {
2470
- namespacesDirSafe = true;
2471
- }
2472
- if (!namespacesDirSafe) {
2473
- skipped.push({ token: "namespaces", reason: "symlink", detail: namespacesDir });
2474
- } else {
2475
- try {
2476
- entries = await readdir(namespacesDir, { withFileTypes: true });
2477
- } catch {
2478
- entries = [];
2479
- }
2480
- }
2481
- const scannedFromTokenized = /* @__PURE__ */ new Set();
2482
- for (const entry of entries) {
2483
- const token = entry.name;
2484
- const fullPath = path2.join(namespacesDir, token);
2485
- let stat4;
2486
- try {
2487
- stat4 = await lstat(fullPath);
2488
- } catch (err) {
2489
- skipped.push({ token, reason: "error", detail: err instanceof Error ? err.message : String(err) });
2490
- continue;
2491
- }
2492
- if (stat4.isSymbolicLink()) {
2493
- skipped.push({ token, reason: "symlink", detail: fullPath });
2494
- continue;
2495
- }
2496
- if (!stat4.isDirectory()) continue;
2497
- try {
2498
- const real = await realpath(fullPath);
2499
- if (memoryReal && !isPathInside(memoryReal, real)) {
2500
- skipped.push({ token, reason: "escape", detail: real });
2501
- continue;
2502
- }
2503
- } catch (err) {
2504
- skipped.push({ token, reason: "error", detail: err instanceof Error ? err.message : String(err) });
2505
- continue;
2506
- }
2507
- const literalRecord = existing.get(token);
2508
- const literalOwnsRoot = configured.has(token) || literalRecord !== void 0 && path2.resolve(literalRecord.storageDir) === path2.resolve(fullPath);
2509
- const tokenDecoded = literalOwnsRoot ? null : namespaceIdentityFromToken(token);
2510
- const rawDecoded = tokenDecoded && tokenDecoded.length > 0 ? tokenDecoded : token;
2511
- const decoded = normalizeNamespaceIdentity(rawDecoded);
2512
- if (decoded.length === 0 || rawDecoded !== decoded) {
2513
- skipped.push({ token, reason: "unsafe", detail: rawDecoded });
2514
- continue;
2515
- }
2516
- if (decoded !== defaultNs && !isSafeRouteNamespace(decoded)) {
2517
- skipped.push({ token, reason: "unsafe", detail: decoded });
2518
- continue;
2519
- }
2520
- const memoryData = await inspectMemoryDataRoot(fullPath);
2521
- if (memoryData.invalidMarker) {
2522
- skipped.push({
2523
- token,
2524
- reason: "unsafe",
2525
- detail: `invalid memory marker: ${memoryData.invalidMarker}`
2526
- });
2527
- continue;
2528
- }
2529
- if (!memoryData.hasData) continue;
2530
- if (decoded === defaultNs) {
2531
- const def = rebuilt.get(defaultNs);
2532
- if (def) {
2533
- def.storageDir = defaultStorageDir;
2534
- def.kind = "default";
2535
- }
2536
- continue;
2537
- }
2538
- const isTokenizedEntry = token === namespaceIdentityToken(decoded);
2539
- if (rebuilt.has(decoded) && scannedFromTokenized.has(decoded) && !isTokenizedEntry) {
2540
- continue;
2541
- }
2542
- if (isTokenizedEntry) scannedFromTokenized.add(decoded);
2543
- const prior = existing.get(decoded);
2544
- rebuilt.set(
2545
- decoded,
2546
- this.mergeForRebuild(prior, {
2547
- namespace: decoded,
2548
- identityToken: namespaceIdentityToken(decoded),
2549
- kind: inferKind(decoded, this.config),
2550
- createdAt: prior?.createdAt ?? nowIso,
2551
- storageDir: fullPath,
2552
- // Configured-and-present namespaces keep config provenance; purely
2553
- // discovered ones are scan.
2554
- discoveredBy: configured.has(decoded) ? "config" : prior?.discoveredBy ?? "scan"
2555
- })
2556
- );
2557
- }
2558
- if (legacyDefaultHasData && defaultStorageDir === this.memoryDir) {
2559
- const def = rebuilt.get(defaultNs);
2560
- if (def) def.kind = "default";
2561
- }
2562
- if (!wantMutate) {
2563
- return this.finishRebuild(rebuilt, skipped, dryRun, false, memoryReal, nowIso);
2564
- }
2565
- if (this.onRebuildAfterScanForTest) {
2566
- await this.onRebuildAfterScanForTest();
2567
- }
2568
- return this.withHeldCatalogLock(
2569
- (acquired) => this.finishRebuild(rebuilt, skipped, dryRun, acquired, memoryReal, nowIso)
2570
- );
2571
- }
2572
- /**
2573
- * Final load→merge→rename window of a rebuild, factored out so the caller can
2574
- * run it WITHIN the cross-process file lock (NFgCT, codex P2) without holding
2575
- * that lock across the preceding disk scan. Re-reads the latest on-disk state,
2576
- * folds concurrent touches, then (when `canMutate`) atomically rewrites the log.
2577
- *
2578
- * `canMutate` records that the cross-process lock was actually held. The
2579
- * re-merge + rewrite run only when it is true — a dry-run, or an unlocked apply
2580
- * (lock-acquisition timeout), computes records but does NOT rename, so it can
2581
- * never clobber a concurrent lock holder's window. `applied` mirrors `canMutate`.
2582
- */
2583
- async finishRebuild(rebuilt, skipped, dryRun, canMutate, memoryReal, nowIso) {
2584
- if (canMutate) {
2585
- const latest = await this.loadCompacted();
2586
- for (const [ns, fresh] of latest) {
2587
- const current = rebuilt.get(ns);
2588
- if (!current) {
2589
- if (ns !== this.defaultNamespaceIdentity && !isSafeRouteNamespace(ns)) {
2590
- continue;
2591
- }
2592
- if (await this.liveStorageRootExistsForRebuild(ns, memoryReal)) {
2593
- const safeDir = await this.resolveSafeStorageDir(ns);
2594
- const resolvedSafe = path2.resolve(safeDir);
2595
- let owningNamespace = null;
2596
- for (const [otherNs, otherRec] of rebuilt) {
2597
- if (otherNs !== ns && path2.resolve(otherRec.storageDir) === resolvedSafe) {
2598
- owningNamespace = otherNs;
2599
- break;
2600
- }
2601
- }
2602
- if (owningNamespace) {
2603
- const owner = rebuilt.get(owningNamespace);
2604
- if (owner) rebuilt.set(owningNamespace, mergeNewerTouchFields(owner, fresh));
2605
- continue;
2606
- }
2607
- rebuilt.set(ns, {
2608
- ...fresh,
2609
- storageDir: safeDir,
2610
- identityToken: namespaceIdentityToken(ns),
2611
- kind: fresh.kind ?? inferKind(ns, this.config),
2612
- createdAt: fresh.createdAt ?? nowIso
2613
- });
2614
- continue;
2615
- }
2616
- continue;
2617
- }
2618
- rebuilt.set(ns, mergeNewerTouchFields(current, fresh));
2619
- }
2620
- }
2621
- const records = [...rebuilt.values()].sort((a, b) => {
2622
- const byName = a.namespace.localeCompare(b.namespace);
2623
- if (byName !== 0) return byName;
2624
- return a.identityToken.localeCompare(b.identityToken);
2625
- });
2626
- if (canMutate) {
2627
- if (this.onRebuildBeforeRenameForTest) {
2628
- await this.onRebuildBeforeRenameForTest();
2629
- }
2630
- await this.rewriteUnchained(records);
2631
- }
2632
- return { dryRun, records, skipped, applied: canMutate };
2633
- }
2634
- // ── Cross-process catalog write lock (held mutex) ────────────────────────
2635
- /**
2636
- * Run `fn` while HOLDING the shared cross-process advisory lock (round 5, codex
2637
- * P2; generalized round 7 — NEZkA). This is the SINGLE mutex shared by BOTH the
2638
- * touch read→merge→append window AND the rebuild final load→merge→rename window,
2639
- * so a touch and a rebuild in different processes are mutually exclusive over
2640
- * their respective critical sections — closing the check-then-append gap where a
2641
- * polled-only touch could append into a rebuild's load→rename window.
2642
- *
2643
- * Acquisition is atomic via `open(..., "wx")`. A lock older than
2644
- * `REBUILD_LOCK_STALE_MS` is treated as a crashed holder and broken. After
2645
- * `REBUILD_LOCK_MAX_WAIT_MS` of contention we proceed best-effort WITHOUT the
2646
- * lock rather than block forever. The lock is always released in `finally`.
2647
- *
2648
- * IN-PROCESS SAFETY: every caller invokes this from inside (or wrapping) the
2649
- * per-process `queueCritical` chain, which serializes all catalog mutations in
2650
- * THIS process. So within one process only one logical holder attempts OS-lock
2651
- * acquisition at a time — the file lock is never self-contended in-process, and
2652
- * the lock is acquired and released within a single in-process turn. The file
2653
- * lock adds only the missing CROSS-process exclusion.
2654
- *
2655
- * HEARTBEAT (round 5, cursor/codex Medium/P2): while WE hold the lock a timer
2656
- * refreshes its mtime every `REBUILD_LOCK_HEARTBEAT_MS`, so a legitimately long
2657
- * holder (> `REBUILD_LOCK_STALE_MS`) is not treated as a crashed holder and
2658
- * unlinked by another process — which would let overlapping windows lose
2659
- * appends. Heartbeat failures are swallowed; the timer is always cleared in
2660
- * `finally`.
2661
- *
2662
- * ACQUISITION RESULT (round 6, codex P2 — NBPmY): `fn` receives whether WE
2663
- * actually hold the lock. When acquisition TIMED OUT (another holder is active),
2664
- * a MUTATING rebuild must NOT perform its load/rename window unlocked, and a
2665
- * touch must NOT append unlocked — both would recreate the lost-append race. The
2666
- * caller uses `acquired` to run compute-only (rebuild) or DROP the append
2667
- * (touch) when unlocked.
2668
- */
2669
- async withHeldCatalogLock(fn) {
2670
- const acquired = await this.acquireRebuildLock();
2671
- let heartbeat;
2672
- if (acquired) {
2673
- heartbeat = setInterval(() => {
2674
- const now = /* @__PURE__ */ new Date();
2675
- utimes(this.rebuildLockPath, now, now).catch(() => void 0);
2676
- }, REBUILD_LOCK_HEARTBEAT_MS);
2677
- heartbeat.unref?.();
2678
- }
2679
- try {
2680
- return await fn(acquired);
2681
- } finally {
2682
- if (heartbeat) clearInterval(heartbeat);
2683
- if (acquired) {
2684
- try {
2685
- if (await this.rebuildLockHeldBySelf()) {
2686
- await unlink(this.rebuildLockPath);
2687
- }
2688
- } catch {
2689
- }
2690
- }
2691
- }
2692
- }
2693
- /** Try to acquire the rebuild lock; returns true if WE created it. */
2694
- async acquireRebuildLock() {
2695
- const deadline = Date.now() + REBUILD_LOCK_MAX_WAIT_MS;
2696
- await mkdir2(this.stateDir, { recursive: true });
2697
- for (; ; ) {
2698
- try {
2699
- const handle = await open(this.rebuildLockPath, "wx");
2700
- try {
2701
- await handle.writeFile(
2702
- `${process.pid} ${this.lockOwnerId} ${(/* @__PURE__ */ new Date()).toISOString()}
2703
- `,
2704
- "utf8"
2705
- );
2706
- } catch {
2707
- } finally {
2708
- await handle.close();
2709
- }
2710
- return true;
2711
- } catch (err) {
2712
- if (err?.code !== "EEXIST") {
2713
- return false;
2714
- }
2715
- await this.breakStaleRebuildLock();
2716
- if (Date.now() >= deadline) return false;
2717
- await new Promise((r) => setTimeout(r, REBUILD_LOCK_POLL_MS));
2718
- }
2719
- }
2720
- }
2721
- /**
2722
- * Remove the lock file if its mtime is older than the stale threshold.
2723
- *
2724
- * REPLACEMENT-SAFE (NG7Bg, codex P2): a plain `stat` → `unlink` has a TOCTOU
2725
- * window — two processes can both observe the SAME stale lock; one removes it and
2726
- * creates a FRESH lock, and the other's later `unlink` then deletes that fresh
2727
- * holder's ACTIVE lock based on the stale identity it read earlier, leaving the
2728
- * fresh holder running its critical section with no visible lock and reopening the
2729
- * lost-update race the mutex prevents. We therefore capture the lock's IDENTITY
2730
- * (its full content line: `<pid> <owner-uuid> <iso>`) when we judge it stale, then
2731
- * RE-READ immediately before unlinking and only remove it when the content is
2732
- * byte-identical AND still stale. A replacement lock has a different owner id /
2733
- * timestamp, so its content differs and we leave it untouched. We never unlink a
2734
- * lock whose mtime is now fresh (a heartbeat refreshed it) or whose identity
2735
- * changed (a replacement was created). This is best-effort: any mismatch/vanish
2736
- * simply skips the break and the caller polls again.
2737
- */
2738
- async breakStaleRebuildLock() {
2739
- let staleIdentity;
2740
- try {
2741
- const info = await stat(this.rebuildLockPath);
2742
- if (Date.now() - info.mtimeMs <= REBUILD_LOCK_STALE_MS) {
2743
- return;
2744
- }
2745
- staleIdentity = await readFile2(this.rebuildLockPath, "utf8");
2746
- } catch {
2747
- return;
2748
- }
2749
- if (this.onBeforeBreakStaleUnlinkForTest) {
2750
- await this.onBeforeBreakStaleUnlinkForTest();
2751
- }
2752
- try {
2753
- const current = await readFile2(this.rebuildLockPath, "utf8");
2754
- if (current !== staleIdentity) return;
2755
- const recheck = await stat(this.rebuildLockPath);
2756
- if (Date.now() - recheck.mtimeMs <= REBUILD_LOCK_STALE_MS) return;
2757
- await unlink(this.rebuildLockPath).catch(() => void 0);
2758
- } catch {
2759
- }
2760
- }
2761
- /**
2762
- * Whether the rebuild lock file was written by THIS instance (round 6, codex
2763
- * P2 — NBsGP). Matches the per-instance owner id, NOT just `process.pid`: two
2764
- * NamespaceCatalog instances in the same process share a PID, so a PID-only
2765
- * check would wrongly treat instance A's lock as self-held by instance B and
2766
- * let B's touch skip the wait and append into A's rebuild window. Falls back to
2767
- * the legacy PID-only form for lock files written before owner ids existed.
2768
- */
2769
- async rebuildLockHeldBySelf() {
2770
- try {
2771
- const body = await readFile2(this.rebuildLockPath, "utf8");
2772
- const parts = body.trim().split(/\s+/);
2773
- const pid = Number.parseInt(parts[0] ?? "", 10);
2774
- const ownerId = parts[1];
2775
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2776
- if (ownerId && UUID_RE.test(ownerId)) {
2777
- return ownerId === this.lockOwnerId;
2778
- }
2779
- return Number.isFinite(pid) && pid === process.pid;
2780
- } catch {
2781
- return false;
2782
- }
2783
- }
2784
- /**
2785
- * Merge a prior record's preserved metadata (timestamps, principal hints)
2786
- * onto a freshly-discovered record. Disk-derived fields (storageDir, kind)
2787
- * take precedence from the new record.
2788
- *
2789
- * PROVENANCE (round 3, cursor Low): `discoveredBy` and `createdAt` are
2790
- * CREATION-ONLY — identical to the touch path's invariant. A rebuild must NOT
2791
- * reset a namespace first seen via a `write`/`read` touch back to `config`
2792
- * just because it is also listed in policies. So when a prior record exists we
2793
- * carry its `discoveredBy` forward; only brand-new records keep the fresh
2794
- * (config/scan) provenance.
2795
- */
2796
- mergeForRebuild(prior, fresh) {
2797
- if (!prior) return fresh;
2798
- const merged = {
2799
- ...fresh,
2800
- createdAt: prior.createdAt ?? fresh.createdAt,
2801
- discoveredBy: prior.discoveredBy ?? fresh.discoveredBy
2802
- };
2803
- if (prior.lastReadAt) merged.lastReadAt = prior.lastReadAt;
2804
- if (prior.lastWriteAt) merged.lastWriteAt = prior.lastWriteAt;
2805
- if (prior.lastMaintenanceAt) merged.lastMaintenanceAt = { ...prior.lastMaintenanceAt };
2806
- if (prior.principal !== void 0) merged.principal = prior.principal;
2807
- if (prior.projectId !== void 0) merged.projectId = prior.projectId;
2808
- if (prior.branch !== void 0) merged.branch = prior.branch;
2809
- if (prior.parentNamespace !== void 0) merged.parentNamespace = prior.parentNamespace;
2810
- return merged;
2811
- }
2812
- // ── Persistence ──────────────────────────────────────────────────────────
2813
- /** Load the JSONL log and fold it into current state (last-record-wins). */
2814
- async loadCompacted() {
2815
- const records = /* @__PURE__ */ new Map();
2816
- let raw;
2817
- try {
2818
- raw = await readFile2(this.catalogPath, "utf8");
2819
- } catch {
2820
- return records;
2821
- }
2822
- for (const line of raw.split("\n")) {
2823
- const trimmed = line.trim();
2824
- if (trimmed.length === 0) continue;
2825
- let parsed;
2826
- try {
2827
- parsed = JSON.parse(trimmed);
2828
- } catch {
2829
- continue;
2830
- }
2831
- const record = coerceRecord(parsed);
2832
- if (!record) continue;
2833
- const prior = records.get(record.namespace);
2834
- records.set(record.namespace, prior ? mergeNewerTouchFields(record, prior) : record);
2835
- }
2836
- return records;
2837
- }
2838
- /**
2839
- * Serialize an arbitrary read-modify-write critical section through the single
2840
- * write chain. Every catalog mutation (touch read+merge+append, full rewrite)
2841
- * runs through this so they are mutually exclusive: a touch always reads the
2842
- * latest persisted state before appending, and a rebuild rewrite cannot
2843
- * interleave with a touch's append. The chain recovers from rejection
2844
- * (CLAUDE.md rule #40) — one failed section never poisons subsequent ones —
2845
- * while still surfacing the error to that section's awaited promise.
2846
- */
2847
- queueCritical(fn) {
2848
- const run = this.writeChain.then(fn);
2849
- this.writeChain = run.then(
2850
- () => void 0,
2851
- () => void 0
2852
- );
2853
- return run;
2854
- }
2855
- /**
2856
- * Append a single record to the JSONL log WITHOUT re-serializing through the
2857
- * write chain. MUST only be called from inside a `queueCritical(...)` section
2858
- * (which already holds the serialized turn); calling it directly would bypass
2859
- * the read-before-append ordering that prevents lost-field races.
2860
- */
2861
- async appendUnchained(record) {
2862
- const line = serializeRecord(record) + "\n";
2863
- await mkdir2(this.stateDir, { recursive: true });
2864
- await appendFile(this.catalogPath, line, "utf8");
2865
- }
2866
- /**
2867
- * Atomic temp-file + rename rewrite (CLAUDE.md rule #54: write temp, then
2868
- * rename — never delete-before-write) WITHOUT re-entering the write chain.
2869
- * MUST only be called from inside a `queueCritical(...)` turn (e.g. the
2870
- * rebuild critical section, which already holds the serialized turn so its
2871
- * load and rewrite are atomic against concurrent touches). Re-entering the
2872
- * chain from within a held turn would deadlock.
2873
- */
2874
- async rewriteUnchained(records) {
2875
- const body = records.map((r) => serializeRecord(r)).join("\n") + (records.length > 0 ? "\n" : "");
2876
- await mkdir2(this.stateDir, { recursive: true });
2877
- const tmp = `${this.catalogPath}.${process.pid}.${Date.now()}.tmp`;
2878
- await writeFile2(tmp, body, "utf8");
2879
- await rename(tmp, this.catalogPath);
2880
- }
2881
- };
2882
- function isPathInside(root, child) {
2883
- const relative = path2.relative(root, child);
2884
- return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
2885
- }
2886
-
2887
- // src/maintenance/namespace-planner.ts
2888
- import { createHash, randomUUID as randomUUID2 } from "crypto";
2889
- import { lstat as lstat2, mkdir as mkdir3, open as open2, readFile as readFile3, readdir as readdir2, rename as rename2, rm, rmdir, utimes as utimes2, writeFile as writeFile3 } from "fs/promises";
2890
- import path3 from "path";
2891
- var DEFAULT_MAX_NAMESPACES_PER_CYCLE = 20;
2892
- var DEFAULT_LOCK_STALE_MS = 10 * 6e4;
2893
- var LOCK_BASE = "maintenance-locks";
2894
- var STATUS_BASE = "namespace-maintenance-status";
2895
- var namespaceMaintenanceFs = { open: open2, rm };
2896
- function configuredNamespaces(config) {
2897
- return Array.from(
2898
- new Set(
2899
- [config.defaultNamespace, config.sharedNamespace, ...config.namespacePolicies.map((policy) => policy.name)].map((value) => value.trim()).filter(Boolean)
2900
- )
2901
- );
2902
- }
2903
- function inferConfiguredKind(config, namespace) {
2904
- if (namespace === config.defaultNamespace.trim()) return "default";
2905
- if (namespace === config.sharedNamespace.trim()) return "shared";
2906
- return "explicit";
2907
- }
2908
- function maxNamespacesPerCycle(config) {
2909
- return Math.max(
2910
- 1,
2911
- Math.floor(
2912
- typeof config.maintenanceMaxNamespacesPerCycle === "number" && Number.isFinite(config.maintenanceMaxNamespacesPerCycle) ? config.maintenanceMaxNamespacesPerCycle : DEFAULT_MAX_NAMESPACES_PER_CYCLE
2913
- )
2914
- );
2915
- }
2916
- function namespaceKindAllowed(config, kind) {
2917
- switch (kind) {
2918
- case "branch":
2919
- return config.maintenanceIncludeBranchNamespaces === true;
2920
- case "project":
2921
- return config.maintenanceIncludeProjectNamespaces !== false;
2922
- case "team-project":
2923
- return config.maintenanceIncludeTeamProjectNamespaces !== false;
2924
- default:
2925
- return true;
2926
- }
2927
- }
2928
- function disabledReasonForKind(kind) {
2929
- if (kind === "branch") return "branch_disabled";
2930
- if (kind === "project") return "project_disabled";
2931
- if (kind === "team-project") return "team_project_disabled";
2932
- return "fanout_disabled";
2933
- }
2934
- async function catalogRootIsLive(config, record) {
2935
- if (typeof record.storageDir !== "string" || record.storageDir.length === 0) {
2936
- return false;
2937
- }
2938
- try {
2939
- const liveRoot = await resolveNamespaceStorageRoot(config, record.namespace);
2940
- if (path3.resolve(liveRoot) !== path3.resolve(record.storageDir)) return false;
2941
- return hasMemoryData(liveRoot);
2942
- } catch {
2943
- return false;
2944
- }
2945
- }
2946
- function candidateSortKey(candidate) {
2947
- const write = candidate.lastWriteAt ?? "";
2948
- return `${write}\0${candidate.namespace}`;
2949
- }
2950
- function candidatePriority(candidate) {
2951
- if (candidate.kind === "default") return 0;
2952
- if (candidate.kind === "shared") return 1;
2953
- if (candidate.source === "configured") return 2;
2954
- if (candidate.kind === "team-project") return 3;
2955
- if (candidate.kind === "project") return 4;
2956
- if (candidate.kind === "self") return 5;
2957
- if (candidate.kind === "legacy") return 6;
2958
- if (candidate.kind === "branch") return 8;
2959
- return 7;
2960
- }
2961
- function sortCandidates(a, b) {
2962
- const priority = candidatePriority(a) - candidatePriority(b);
2963
- if (priority !== 0) return priority;
2964
- const am = Date.parse(a.lastMaintenanceAt ?? "");
2965
- const bm = Date.parse(b.lastMaintenanceAt ?? "");
2966
- const aMaintained = Number.isFinite(am);
2967
- const bMaintained = Number.isFinite(bm);
2968
- if (aMaintained && bMaintained && am !== bm) return am - bm;
2969
- if (aMaintained !== bMaintained) return aMaintained ? 1 : -1;
2970
- const aw = Date.parse(a.lastWriteAt ?? "");
2971
- const bw = Date.parse(b.lastWriteAt ?? "");
2972
- const aValid = Number.isFinite(aw);
2973
- const bValid = Number.isFinite(bw);
2974
- if (aValid && bValid && aw !== bw) return bw - aw;
2975
- if (aValid !== bValid) return aValid ? -1 : 1;
2976
- const byKey = candidateSortKey(a).localeCompare(candidateSortKey(b));
2977
- if (byKey !== 0) return byKey;
2978
- return a.namespace.localeCompare(b.namespace);
2979
- }
2980
- async function planNamespaceMaintenance(config, options) {
2981
- const generatedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
2982
- const configured = configuredNamespaces(config);
2983
- const byNamespace = /* @__PURE__ */ new Map();
2984
- const skipped = [];
2985
- for (const namespace of configured) {
2986
- const kind = inferConfiguredKind(config, namespace);
2987
- byNamespace.set(namespace, {
2988
- namespace,
2989
- kind,
2990
- source: "configured"
2991
- });
2992
- }
2993
- if (config.namespacesEnabled && config.maintenanceNamespaceFanoutEnabled !== false) {
2994
- const configuredSet = new Set(configured);
2995
- try {
2996
- const records = options.catalog?.enabled ? await options.catalog.listNamespaces() : [];
2997
- for (const record of records) {
2998
- const namespace = record.namespace.trim();
2999
- if (!namespace) continue;
3000
- const isConfigured = configuredSet.has(namespace);
3001
- const kind = isConfigured ? inferConfiguredKind(config, namespace) : record.kind;
3002
- if (!namespaceKindAllowed(config, kind)) {
3003
- skipped.push({
3004
- namespace,
3005
- kind,
3006
- reason: disabledReasonForKind(kind)
3007
- });
3008
- continue;
3009
- }
3010
- if (!isConfigured && !await catalogRootIsLive(config, record)) {
3011
- skipped.push({
3012
- namespace,
3013
- kind,
3014
- reason: "unsafe_or_stale_root"
3015
- });
3016
- continue;
3017
- }
3018
- byNamespace.set(namespace, {
3019
- namespace,
3020
- kind,
3021
- storageDir: record.storageDir,
3022
- source: isConfigured ? "configured" : "catalog",
3023
- lastWriteAt: record.lastWriteAt,
3024
- lastMaintenanceAt: record.lastMaintenanceAt?.[options.jobName]
3025
- });
3026
- }
3027
- } catch (error) {
3028
- skipped.push({
3029
- namespace: "*",
3030
- reason: "catalog_read_failed",
3031
- detail: error instanceof Error ? error.message : String(error)
3032
- });
3033
- }
3034
- } else if (config.namespacesEnabled) {
3035
- skipped.push({
3036
- namespace: "*",
3037
- reason: "fanout_disabled"
3038
- });
3039
- }
3040
- if (options.budgetMode !== "unbounded") {
3041
- const latestStatusAtByNamespace = await readLatestStatusAtByNamespace(config, options.jobName);
3042
- for (const candidate of byNamespace.values()) {
3043
- if (!candidate.lastMaintenanceAt) {
3044
- candidate.lastMaintenanceAt = latestStatusAtByNamespace.get(candidate.namespace);
3045
- }
3046
- }
3047
- }
3048
- const candidates = [...byNamespace.values()].filter((candidate) => namespaceKindAllowed(config, candidate.kind)).sort(sortCandidates);
3049
- const max = maxNamespacesPerCycle(config);
3050
- const applyCycleBudget = options.budgetMode !== "unbounded";
3051
- const selected = applyCycleBudget ? candidates.slice(0, max) : candidates;
3052
- if (applyCycleBudget) {
3053
- for (const candidate of candidates.slice(max)) {
3054
- skipped.push({
3055
- namespace: candidate.namespace,
3056
- kind: candidate.kind,
3057
- reason: "budget_exhausted"
3058
- });
3059
- }
3060
- }
3061
- return {
3062
- jobName: options.jobName,
3063
- generatedAt,
3064
- namespaces: selected,
3065
- skipped,
3066
- budget: {
3067
- maxNamespacesPerCycle: max,
3068
- selected: selected.length
3069
- }
3070
- };
3071
- }
3072
- function stablePathSegment(value) {
3073
- const sanitized = value.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 128) || "unnamed";
3074
- if (sanitized.length <= 128 && sanitized === value) return sanitized;
3075
- return `${sanitized.slice(0, 80)}-${createHash("sha256").update(value).digest("hex").slice(0, 16)}`;
3076
- }
3077
- function namespacePathSegment(namespace) {
3078
- const token = namespaceIdentityToken(namespace);
3079
- if (token.length <= 160) return token;
3080
- return `ns-${createHash("sha256").update(namespace).digest("hex")}`;
3081
- }
3082
- function lockPath(config, jobName, namespace) {
3083
- return path3.join(
3084
- config.memoryDir,
3085
- "state",
3086
- LOCK_BASE,
3087
- stablePathSegment(jobName),
3088
- `${namespacePathSegment(namespace)}.lock`
3089
- );
3090
- }
3091
- function namespaceMaintenanceLockStaleMs(config) {
3092
- if (typeof config.maintenanceNamespaceLockStaleMs === "number" && Number.isFinite(config.maintenanceNamespaceLockStaleMs) && config.maintenanceNamespaceLockStaleMs > 0) {
3093
- return Math.floor(config.maintenanceNamespaceLockStaleMs);
3094
- }
3095
- return DEFAULT_LOCK_STALE_MS;
3096
- }
3097
- function namespaceMaintenanceLockHeartbeatMs(config) {
3098
- const staleMs = namespaceMaintenanceLockStaleMs(config);
3099
- return Math.max(1, Math.min(3e4, Math.floor(staleMs / 3) || 1));
3100
- }
3101
- function errorCode(error) {
3102
- return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
3103
- }
3104
- async function withNamespaceMaintenanceLockHeartbeat(config, locks, task) {
3105
- const activeLocks = Array.isArray(locks) ? locks : [locks];
3106
- const interval = setInterval(() => {
3107
- for (const lock of activeLocks) {
3108
- void lock.touch().catch(() => void 0);
3109
- }
3110
- }, namespaceMaintenanceLockHeartbeatMs(config));
3111
- interval.unref?.();
3112
- try {
3113
- return await task();
3114
- } finally {
3115
- clearInterval(interval);
3116
- }
3117
- }
3118
- async function removeStaleLockDirectory(filePath) {
3119
- let entries;
3120
- try {
3121
- entries = await readdir2(filePath, { withFileTypes: true });
3122
- } catch (error) {
3123
- if (errorCode(error) === "ENOENT") return;
3124
- throw error;
3125
- }
3126
- for (const entry of entries) {
3127
- if (!entry.isFile()) continue;
3128
- await namespaceMaintenanceFs.rm(path3.join(filePath, entry.name), { force: true });
3129
- }
3130
- try {
3131
- await rmdir(filePath);
3132
- } catch (error) {
3133
- if (errorCode(error) === "ENOENT") return;
3134
- throw error;
3135
- }
3136
- }
3137
- async function tryAcquireNamespaceMaintenanceLock(config, jobName, namespace) {
3138
- const filePath = lockPath(config, jobName, namespace);
3139
- await mkdir3(path3.dirname(filePath), { recursive: true });
3140
- try {
3141
- const lockId = randomUUID2();
3142
- await mkdir3(filePath);
3143
- const ownerPath = path3.join(filePath, `${lockId}.json`);
3144
- let handle;
3145
- try {
3146
- handle = await namespaceMaintenanceFs.open(ownerPath, "wx");
3147
- await handle.writeFile(
3148
- `${JSON.stringify({
3149
- lockId,
3150
- pid: process.pid,
3151
- jobName,
3152
- namespace,
3153
- acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
3154
- })}
3155
- `,
3156
- "utf8"
3157
- );
3158
- await handle.close();
3159
- } catch (setupError) {
3160
- await handle?.close().catch(() => void 0);
3161
- await namespaceMaintenanceFs.rm(ownerPath, { force: true }).catch(() => void 0);
3162
- await rmdir(filePath).catch(() => void 0);
3163
- throw setupError;
3164
- }
3165
- return {
3166
- path: filePath,
3167
- async touch() {
3168
- try {
3169
- const parsed = JSON.parse(await readFile3(ownerPath, "utf8"));
3170
- if (parsed.lockId === lockId) {
3171
- const now = /* @__PURE__ */ new Date();
3172
- await utimes2(ownerPath, now, now);
3173
- await utimes2(filePath, now, now);
3174
- }
3175
- } catch {
3176
- }
3177
- },
3178
- async release() {
3179
- try {
3180
- const parsed = JSON.parse(await readFile3(ownerPath, "utf8"));
3181
- if (parsed.lockId === lockId) {
3182
- await namespaceMaintenanceFs.rm(ownerPath, { force: true });
3183
- await rmdir(filePath).catch(() => void 0);
3184
- }
3185
- } catch {
3186
- }
1416
+ async transcriptMemories(options = {}) {
1417
+ if (options.date !== void 0 && !isValidTranscriptDate(options.date)) {
1418
+ throw new WearablesInputError(`invalid date '${options.date}' \u2014 expected YYYY-MM-DD`);
1419
+ }
1420
+ if (options.source !== void 0) assertValidSourceId(options.source);
1421
+ const limit = clampLimit(
1422
+ options.limit,
1423
+ MEMORY_LIST_DEFAULT_LIMIT,
1424
+ MEMORY_LIST_MAX_LIMIT,
1425
+ "limit"
1426
+ );
1427
+ const storage = await this.deps.getStorage();
1428
+ const memories = await storage.readAllMemories();
1429
+ const results = [];
1430
+ for (const memory of memories) {
1431
+ const source = memory.frontmatter.source;
1432
+ if (typeof source !== "string" || !source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`)) {
1433
+ continue;
3187
1434
  }
3188
- };
3189
- } catch (error) {
3190
- if (errorCode(error) === "EEXIST") {
3191
- const staleMs = namespaceMaintenanceLockStaleMs(config);
3192
- try {
3193
- const s = await lstat2(filePath);
3194
- if (s.isSymbolicLink()) {
3195
- return null;
3196
- }
3197
- if ((s.isFile() || s.isDirectory()) && Date.now() - s.mtimeMs > staleMs) {
3198
- try {
3199
- if (s.isDirectory()) {
3200
- await removeStaleLockDirectory(filePath);
3201
- } else {
3202
- await namespaceMaintenanceFs.rm(filePath, { force: true });
3203
- }
3204
- } catch (removeError) {
3205
- if (errorCode(removeError) === "ENOENT") {
3206
- return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);
3207
- }
3208
- if (errorCode(removeError) === "ENOTEMPTY") {
3209
- return null;
3210
- }
3211
- throw removeError;
3212
- }
3213
- return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);
3214
- }
3215
- } catch (statError) {
3216
- if (errorCode(statError) === "ENOENT") {
3217
- return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);
1435
+ const attrs = memory.frontmatter.structuredAttributes ?? {};
1436
+ const sourceId = attrs.wearableSource;
1437
+ if (options.source !== void 0) {
1438
+ if (sourceId !== options.source && source !== wearableSourceLabel(options.source) && source !== `${wearableSourceLabel(options.source)}:native`) {
1439
+ continue;
3218
1440
  }
3219
- throw statError;
3220
1441
  }
3221
- return null;
1442
+ if (options.date !== void 0 && attrs.wearableDate !== options.date) {
1443
+ continue;
1444
+ }
1445
+ results.push({
1446
+ id: memory.frontmatter.id,
1447
+ source: sourceId ?? source,
1448
+ date: attrs.wearableDate,
1449
+ conversationId: attrs.wearableConversationId,
1450
+ status: memory.frontmatter.status,
1451
+ content: memory.content,
1452
+ created: memory.frontmatter.created
1453
+ });
3222
1454
  }
3223
- throw error;
3224
- }
3225
- }
3226
- function statusBasePath(config) {
3227
- return path3.join(config.memoryDir, "state", STATUS_BASE);
3228
- }
3229
- function statusPath(config, jobName, namespace) {
3230
- return path3.join(statusBasePath(config), stablePathSegment(jobName), `${namespacePathSegment(namespace)}.json`);
3231
- }
3232
- function lastRanStatusPath(config, jobName, namespace) {
3233
- return path3.join(statusBasePath(config), stablePathSegment(jobName), `${namespacePathSegment(namespace)}.last-ran.json`);
3234
- }
3235
- function parseStatus(value) {
3236
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3237
- const v = value;
3238
- if (typeof v.namespace === "string" && typeof v.jobName === "string" && (v.state === "ran" || v.state === "skipped" || v.state === "failed") && typeof v.startedAt === "string" && typeof v.completedAt === "string") {
3239
- return v;
3240
- }
3241
- return null;
3242
- }
3243
- async function readLatestStatusAtByNamespace(config, jobName) {
3244
- const latest = /* @__PURE__ */ new Map();
3245
- const latestMs = /* @__PURE__ */ new Map();
3246
- for (const status of [...await readStatusFiles(config), ...await readLastRanStatusFiles(config)]) {
3247
- if (status.state !== "ran") continue;
3248
- if (status.jobName !== jobName) continue;
3249
- const completedAtMs = Date.parse(status.completedAt);
3250
- if (!Number.isFinite(completedAtMs)) continue;
3251
- const previousMs = latestMs.get(status.namespace);
3252
- if (previousMs !== void 0 && previousMs >= completedAtMs) continue;
3253
- latestMs.set(status.namespace, completedAtMs);
3254
- latest.set(status.namespace, status.completedAt);
3255
- }
3256
- return latest;
3257
- }
3258
- async function readStatusFile(filePath) {
3259
- try {
3260
- const parsed = JSON.parse(await readFile3(filePath, "utf8"));
3261
- return parseStatus(parsed);
3262
- } catch {
3263
- return null;
3264
- }
3265
- }
3266
- async function readStatusFiles(config) {
3267
- if (typeof config.memoryDir !== "string" || config.memoryDir.length === 0) {
3268
- return [];
1455
+ results.sort((a, b) => {
1456
+ if (a.created > b.created) return -1;
1457
+ if (a.created < b.created) return 1;
1458
+ if (a.id < b.id) return -1;
1459
+ if (a.id > b.id) return 1;
1460
+ return 0;
1461
+ });
1462
+ return results.slice(0, limit);
3269
1463
  }
3270
- const root = statusBasePath(config);
3271
- const statuses = [];
3272
- let jobDirs;
3273
- try {
3274
- jobDirs = await readdir2(root, { withFileTypes: true });
3275
- } catch {
3276
- return statuses;
1464
+ // -- speakers -------------------------------------------------------------
1465
+ async listSpeakers() {
1466
+ const storage = await this.deps.getStorage();
1467
+ return loadSpeakerRegistry(storage.dir);
3277
1468
  }
3278
- for (const jobDir of jobDirs) {
3279
- if (!jobDir.isDirectory()) continue;
3280
- let files;
3281
- try {
3282
- files = await readdir2(path3.join(root, jobDir.name), { withFileTypes: true });
3283
- } catch {
3284
- continue;
1469
+ async setSpeaker(sourceId, speakerKey, name, opts = {}) {
1470
+ if (typeof name !== "string" || name.trim().length === 0) {
1471
+ throw new WearablesInputError("speaker name must be a non-empty string");
3285
1472
  }
3286
- for (const file of files) {
3287
- if (!file.isFile() || !file.name.endsWith(".json")) continue;
3288
- if (file.name.endsWith(".last-ran.json")) continue;
3289
- const status = await readStatusFile(path3.join(root, jobDir.name, file.name));
3290
- if (status) statuses.push(status);
1473
+ if (typeof speakerKey !== "string" || speakerKey.trim().length === 0) {
1474
+ throw new WearablesInputError("speaker key must be a non-empty string");
3291
1475
  }
1476
+ const storage = await this.deps.getStorage();
1477
+ const registry = await loadSpeakerRegistry(storage.dir);
1478
+ registry.speakers[speakerRegistryKey(sourceId, speakerKey.trim())] = {
1479
+ name: name.trim(),
1480
+ ...opts.isSelf === true ? { isSelf: true } : {},
1481
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1482
+ };
1483
+ await saveSpeakerRegistry(storage.dir, registry);
1484
+ return registry;
3292
1485
  }
3293
- return statuses;
3294
- }
3295
- async function readLastRanStatusFiles(config) {
3296
- if (typeof config.memoryDir !== "string" || config.memoryDir.length === 0) {
3297
- return [];
3298
- }
3299
- const root = statusBasePath(config);
3300
- const statuses = [];
3301
- let jobDirs;
3302
- try {
3303
- jobDirs = await readdir2(root, { withFileTypes: true });
3304
- } catch {
3305
- return statuses;
3306
- }
3307
- for (const jobDir of jobDirs) {
3308
- if (!jobDir.isDirectory()) continue;
3309
- let files;
3310
- try {
3311
- files = await readdir2(path3.join(root, jobDir.name), { withFileTypes: true });
3312
- } catch {
3313
- continue;
3314
- }
3315
- for (const file of files) {
3316
- if (!file.isFile() || !file.name.endsWith(".last-ran.json")) continue;
3317
- const status = await readStatusFile(path3.join(root, jobDir.name, file.name));
3318
- if (status) statuses.push(status);
1486
+ async setSelfName(name) {
1487
+ if (typeof name !== "string" || name.trim().length === 0) {
1488
+ throw new WearablesInputError("self name must be a non-empty string");
3319
1489
  }
1490
+ const storage = await this.deps.getStorage();
1491
+ const registry = await loadSpeakerRegistry(storage.dir);
1492
+ registry.selfName = name.trim();
1493
+ await saveSpeakerRegistry(storage.dir, registry);
1494
+ return registry;
3320
1495
  }
3321
- return statuses;
3322
- }
3323
- async function writeStatusPayload(target, status) {
3324
- const dir = path3.dirname(target);
3325
- await mkdir3(dir, { recursive: true });
3326
- const temp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
3327
- const payload = {
3328
- version: 1,
3329
- ...status
3330
- };
3331
- await writeFile3(temp, `${JSON.stringify(payload, null, 2)}
3332
- `, "utf8");
3333
- await rename2(temp, target);
3334
- }
3335
- async function writeStatusFile(config, status) {
3336
- await writeStatusPayload(statusPath(config, status.jobName, status.namespace), status);
3337
- if (status.state === "ran") {
3338
- await writeStatusPayload(lastRanStatusPath(config, status.jobName, status.namespace), status);
3339
- }
3340
- }
3341
- async function recordNamespaceMaintenanceStatusSafely(config, status) {
3342
- try {
3343
- await writeStatusFile(config, status);
3344
- } catch {
3345
- }
3346
- }
3347
- function maintenanceErrorDetail(error) {
3348
- return displayErrorDetail(error) || "Error";
3349
- }
3350
- async function runNamespaceMaintenanceBatchPlan(config, plan, runner, catalog, options = {}) {
3351
- const statuses = [];
3352
- for (const skipped of plan.skipped) {
3353
- if (skipped.namespace === "*") continue;
3354
- const now = (/* @__PURE__ */ new Date()).toISOString();
3355
- const status = {
3356
- namespace: skipped.namespace,
3357
- jobName: plan.jobName,
3358
- state: "skipped",
3359
- reason: skipped.reason,
3360
- startedAt: now,
3361
- completedAt: now
3362
- };
3363
- statuses.push(status);
3364
- await recordNamespaceMaintenanceStatusSafely(config, status);
3365
- }
3366
- const acquired = [];
3367
- try {
3368
- for (const candidate of plan.namespaces) {
3369
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3370
- const lock = await tryAcquireNamespaceMaintenanceLock(config, plan.jobName, candidate.namespace);
3371
- if (!lock) {
3372
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3373
- const status = {
3374
- namespace: candidate.namespace,
3375
- jobName: plan.jobName,
3376
- state: "skipped",
3377
- reason: "lock_held",
3378
- startedAt,
3379
- completedAt
3380
- };
3381
- statuses.push(status);
3382
- await recordNamespaceMaintenanceStatusSafely(config, status);
3383
- continue;
3384
- }
3385
- acquired.push({ candidate, lock, startedAt });
3386
- }
3387
- } catch (error) {
3388
- await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => void 0)));
3389
- throw error;
3390
- }
3391
- if (options.requireAllLocks && acquired.length > 0 && acquired.length < plan.namespaces.length) {
3392
- for (const { candidate, startedAt } of acquired) {
3393
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3394
- const status = {
3395
- namespace: candidate.namespace,
3396
- jobName: plan.jobName,
3397
- state: "skipped",
3398
- reason: "batch_lock_incomplete",
3399
- startedAt,
3400
- completedAt
3401
- };
3402
- statuses.push(status);
3403
- await recordNamespaceMaintenanceStatusSafely(config, status);
1496
+ async removeSpeaker(sourceId, speakerKey) {
1497
+ const storage = await this.deps.getStorage();
1498
+ const registry = await loadSpeakerRegistry(storage.dir);
1499
+ const key = speakerRegistryKey(sourceId, speakerKey.trim());
1500
+ if (!(key in registry.speakers)) {
1501
+ throw new WearablesInputError(`no speaker override stored for '${key}'`);
3404
1502
  }
3405
- await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => void 0)));
3406
- return {
3407
- jobName: plan.jobName,
3408
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3409
- ran: statuses.filter((s) => s.state === "ran").length,
3410
- skipped: statuses.filter((s) => s.state === "skipped").length,
3411
- failed: statuses.filter((s) => s.state === "failed").length,
3412
- statuses
3413
- };
1503
+ delete registry.speakers[key];
1504
+ await saveSpeakerRegistry(storage.dir, registry);
1505
+ return registry;
3414
1506
  }
3415
- if (acquired.length === 0) {
1507
+ // -- corrections ----------------------------------------------------------
1508
+ async listCorrections() {
1509
+ const storage = await this.deps.getStorage();
3416
1510
  return {
3417
- jobName: plan.jobName,
3418
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3419
- ran: statuses.filter((s) => s.state === "ran").length,
3420
- skipped: statuses.filter((s) => s.state === "skipped").length,
3421
- failed: statuses.filter((s) => s.state === "failed").length,
3422
- statuses
1511
+ fromConfig: this.deps.config.corrections,
1512
+ fromState: await loadCorrectionsFile(storage.dir),
1513
+ stateFilePath: correctionsFilePath(storage.dir)
3423
1514
  };
3424
1515
  }
3425
- try {
3426
- const result = await withNamespaceMaintenanceLockHeartbeat(
3427
- config,
3428
- acquired.map(({ lock }) => lock),
3429
- () => runner(acquired.map(({ candidate }) => candidate))
1516
+ async addCorrection(rule) {
1517
+ compileCorrectionRule(rule, "correction");
1518
+ const storage = await this.deps.getStorage();
1519
+ const rules = await loadCorrectionsFile(storage.dir);
1520
+ const duplicate = rules.some(
1521
+ (existing) => existing.match === rule.match && existing.replace === rule.replace && existing.regex === true === (rule.regex === true)
3430
1522
  );
3431
- for (const { candidate, startedAt } of acquired) {
3432
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3433
- const status = {
3434
- namespace: candidate.namespace,
3435
- jobName: plan.jobName,
3436
- state: "ran",
3437
- startedAt,
3438
- completedAt,
3439
- itemCount: itemCountForNamespace(result, candidate.namespace)
3440
- };
3441
- statuses.push(status);
3442
- await recordNamespaceMaintenanceStatusSafely(config, status);
3443
- try {
3444
- await catalog?.markMaintenance(candidate.namespace, plan.jobName, new Date(completedAt));
3445
- } catch {
3446
- }
1523
+ if (duplicate) {
1524
+ throw new WearablesInputError(
1525
+ `an identical correction rule already exists (match: ${JSON.stringify(rule.match)})`
1526
+ );
3447
1527
  }
3448
- } catch (error) {
3449
- const skipReason = options.skipReasonForError?.(error);
3450
- for (const { candidate, startedAt } of acquired) {
3451
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
3452
- const status = skipReason ? {
3453
- namespace: candidate.namespace,
3454
- jobName: plan.jobName,
3455
- state: "skipped",
3456
- reason: skipReason,
3457
- startedAt,
3458
- completedAt
3459
- } : {
3460
- namespace: candidate.namespace,
3461
- jobName: plan.jobName,
3462
- state: "failed",
3463
- reason: "job_failed",
3464
- startedAt,
3465
- completedAt,
3466
- error: maintenanceErrorDetail(error)
3467
- };
3468
- statuses.push(status);
3469
- await recordNamespaceMaintenanceStatusSafely(config, status);
1528
+ rules.push(rule);
1529
+ await saveCorrectionsFile(storage.dir, rules);
1530
+ }
1531
+ async removeCorrection(index) {
1532
+ if (!Number.isInteger(index) || index < 0) {
1533
+ throw new WearablesInputError(`invalid correction index '${index}'`);
3470
1534
  }
3471
- } finally {
3472
- await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => void 0)));
1535
+ const storage = await this.deps.getStorage();
1536
+ const rules = await loadCorrectionsFile(storage.dir);
1537
+ if (index >= rules.length) {
1538
+ throw new WearablesInputError(
1539
+ `correction index ${index} is out of range (have ${rules.length} state rule${rules.length === 1 ? "" : "s"})`
1540
+ );
1541
+ }
1542
+ const [removed] = rules.splice(index, 1);
1543
+ await saveCorrectionsFile(storage.dir, rules);
1544
+ return removed;
3473
1545
  }
3474
- return {
3475
- jobName: plan.jobName,
3476
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3477
- ran: statuses.filter((s) => s.state === "ran").length,
3478
- skipped: statuses.filter((s) => s.state === "skipped").length,
3479
- failed: statuses.filter((s) => s.state === "failed").length,
3480
- statuses
3481
- };
3482
- }
3483
- function itemCountForNamespace(result, namespace) {
3484
- const itemCounts = result?.itemCounts;
3485
- if (itemCounts instanceof Map) return itemCounts.get(namespace);
3486
- if (itemCounts && Object.prototype.hasOwnProperty.call(itemCounts, namespace)) {
3487
- return itemCounts[namespace];
1546
+ };
1547
+ function clampLimit(value, fallback, max, label) {
1548
+ if (value === void 0) return fallback;
1549
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1 || value > max) {
1550
+ throw new WearablesInputError(
1551
+ `invalid ${label} '${value}' \u2014 expected an integer between 1 and ${max}`
1552
+ );
3488
1553
  }
3489
- return result?.itemCount;
1554
+ return value;
1555
+ }
1556
+ function locateTranscriptPath(hitPath) {
1557
+ const normalized = hitPath.replace(/\\/g, "/");
1558
+ const match = normalized.match(
1559
+ /(?:^|\/)wearables\/([a-z][a-z0-9-]{0,63})\/(\d{4}-\d{2}-\d{2})\.md$/
1560
+ );
1561
+ if (!match) return null;
1562
+ if (!isValidTranscriptDate(match[2])) return null;
1563
+ return { source: match[1], date: match[2] };
1564
+ }
1565
+ function extractSnippet(body, index, matchLength) {
1566
+ const start = Math.max(0, index - 80);
1567
+ const end = Math.min(body.length, index + matchLength + 80);
1568
+ const prefix = start > 0 ? "\u2026" : "";
1569
+ const suffix = end < body.length ? "\u2026" : "";
1570
+ return `${prefix}${body.slice(start, end).replace(/\s+/g, " ").trim()}${suffix}`;
3490
1571
  }
3491
1572
 
3492
1573
  // src/scopes/scope-plan.ts
@@ -3625,7 +1706,7 @@ function flattenStructuredSectionEvidence(sections) {
3625
1706
  );
3626
1707
  }
3627
1708
  function fingerprintEntitySynthesisEvidence(entity) {
3628
- const fingerprint = createHash2("sha256");
1709
+ const fingerprint = createHash("sha256");
3629
1710
  const timelineEntries = entity.timeline.map((entry) => [
3630
1711
  entry.timestamp,
3631
1712
  entry.source ?? "",
@@ -3675,7 +1756,7 @@ async function raceRecallAbort(promise, signal, message = "recall aborted") {
3675
1756
  }
3676
1757
  }
3677
1758
  function qmdCollectionPathParts(resultPath) {
3678
- if (!resultPath || path4.isAbsolute(resultPath)) return null;
1759
+ if (!resultPath || path2.isAbsolute(resultPath)) return null;
3679
1760
  const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
3680
1761
  const slashIndex = normalized.indexOf("/");
3681
1762
  if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
@@ -3688,9 +1769,9 @@ function qmdCollectionPathParts(resultPath) {
3688
1769
  }
3689
1770
  function qmdResultPathCandidates(storageDir, resultPath) {
3690
1771
  const candidates = /* @__PURE__ */ new Set();
3691
- const storageRoot = path4.resolve(storageDir);
1772
+ const storageRoot = path2.resolve(storageDir);
3692
1773
  const addCandidate = (candidate) => {
3693
- const resolved = path4.resolve(candidate);
1774
+ const resolved = path2.resolve(candidate);
3694
1775
  if (isPathInsideStorageRoot(storageRoot, resolved)) {
3695
1776
  candidates.add(resolved);
3696
1777
  }
@@ -3698,12 +1779,12 @@ function qmdResultPathCandidates(storageDir, resultPath) {
3698
1779
  const addRelativeCandidates = (relativePath) => {
3699
1780
  const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
3700
1781
  if (!normalized) return;
3701
- addCandidate(path4.join(storageRoot, normalized));
1782
+ addCandidate(path2.join(storageRoot, normalized));
3702
1783
  if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
3703
- addCandidate(path4.join(storageRoot, "facts", normalized));
1784
+ addCandidate(path2.join(storageRoot, "facts", normalized));
3704
1785
  }
3705
1786
  };
3706
- if (path4.isAbsolute(resultPath)) {
1787
+ if (path2.isAbsolute(resultPath)) {
3707
1788
  addCandidate(resultPath);
3708
1789
  } else {
3709
1790
  addRelativeCandidates(resultPath);
@@ -3820,11 +1901,11 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
3820
1901
  return await Promise.race([checkedPromise, timeoutPromise]);
3821
1902
  }
3822
1903
  function defaultWorkspaceDir() {
3823
- return path4.join(os.homedir(), ".openclaw", "workspace");
1904
+ return path2.join(os.homedir(), ".openclaw", "workspace");
3824
1905
  }
3825
1906
  function sanitizeSessionKeyForFilename(sessionKey) {
3826
1907
  const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
3827
- const hash = createHash2("sha256").update(sessionKey).digest("hex").slice(0, 12);
1908
+ const hash = createHash("sha256").update(sessionKey).digest("hex").slice(0, 12);
3828
1909
  return `${readable}-${hash}`;
3829
1910
  }
3830
1911
  function latestSourceValidAtFromTurns(turns) {
@@ -4127,11 +2208,11 @@ function mergeGraphExpandedResults(primary, expanded) {
4127
2208
  return Array.from(mergedByPath.values());
4128
2209
  }
4129
2210
  function graphPathRelativeToStorage(storageDir, candidatePath) {
4130
- const absolutePath = path4.isAbsolute(candidatePath) ? candidatePath : path4.resolve(storageDir, candidatePath);
4131
- const rel = path4.relative(storageDir, absolutePath);
2211
+ const absolutePath = path2.isAbsolute(candidatePath) ? candidatePath : path2.resolve(storageDir, candidatePath);
2212
+ const rel = path2.relative(storageDir, absolutePath);
4132
2213
  if (!rel || rel === ".") return null;
4133
2214
  if (rel.startsWith("..")) return null;
4134
- return rel.split(path4.sep).join("/");
2215
+ return rel.split(path2.sep).join("/");
4135
2216
  }
4136
2217
  function normalizeGraphActivationScore(score) {
4137
2218
  const bounded = Number.isFinite(score) && score > 0 ? score : 0;
@@ -4273,7 +2354,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
4273
2354
  for (const mem of allMemsForGraph ?? []) {
4274
2355
  const id = mem.frontmatter.id;
4275
2356
  if (!id) continue;
4276
- pathById.set(id, path4.relative(storageDir, mem.path));
2357
+ pathById.set(id, path2.relative(storageDir, mem.path));
4277
2358
  }
4278
2359
  return pathById;
4279
2360
  }
@@ -4281,7 +2362,7 @@ function appendMemoryToGraphContext(options) {
4281
2362
  if (!Array.isArray(options.allMemsForGraph)) return;
4282
2363
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4283
2364
  options.allMemsForGraph.push({
4284
- path: path4.join(options.storageDir, options.memoryRelPath),
2365
+ path: path2.join(options.storageDir, options.memoryRelPath),
4285
2366
  content: options.content,
4286
2367
  frontmatter: {
4287
2368
  id: options.memoryId,
@@ -4301,16 +2382,16 @@ function resolvePersistedMemoryRelativePath(options) {
4301
2382
  const persisted = options.pathById.get(options.memoryId);
4302
2383
  if (persisted) return persisted;
4303
2384
  if (options.category === "correction") {
4304
- return path4.join("corrections", `${options.memoryId}.md`);
2385
+ return path2.join("corrections", `${options.memoryId}.md`);
4305
2386
  }
4306
2387
  const subtree = categoryDirName(options.category);
4307
2388
  const idParts = options.memoryId.split("-");
4308
2389
  const maybeTimestamp = Number(idParts[1]);
4309
2390
  if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
4310
2391
  const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
4311
- return path4.join(subtree, day, `${options.memoryId}.md`);
2392
+ return path2.join(subtree, day, `${options.memoryId}.md`);
4312
2393
  }
4313
- return path4.join(subtree, `${options.memoryId}.md`);
2394
+ return path2.join(subtree, `${options.memoryId}.md`);
4314
2395
  }
4315
2396
  function qmdMaintenanceSkipReasonForError(error) {
4316
2397
  const message = error instanceof Error ? error.message : String(error);
@@ -4745,7 +2826,7 @@ var Orchestrator = class _Orchestrator {
4745
2826
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
4746
2827
  if (ns !== defaultNs && !isSafeRouteNamespace(ns)) return;
4747
2828
  if (!this.storageDirMatchesNamespaceHint(ns, storageDir)) return;
4748
- const resolvedStorageDir = path4.resolve(storageDir);
2829
+ const resolvedStorageDir = path2.resolve(storageDir);
4749
2830
  let hints = this.namespaceStorageDirHints.get(resolvedStorageDir);
4750
2831
  if (!hints) {
4751
2832
  hints = /* @__PURE__ */ new Set();
@@ -4756,21 +2837,21 @@ var Orchestrator = class _Orchestrator {
4756
2837
  storageDirMatchesNamespaceHint(namespace, storageDir) {
4757
2838
  const ns = normalizeNamespaceIdentity(namespace);
4758
2839
  if (!ns) return false;
4759
- const resolvedStorageDir = path4.resolve(storageDir);
4760
- const resolvedMemoryDir = path4.resolve(this.config.memoryDir);
2840
+ const resolvedStorageDir = path2.resolve(storageDir);
2841
+ const resolvedMemoryDir = path2.resolve(this.config.memoryDir);
4761
2842
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
4762
2843
  if (resolvedStorageDir === resolvedMemoryDir) return ns === defaultNs;
4763
- const resolvedNamespacesDir = path4.join(resolvedMemoryDir, "namespaces");
2844
+ const resolvedNamespacesDir = path2.join(resolvedMemoryDir, "namespaces");
4764
2845
  if (!isPathInsideStorageRoot(resolvedNamespacesDir, resolvedStorageDir)) return false;
4765
- const rawRoot = path4.resolve(resolvedNamespacesDir, ns);
4766
- const tokenRoot = path4.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
2846
+ const rawRoot = path2.resolve(resolvedNamespacesDir, ns);
2847
+ const tokenRoot = path2.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
4767
2848
  return resolvedStorageDir === rawRoot || resolvedStorageDir === tokenRoot;
4768
2849
  }
4769
2850
  namespaceStorageDirHintOwnershipRank(record, resolvedStorageDir, configured) {
4770
- if (resolvedStorageDir === path4.resolve(this.config.memoryDir)) {
2851
+ if (resolvedStorageDir === path2.resolve(this.config.memoryDir)) {
4771
2852
  return record.namespace === normalizeNamespaceIdentity(this.config.defaultNamespace) ? 0 : 3;
4772
2853
  }
4773
- const leaf = path4.basename(resolvedStorageDir);
2854
+ const leaf = path2.basename(resolvedStorageDir);
4774
2855
  const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
4775
2856
  if (tokenOwnsRoot && configured.has(record.namespace)) return 0;
4776
2857
  if (record.namespace === leaf) return 1;
@@ -4798,7 +2879,7 @@ var Orchestrator = class _Orchestrator {
4798
2879
  loadNamespaceStorageDirHintsFromCatalog() {
4799
2880
  if (this.namespaceStorageDirHintsLoaded || !this.namespaceCatalog.enabled) return;
4800
2881
  this.namespaceStorageDirHintsLoaded = true;
4801
- const catalogPath = path4.join(this.config.memoryDir, "state", "namespaces.jsonl");
2882
+ const catalogPath = path2.join(this.config.memoryDir, "state", "namespaces.jsonl");
4802
2883
  if (!existsSync(catalogPath)) return;
4803
2884
  let body;
4804
2885
  try {
@@ -4835,7 +2916,7 @@ var Orchestrator = class _Orchestrator {
4835
2916
  if (!this.storageDirMatchesNamespaceHint(record.namespace, record.storageDir)) {
4836
2917
  continue;
4837
2918
  }
4838
- const resolvedStorageDir = path4.resolve(record.storageDir);
2919
+ const resolvedStorageDir = path2.resolve(record.storageDir);
4839
2920
  const current = preferredByStorageDir.get(resolvedStorageDir);
4840
2921
  preferredByStorageDir.set(
4841
2922
  resolvedStorageDir,
@@ -4874,6 +2955,36 @@ var Orchestrator = class _Orchestrator {
4874
2955
  });
4875
2956
  return plan.namespaces.map((candidate) => candidate.namespace);
4876
2957
  }
2958
+ /**
2959
+ * Fan out a maintenance job across all maintained namespaces (issue #1500).
2960
+ *
2961
+ * Delegates to the namespace-maintenance-fanout coordinator, which plans
2962
+ * namespace discovery (configured + catalog), applies the cycle budget,
2963
+ * acquires per-job+namespace locks, records status files, and touches the
2964
+ * catalog's `lastMaintenanceAt`. The runner receives a per-namespace
2965
+ * candidate and a storage resolver wired to `this.getStorage(namespace)`.
2966
+ *
2967
+ * When namespaces are disabled the planner returns only the default
2968
+ * namespace, so the job runs exactly once — preserving single-user behavior.
2969
+ */
2970
+ async runNamespaceMaintenanceFanoutForJob(jobName, runner, options = {}) {
2971
+ return runNamespaceMaintenanceFanout({
2972
+ config: this.config,
2973
+ catalog: this.namespaceCatalog,
2974
+ jobName,
2975
+ runner,
2976
+ resolveStorage: (namespace) => this.getStorage(namespace),
2977
+ enabled: options.enabled
2978
+ });
2979
+ }
2980
+ /**
2981
+ * Read-only namespace maintenance health summary for doctor / dashboard /
2982
+ * CLI (issue #1500). Aggregates all per-namespace maintenance status files
2983
+ * into one report without running any maintenance.
2984
+ */
2985
+ async readNamespaceMaintenanceHealth() {
2986
+ return summarizeNamespaceMaintenanceHealth(this.config);
2987
+ }
4877
2988
  buildConfiguredQmdSearchOptions(queryText) {
4878
2989
  const intentHint = this.config.qmdIntentHintsEnabled ? buildQmdIntentHint(inferIntentFromText(queryText)) : void 0;
4879
2990
  const explain = this.config.qmdExplainEnabled === true;
@@ -5002,7 +3113,7 @@ var Orchestrator = class _Orchestrator {
5002
3113
  this.config = config;
5003
3114
  this.profiler = new ProfilingCollector({
5004
3115
  enabled: config.profilingEnabled,
5005
- storageDir: config.profilingStorageDir || path4.join(config.memoryDir, "profiling"),
3116
+ storageDir: config.profilingStorageDir || path2.join(config.memoryDir, "profiling"),
5006
3117
  maxTraces: config.profilingMaxTraces
5007
3118
  });
5008
3119
  this.namespaceCatalog = new NamespaceCatalog(config);
@@ -5048,7 +3159,7 @@ var Orchestrator = class _Orchestrator {
5048
3159
  this.compounding = config.compoundingEnabled ? new CompoundingEngine(config, this.storage) : void 0;
5049
3160
  this.buffer = new SmartBuffer(config, this.storage);
5050
3161
  this.transcript = new TranscriptManager(config);
5051
- this.conversationIndexDir = path4.join(
3162
+ this.conversationIndexDir = path2.join(
5052
3163
  config.memoryDir,
5053
3164
  "conversation-index",
5054
3165
  "chunks"
@@ -5105,7 +3216,7 @@ var Orchestrator = class _Orchestrator {
5105
3216
  this.modelRegistry
5106
3217
  );
5107
3218
  this.threading = new ThreadingManager(
5108
- path4.join(config.memoryDir, "threads"),
3219
+ path2.join(config.memoryDir, "threads"),
5109
3220
  config.threadingGapMinutes
5110
3221
  );
5111
3222
  this.tmtBuilder = new TmtBuilder(config.memoryDir, {
@@ -5184,7 +3295,7 @@ var Orchestrator = class _Orchestrator {
5184
3295
  utilityPromoteThresholdDelta: this.utilityRuntimeValues?.promoteThresholdDelta ?? 0,
5185
3296
  utilityDemoteThresholdDelta: this.utilityRuntimeValues?.demoteThresholdDelta ?? 0
5186
3297
  };
5187
- return createHash2("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 12);
3298
+ return createHash("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 12);
5188
3299
  }
5189
3300
  effectiveLifecycleThresholds() {
5190
3301
  const archiveDecayThreshold = this.config.lifecycleArchiveDecayThreshold;
@@ -5404,13 +3515,13 @@ var Orchestrator = class _Orchestrator {
5404
3515
  if (this.config.compactionResetEnabled) {
5405
3516
  try {
5406
3517
  const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
5407
- const files = await readdir3(wsDir).catch(() => []);
3518
+ const files = await readdir(wsDir).catch(() => []);
5408
3519
  for (const f of files) {
5409
3520
  if (!f.startsWith(".compaction-reset-signal-")) continue;
5410
- const fp = path4.join(wsDir, f);
5411
- const s = await stat3(fp).catch(() => null);
3521
+ const fp = path2.join(wsDir, f);
3522
+ const s = await stat(fp).catch(() => null);
5412
3523
  if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
5413
- await unlink2(fp).catch(() => {
3524
+ await unlink(fp).catch(() => {
5414
3525
  });
5415
3526
  log.debug(`initialize: removed stale compaction signal ${f}`);
5416
3527
  }
@@ -5665,7 +3776,7 @@ var Orchestrator = class _Orchestrator {
5665
3776
  if (signal.aborted) return;
5666
3777
  if (!this.wearablesAutoSyncHandle && this.config.wearables.enabled && this.config.wearables.autoSyncEnabled && Object.values(this.config.wearables.sources).some((source) => source.enabled)) {
5667
3778
  try {
5668
- const { startWearablesAutoSync } = await import("./auto-sync-NUQWSFQD.js");
3779
+ const { startWearablesAutoSync } = await import("./auto-sync-RWXT6P5M.js");
5669
3780
  if (signal.aborted) return;
5670
3781
  this.wearablesAutoSyncHandle = startWearablesAutoSync(
5671
3782
  {
@@ -5686,9 +3797,9 @@ var Orchestrator = class _Orchestrator {
5686
3797
  `wearables auto-sync started: every ${this.config.wearables.autoSyncIntervalMinutes}m over ${this.config.wearables.autoSyncDays}d (deep ${this.config.wearables.autoSyncDeepDays}d daily)`
5687
3798
  );
5688
3799
  } catch (err) {
5689
- const { displayErrorDetail: displayErrorDetail2 } = await import("./runtime/better-sqlite.js");
3800
+ const { displayErrorDetail } = await import("./runtime/better-sqlite.js");
5690
3801
  log.warn(
5691
- `wearables auto-sync failed to start (non-fatal): ${displayErrorDetail2(err)}`
3802
+ `wearables auto-sync failed to start (non-fatal): ${displayErrorDetail(err)}`
5692
3803
  );
5693
3804
  }
5694
3805
  }
@@ -5801,7 +3912,7 @@ var Orchestrator = class _Orchestrator {
5801
3912
  */
5802
3913
  async autoRegisterDaySummaryCron() {
5803
3914
  const home = resolveHomeDir();
5804
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
3915
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5805
3916
  try {
5806
3917
  if (!existsSync(jobsPath)) {
5807
3918
  log.debug(
@@ -5854,7 +3965,7 @@ var Orchestrator = class _Orchestrator {
5854
3965
  }
5855
3966
  async autoRegisterNightlyGovernanceCron() {
5856
3967
  const home = resolveHomeDir();
5857
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
3968
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5858
3969
  try {
5859
3970
  if (!existsSync(jobsPath)) {
5860
3971
  log.debug("nightly governance cron: jobs.json not found, skipping auto-register");
@@ -5876,7 +3987,7 @@ var Orchestrator = class _Orchestrator {
5876
3987
  }
5877
3988
  async autoRegisterProceduralMiningCron() {
5878
3989
  const home = resolveHomeDir();
5879
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
3990
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5880
3991
  try {
5881
3992
  if (!existsSync(jobsPath)) {
5882
3993
  log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
@@ -5896,7 +4007,7 @@ var Orchestrator = class _Orchestrator {
5896
4007
  }
5897
4008
  async autoRegisterContradictionScanCron() {
5898
4009
  const home = resolveHomeDir();
5899
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
4010
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5900
4011
  try {
5901
4012
  if (!existsSync(jobsPath)) {
5902
4013
  log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
@@ -5916,7 +4027,7 @@ var Orchestrator = class _Orchestrator {
5916
4027
  }
5917
4028
  async autoRegisterPatternReinforcementCron() {
5918
4029
  const home = resolveHomeDir();
5919
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
4030
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5920
4031
  try {
5921
4032
  if (!existsSync(jobsPath)) {
5922
4033
  log.debug("pattern reinforcement cron: jobs.json not found, skipping auto-register");
@@ -5976,9 +4087,92 @@ var Orchestrator = class _Orchestrator {
5976
4087
  );
5977
4088
  return { ran: true, result, namespace: cadenceKey };
5978
4089
  }
4090
+ /**
4091
+ * Fan out pattern reinforcement across all maintained namespaces (issue #1500).
4092
+ * Delegates per-namespace execution to {@link runPatternReinforcement} while
4093
+ * the planner handles discovery, budgeting, locking, and status recording.
4094
+ * When namespaces are disabled, runs once against default storage.
4095
+ */
4096
+ async runPatternReinforcementFanout(options = {}) {
4097
+ return this.runNamespaceMaintenanceFanoutForJob(
4098
+ "pattern-reinforcement",
4099
+ async (ctx) => {
4100
+ const result = await this.runPatternReinforcement({
4101
+ namespace: ctx.candidate.namespace,
4102
+ force: options.force
4103
+ });
4104
+ if (!result.ran) {
4105
+ return {
4106
+ skipped: true,
4107
+ skipReason: result.skippedReason ?? "throttled"
4108
+ };
4109
+ }
4110
+ return result.result ? { itemCount: result.result.clustersFound } : { itemCount: 0 };
4111
+ },
4112
+ { enabled: this.config.patternReinforcementEnabled }
4113
+ );
4114
+ }
4115
+ /**
4116
+ * Fan out lifecycle/governance policy across all maintained namespaces
4117
+ * (issue #1500). Each namespace gets its own lifecycle pass against its
4118
+ * namespace-scoped storage. When namespaces are disabled, runs once against
4119
+ * default storage.
4120
+ */
4121
+ async runLifecyclePolicyFanout() {
4122
+ return this.runNamespaceMaintenanceFanoutForJob(
4123
+ "lifecycle",
4124
+ async (ctx) => {
4125
+ const storage = await this.getStorage(ctx.candidate.namespace);
4126
+ const corpus = await storage.readAllMemories();
4127
+ const assessed = await this.runLifecyclePolicyPass(corpus, storage);
4128
+ return { itemCount: assessed };
4129
+ },
4130
+ { enabled: this.config.lifecyclePolicyEnabled }
4131
+ );
4132
+ }
4133
+ /**
4134
+ * Fan out semantic consolidation across all maintained namespaces (issue #1500).
4135
+ * Each namespace gets its own consolidation pass against its namespace-scoped
4136
+ * storage. When namespaces are disabled, runs once against default storage.
4137
+ */
4138
+ async runSemanticConsolidationFanout(options = {}) {
4139
+ return this.runNamespaceMaintenanceFanoutForJob(
4140
+ "semantic-consolidation",
4141
+ async (ctx) => {
4142
+ const storage = await this.getStorage(ctx.candidate.namespace);
4143
+ const result = await this.runSemanticConsolidation({
4144
+ dryRun: options.dryRun,
4145
+ thresholdOverride: void 0,
4146
+ force: true,
4147
+ storage
4148
+ });
4149
+ return { itemCount: result.clustersFound };
4150
+ },
4151
+ { enabled: this.config.semanticConsolidationEnabled }
4152
+ );
4153
+ }
4154
+ /**
4155
+ * Fan out deep-sleep governance across all maintained namespaces (issue #1500).
4156
+ * Each namespace gets its own governance scan against its namespace-scoped
4157
+ * storage. When namespaces are disabled, runs once against default storage.
4158
+ */
4159
+ async runDeepSleepGovernanceFanout(options = {}) {
4160
+ return this.runNamespaceMaintenanceFanoutForJob(
4161
+ "governance",
4162
+ async (ctx) => {
4163
+ const storage = await this.getStorage(ctx.candidate.namespace);
4164
+ const result = await this.runDeepSleepGovernanceNow({
4165
+ dryRun: options.dryRun,
4166
+ storage
4167
+ });
4168
+ return { itemCount: result.scannedMemories };
4169
+ },
4170
+ { enabled: this.config.dreamsPhases.deepSleep.enabled }
4171
+ );
4172
+ }
5979
4173
  async autoRegisterGraphEdgeDecayCron() {
5980
4174
  const home = resolveHomeDir();
5981
- const jobsPath = path4.join(home, ".openclaw", "cron", "jobs.json");
4175
+ const jobsPath = path2.join(home, ".openclaw", "cron", "jobs.json");
5982
4176
  try {
5983
4177
  if (!existsSync(jobsPath)) {
5984
4178
  log.debug("graph edge decay cron: jobs.json not found, skipping auto-register");
@@ -6035,15 +4229,15 @@ ${doc.content}` : doc.content,
6035
4229
  this.lastFileHygieneRunAtMs = now;
6036
4230
  if (hygiene.rotateEnabled) {
6037
4231
  for (const rel of hygiene.rotatePaths) {
6038
- const abs = path4.isAbsolute(rel) ? rel : path4.join(this.config.workspaceDir, rel);
4232
+ const abs = path2.isAbsolute(rel) ? rel : path2.join(this.config.workspaceDir, rel);
6039
4233
  try {
6040
- const raw = await readFile4(abs, "utf-8");
4234
+ const raw = await readFile2(abs, "utf-8");
6041
4235
  if (raw.length > hygiene.rotateMaxBytes) {
6042
- const archiveDir = path4.join(
4236
+ const archiveDir = path2.join(
6043
4237
  this.config.workspaceDir,
6044
4238
  hygiene.archiveDir
6045
4239
  );
6046
- const base = path4.basename(abs);
4240
+ const base = path2.basename(abs);
6047
4241
  const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
6048
4242
  const { newContent } = await rotateMarkdownFileToArchive({
6049
4243
  filePath: abs,
@@ -6051,7 +4245,7 @@ ${doc.content}` : doc.content,
6051
4245
  archivePrefix: prefix,
6052
4246
  keepTailChars: hygiene.rotateKeepTailChars
6053
4247
  });
6054
- await writeFile4(abs, newContent, "utf-8");
4248
+ await writeFile2(abs, newContent, "utf-8");
6055
4249
  }
6056
4250
  } catch {
6057
4251
  }
@@ -6068,8 +4262,8 @@ ${doc.content}` : doc.content,
6068
4262
  log.warn(w.message);
6069
4263
  }
6070
4264
  if (hygiene.warningsLogEnabled && warnings.length > 0) {
6071
- const fp = path4.join(this.config.memoryDir, hygiene.warningsLogPath);
6072
- await mkdir4(path4.dirname(fp), { recursive: true });
4265
+ const fp = path2.join(this.config.memoryDir, hygiene.warningsLogPath);
4266
+ await mkdir2(path2.dirname(fp), { recursive: true });
6073
4267
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
6074
4268
  const block = `
6075
4269
 
@@ -6078,11 +4272,11 @@ ${doc.content}` : doc.content,
6078
4272
  ` + warnings.map((w) => `- ${w.message}`).join("\n") + "\n";
6079
4273
  let existing = "";
6080
4274
  try {
6081
- existing = await readFile4(fp, "utf-8");
4275
+ existing = await readFile2(fp, "utf-8");
6082
4276
  } catch {
6083
4277
  existing = "# Engram File Hygiene Warnings\n";
6084
4278
  }
6085
- await writeFile4(fp, existing + block, "utf-8");
4279
+ await writeFile2(fp, existing + block, "utf-8");
6086
4280
  }
6087
4281
  }
6088
4282
  }
@@ -6547,26 +4741,26 @@ ${evidenceText}`
6547
4741
  const facts = [];
6548
4742
  let memoryRootReal = null;
6549
4743
  try {
6550
- memoryRootReal = await realpath2(storage.dir);
4744
+ memoryRootReal = await realpath(storage.dir);
6551
4745
  } catch {
6552
4746
  memoryRootReal = null;
6553
4747
  }
6554
4748
  for (const categoryDir of RECALL_FALLBACK_DIRS) {
6555
4749
  if (memoryRootReal === null) break;
6556
4750
  for (const date of datesToScan) {
6557
- const dateDir = path4.join(storage.dir, categoryDir, date);
4751
+ const dateDir = path2.join(storage.dir, categoryDir, date);
6558
4752
  try {
6559
- const dirStat = await lstat3(dateDir);
4753
+ const dirStat = await lstat(dateDir);
6560
4754
  if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
6561
- assertPathInsideRoot(memoryRootReal, await realpath2(dateDir), dateDir);
6562
- const entries = await readdir3(dateDir, { withFileTypes: true });
4755
+ assertPathInsideRoot(memoryRootReal, await realpath(dateDir), dateDir);
4756
+ const entries = await readdir(dateDir, { withFileTypes: true });
6563
4757
  for (const entry of entries) {
6564
4758
  if (entry.isSymbolicLink()) continue;
6565
4759
  if (!entry.name.endsWith(".md")) continue;
6566
- const fullPath = path4.join(dateDir, entry.name);
4760
+ const fullPath = path2.join(dateDir, entry.name);
6567
4761
  try {
6568
- assertPathInsideRoot(memoryRootReal, await realpath2(fullPath), fullPath);
6569
- const raw = await readFile4(fullPath, "utf-8");
4762
+ assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
4763
+ const raw = await readFile2(fullPath, "utf-8");
6570
4764
  const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
6571
4765
  if (!fmMatch) continue;
6572
4766
  const fmBlock = fmMatch[1];
@@ -6585,7 +4779,7 @@ ${evidenceText}`
6585
4779
  facts.push({
6586
4780
  path: fullPath,
6587
4781
  frontmatter: {
6588
- id: fm.id || path4.basename(entry.name, ".md"),
4782
+ id: fm.id || path2.basename(entry.name, ".md"),
6589
4783
  category: fm.category || "fact",
6590
4784
  created,
6591
4785
  updated: fm.updated || created,
@@ -6608,15 +4802,15 @@ ${evidenceText}`
6608
4802
  return a.frontmatter.created < b.frontmatter.created ? -1 : 1;
6609
4803
  });
6610
4804
  const hourlySummaries = [];
6611
- const hourlyBaseDir = path4.join(storage.dir, "summaries", "hourly");
4805
+ const hourlyBaseDir = path2.join(storage.dir, "summaries", "hourly");
6612
4806
  try {
6613
- const sessionKeys = await readdir3(hourlyBaseDir, { withFileTypes: true });
4807
+ const sessionKeys = await readdir(hourlyBaseDir, { withFileTypes: true });
6614
4808
  for (const sk of sessionKeys) {
6615
4809
  if (!sk.isDirectory()) continue;
6616
4810
  for (const date of datesToScan) {
6617
- const summaryFile = path4.join(hourlyBaseDir, sk.name, `${date}.md`);
4811
+ const summaryFile = path2.join(hourlyBaseDir, sk.name, `${date}.md`);
6618
4812
  try {
6619
- const raw = await readFile4(summaryFile, "utf-8");
4813
+ const raw = await readFile2(summaryFile, "utf-8");
6620
4814
  const filtered = filterHourlySummaryMarkdownForLocalDay(
6621
4815
  raw,
6622
4816
  date,
@@ -6718,13 +4912,13 @@ ${evidenceText}`
6718
4912
  }
6719
4913
  async getLastGraphRecallSnapshot(namespace) {
6720
4914
  const storage = await this.getStorage(namespace);
6721
- const snapshotPath = path4.join(
4915
+ const snapshotPath = path2.join(
6722
4916
  storage.dir,
6723
4917
  "state",
6724
4918
  "last_graph_recall.json"
6725
4919
  );
6726
4920
  try {
6727
- const raw = await readFile4(snapshotPath, "utf-8");
4921
+ const raw = await readFile2(snapshotPath, "utf-8");
6728
4922
  const parsed = JSON.parse(raw);
6729
4923
  if (!parsed || typeof parsed !== "object") return null;
6730
4924
  return {
@@ -6757,9 +4951,9 @@ ${evidenceText}`
6757
4951
  }
6758
4952
  async getLastIntentSnapshot(namespace) {
6759
4953
  const storage = await this.getStorage(namespace);
6760
- const snapshotPath = path4.join(storage.dir, "state", "last_intent.json");
4954
+ const snapshotPath = path2.join(storage.dir, "state", "last_intent.json");
6761
4955
  try {
6762
- const raw = await readFile4(snapshotPath, "utf-8");
4956
+ const raw = await readFile2(snapshotPath, "utf-8");
6763
4957
  const parsed = JSON.parse(raw);
6764
4958
  if (!parsed || typeof parsed !== "object") return null;
6765
4959
  const graphDecision = parsed.graphDecision && typeof parsed.graphDecision === "object" ? parsed.graphDecision : void 0;
@@ -6790,13 +4984,13 @@ ${evidenceText}`
6790
4984
  }
6791
4985
  async getLastQmdRecallSnapshot(namespace) {
6792
4986
  const storage = await this.getStorage(namespace);
6793
- const snapshotPath = path4.join(
4987
+ const snapshotPath = path2.join(
6794
4988
  storage.dir,
6795
4989
  "state",
6796
4990
  "last_qmd_recall.json"
6797
4991
  );
6798
4992
  try {
6799
- const raw = await readFile4(snapshotPath, "utf-8");
4993
+ const raw = await readFile2(snapshotPath, "utf-8");
6800
4994
  const parsed = JSON.parse(raw);
6801
4995
  if (!parsed || typeof parsed !== "object") return null;
6802
4996
  return {
@@ -6940,10 +5134,10 @@ ${r.snippet.trim()}
6940
5134
  }
6941
5135
  async countConversationChunkDocs(dir) {
6942
5136
  try {
6943
- const entries = await readdir3(dir, { withFileTypes: true });
5137
+ const entries = await readdir(dir, { withFileTypes: true });
6944
5138
  let total = 0;
6945
5139
  for (const entry of entries) {
6946
- const fullPath = path4.join(dir, entry.name);
5140
+ const fullPath = path2.join(dir, entry.name);
6947
5141
  if (entry.isDirectory()) {
6948
5142
  total += await this.countConversationChunkDocs(fullPath);
6949
5143
  continue;
@@ -7667,7 +5861,7 @@ ${r.snippet.trim()}
7667
5861
  if (!options.onDebugSnapshot) return;
7668
5862
  await options.onDebugSnapshot({
7669
5863
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
7670
- queryHash: createHash2("sha256").update(prompt).digest("hex"),
5864
+ queryHash: createHash("sha256").update(prompt).digest("hex"),
7671
5865
  queryLength: prompt.length,
7672
5866
  collection: options.collection,
7673
5867
  namespaces: options.recallNamespaces,
@@ -7849,7 +6043,7 @@ ${r.snippet.trim()}
7849
6043
  resolvedPath = resolvedCold.result.path;
7850
6044
  resolvedResult = resolvedCold.result;
7851
6045
  }
7852
- if (!path4.isAbsolute(resolvedPath)) {
6046
+ if (!path2.isAbsolute(resolvedPath)) {
7853
6047
  resolvedAmbiguousSeeds.set(result.path, null);
7854
6048
  return null;
7855
6049
  }
@@ -7874,7 +6068,7 @@ ${r.snippet.trim()}
7874
6068
  }
7875
6069
  continue;
7876
6070
  }
7877
- if (path4.isAbsolute(result.path)) {
6071
+ if (path2.isAbsolute(result.path)) {
7878
6072
  const resolved = await resolveAmbiguousSeedOwner(result, null);
7879
6073
  if (resolved) {
7880
6074
  addResultForNamespace(resolved.namespace, resolved.result);
@@ -7917,7 +6111,7 @@ ${r.snippet.trim()}
7917
6111
  0
7918
6112
  );
7919
6113
  seedPaths.push(
7920
- ...seedRelativePaths.map((rel) => path4.join(storage.dir, rel))
6114
+ ...seedRelativePaths.map((rel) => path2.join(storage.dir, rel))
7921
6115
  );
7922
6116
  const seedSet = new Set(seedRelativePaths);
7923
6117
  const expanded = await this.graphIndexFor(storage).spreadingActivation(
@@ -7933,7 +6127,7 @@ ${r.snippet.trim()}
7933
6127
  for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
7934
6128
  if (deadlineExpired()) break;
7935
6129
  if (seedSet.has(candidate.path)) continue;
7936
- const memoryPath = path4.resolve(storage.dir, candidate.path);
6130
+ const memoryPath = path2.resolve(storage.dir, candidate.path);
7937
6131
  const memory = await storage.readMemoryByPath(memoryPath);
7938
6132
  if (deadlineExpired()) break;
7939
6133
  if (!memory) continue;
@@ -7958,7 +6152,7 @@ ${r.snippet.trim()}
7958
6152
  path: memory.path,
7959
6153
  score,
7960
6154
  namespace,
7961
- seed: path4.resolve(storage.dir, candidate.seed),
6155
+ seed: path2.resolve(storage.dir, candidate.seed),
7962
6156
  hopDepth: candidate.hopDepth,
7963
6157
  decayedWeight: candidate.decayedWeight,
7964
6158
  graphType: candidate.graphType,
@@ -7979,12 +6173,12 @@ ${r.snippet.trim()}
7979
6173
  }
7980
6174
  async recordLastGraphRecallSnapshot(options) {
7981
6175
  try {
7982
- const snapshotPath = path4.join(
6176
+ const snapshotPath = path2.join(
7983
6177
  options.storage.dir,
7984
6178
  "state",
7985
6179
  "last_graph_recall.json"
7986
6180
  );
7987
- await mkdir4(path4.dirname(snapshotPath), { recursive: true });
6181
+ await mkdir2(path2.dirname(snapshotPath), { recursive: true });
7988
6182
  const now = (/* @__PURE__ */ new Date()).toISOString();
7989
6183
  const totalSeedCount = options.seedPaths.length;
7990
6184
  const totalExpandedCount = options.expandedPaths.length;
@@ -7996,7 +6190,7 @@ ${r.snippet.trim()}
7996
6190
  const payload = {
7997
6191
  recordedAt: now,
7998
6192
  mode: options.recallMode,
7999
- queryHash: createHash2("sha256").update(options.prompt).digest("hex"),
6193
+ queryHash: createHash("sha256").update(options.prompt).digest("hex"),
8000
6194
  queryLength: options.prompt.length,
8001
6195
  namespaces: options.recallNamespaces,
8002
6196
  seedCount: totalSeedCount,
@@ -8011,20 +6205,20 @@ ${r.snippet.trim()}
8011
6205
  finalResults: (options.finalResults ?? []).slice(0, 64),
8012
6206
  shadowComparison: options.shadowComparison
8013
6207
  };
8014
- await writeFile4(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
6208
+ await writeFile2(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
8015
6209
  } catch (err) {
8016
6210
  log.debug(`last graph recall write failed: ${err}`);
8017
6211
  }
8018
6212
  }
8019
6213
  async recordLastIntentSnapshot(options) {
8020
6214
  try {
8021
- const snapshotPath = path4.join(
6215
+ const snapshotPath = path2.join(
8022
6216
  options.storage.dir,
8023
6217
  "state",
8024
6218
  "last_intent.json"
8025
6219
  );
8026
- await mkdir4(path4.dirname(snapshotPath), { recursive: true });
8027
- await writeFile4(
6220
+ await mkdir2(path2.dirname(snapshotPath), { recursive: true });
6221
+ await writeFile2(
8028
6222
  snapshotPath,
8029
6223
  JSON.stringify(options.snapshot, null, 2),
8030
6224
  "utf-8"
@@ -8035,13 +6229,13 @@ ${r.snippet.trim()}
8035
6229
  }
8036
6230
  async recordLastQmdRecallSnapshot(options) {
8037
6231
  try {
8038
- const snapshotPath = path4.join(
6232
+ const snapshotPath = path2.join(
8039
6233
  options.storage.dir,
8040
6234
  "state",
8041
6235
  "last_qmd_recall.json"
8042
6236
  );
8043
- await mkdir4(path4.dirname(snapshotPath), { recursive: true });
8044
- await writeFile4(
6237
+ await mkdir2(path2.dirname(snapshotPath), { recursive: true });
6238
+ await writeFile2(
8045
6239
  snapshotPath,
8046
6240
  JSON.stringify(options.snapshot, null, 2),
8047
6241
  "utf-8"
@@ -8055,9 +6249,9 @@ ${r.snippet.trim()}
8055
6249
  const stateDir = await this.resolveStateDirForNamespace(
8056
6250
  options.namespace
8057
6251
  );
8058
- const snapshotPath = path4.join(stateDir, "last_intent.json");
8059
- await mkdir4(path4.dirname(snapshotPath), { recursive: true });
8060
- await writeFile4(
6252
+ const snapshotPath = path2.join(stateDir, "last_intent.json");
6253
+ await mkdir2(path2.dirname(snapshotPath), { recursive: true });
6254
+ await writeFile2(
8061
6255
  snapshotPath,
8062
6256
  JSON.stringify(options.snapshot, null, 2),
8063
6257
  "utf-8"
@@ -8068,24 +6262,24 @@ ${r.snippet.trim()}
8068
6262
  }
8069
6263
  async resolveStateDirForNamespace(namespace) {
8070
6264
  if (!this.config.namespacesEnabled) {
8071
- return path4.join(this.config.memoryDir, "state");
6265
+ return path2.join(this.config.memoryDir, "state");
8072
6266
  }
8073
6267
  if (namespace !== this.config.defaultNamespace) {
8074
- return path4.join(this.config.memoryDir, "namespaces", namespace, "state");
6268
+ return path2.join(this.config.memoryDir, "namespaces", namespace, "state");
8075
6269
  }
8076
- const candidate = path4.join(
6270
+ const candidate = path2.join(
8077
6271
  this.config.memoryDir,
8078
6272
  "namespaces",
8079
6273
  this.config.defaultNamespace
8080
6274
  );
8081
6275
  try {
8082
- const candidateStat = await stat3(candidate);
6276
+ const candidateStat = await stat(candidate);
8083
6277
  if (candidateStat.isDirectory()) {
8084
- return path4.join(candidate, "state");
6278
+ return path2.join(candidate, "state");
8085
6279
  }
8086
6280
  } catch {
8087
6281
  }
8088
- return path4.join(this.config.memoryDir, "state");
6282
+ return path2.join(this.config.memoryDir, "state");
8089
6283
  }
8090
6284
  buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
8091
6285
  return results.slice(0, limit).map((result) => ({
@@ -8338,8 +6532,8 @@ ${r.snippet.trim()}
8338
6532
  timings,
8339
6533
  logger: log
8340
6534
  });
8341
- const promptHash = createHash2("sha256").update(prompt).digest("hex");
8342
- const traceId = createHash2("sha256").update(`${sessionKey ?? "default"}:${recallStart}:${promptHash}`).digest("hex").slice(0, 16);
6535
+ const promptHash = createHash("sha256").update(prompt).digest("hex");
6536
+ const traceId = createHash("sha256").update(`${sessionKey ?? "default"}:${recallStart}:${promptHash}`).digest("hex").slice(0, 16);
8343
6537
  const sectionBuckets = /* @__PURE__ */ new Map();
8344
6538
  const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
8345
6539
  cronRecallPolicyEnabled: this.config.cronRecallPolicyEnabled,
@@ -8348,7 +6542,7 @@ ${r.snippet.trim()}
8348
6542
  cronConversationRecallMode: this.config.cronConversationRecallMode
8349
6543
  });
8350
6544
  const retrievalQuery = queryPolicy.retrievalQuery || prompt;
8351
- const retrievalQueryHash = createHash2("sha256").update(retrievalQuery).digest("hex");
6545
+ const retrievalQueryHash = createHash("sha256").update(retrievalQuery).digest("hex");
8352
6546
  const policyVersion = this.currentPolicyVersion();
8353
6547
  let impressionRecorded = false;
8354
6548
  let recallSource = "none";
@@ -8483,7 +6677,7 @@ ${r.snippet.trim()}
8483
6677
  const graphExpandedResultPaths = /* @__PURE__ */ new Set();
8484
6678
  const graphSourceLabelsForPath = (resultPath) => {
8485
6679
  const labels = [];
8486
- const normalizedPath = resultPath.split(path4.sep).join("/");
6680
+ const normalizedPath = resultPath.split(path2.sep).join("/");
8487
6681
  const isEntityPath = normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/");
8488
6682
  if (graphBaselinePaths.has(resultPath)) labels.push("baseline");
8489
6683
  if (graphExpandedResultPaths.has(resultPath))
@@ -8633,7 +6827,7 @@ ${r.snippet.trim()}
8633
6827
  profileStorageNamespaces.map((namespace) => this.storageRouter.storageFor(namespace))
8634
6828
  );
8635
6829
  const emptyProfileStorage = new Proxy(
8636
- { dir: path4.join(this.config.memoryDir, ".empty-scope-profile") },
6830
+ { dir: path2.join(this.config.memoryDir, ".empty-scope-profile") },
8637
6831
  {
8638
6832
  get(target, prop) {
8639
6833
  if (prop in target) return target[prop];
@@ -10109,16 +8303,16 @@ ${formatted}`;
10109
8303
  if (!this.config.compactionResetEnabled) return null;
10110
8304
  const workspaceDir = compactionWorkspaceDir || this.config.workspaceDir || defaultWorkspaceDir();
10111
8305
  const safeSessionKey = sanitizeSessionKeyForFilename(effectiveSessionKey);
10112
- const signalPath = path4.join(
8306
+ const signalPath = path2.join(
10113
8307
  workspaceDir,
10114
8308
  `.compaction-reset-signal-${safeSessionKey}`
10115
8309
  );
10116
- const bootPath = path4.join(workspaceDir, "BOOT.md");
8310
+ const bootPath = path2.join(workspaceDir, "BOOT.md");
10117
8311
  try {
10118
- const signalStat = await stat3(signalPath).catch(() => null);
8312
+ const signalStat = await stat(signalPath).catch(() => null);
10119
8313
  if (!signalStat) return null;
10120
8314
  const signalAge = Date.now() - signalStat.mtimeMs;
10121
- const signalData = JSON.parse(await readFile4(signalPath, "utf-8"));
8315
+ const signalData = JSON.parse(await readFile2(signalPath, "utf-8"));
10122
8316
  if (signalData.sessionKey !== effectiveSessionKey) {
10123
8317
  log.debug(
10124
8318
  `recall: compaction signal is for ${signalData.sessionKey}, not ${effectiveSessionKey} \u2014 skipping`
@@ -10129,7 +8323,7 @@ ${formatted}`;
10129
8323
  log.debug(
10130
8324
  `recall: stale compaction signal (${Math.round(signalAge / 1e3)}s old), skipping`
10131
8325
  );
10132
- await unlink2(signalPath).catch(() => {
8326
+ await unlink(signalPath).catch(() => {
10133
8327
  });
10134
8328
  return null;
10135
8329
  }
@@ -10138,7 +8332,7 @@ ${formatted}`;
10138
8332
 
10139
8333
  `;
10140
8334
  try {
10141
- const bootContent = await readFile4(bootPath, "utf-8");
8335
+ const bootContent = await readFile2(bootPath, "utf-8");
10142
8336
  section += "### BOOT.md (working state before compaction)\n\n";
10143
8337
  section += bootContent + "\n";
10144
8338
  } catch {
@@ -10149,12 +8343,12 @@ ${formatted}`;
10149
8343
  log.info(
10150
8344
  `recall: injected compaction reset context for ${effectiveSessionKey}`
10151
8345
  );
10152
- await unlink2(signalPath).catch(() => {
8346
+ await unlink(signalPath).catch(() => {
10153
8347
  });
10154
8348
  return section;
10155
8349
  } catch (err) {
10156
8350
  log.debug("recall: compaction signal check failed:", err);
10157
- await unlink2(signalPath).catch(() => {
8351
+ await unlink(signalPath).catch(() => {
10158
8352
  });
10159
8353
  return null;
10160
8354
  }
@@ -12293,7 +10487,7 @@ _Context: ${topQuestion.context}_`
12293
10487
  const shouldUseStableBatchKey = turns.some(
12294
10488
  (turn) => turn.persistProcessedFingerprint === true || typeof turn.turnFingerprint === "string" && turn.turnFingerprint.length > 0
12295
10489
  );
12296
- const stableBatchFingerprint = shouldUseStableBatchKey ? createHash2("sha256").update(
10490
+ const stableBatchFingerprint = shouldUseStableBatchKey ? createHash("sha256").update(
12297
10491
  turns.map(
12298
10492
  (turn) => [
12299
10493
  turn.role,
@@ -12550,7 +10744,7 @@ _Context: ${topQuestion.context}_`
12550
10744
  buildExtractionFingerprint(turns, bufferKey) {
12551
10745
  const normalized = this.normalizeExtractionFingerprintTurns(turns).join("\n");
12552
10746
  if (!normalized) return null;
12553
- return createHash2("sha256").update(`${bufferKey}
10747
+ return createHash("sha256").update(`${bufferKey}
12554
10748
  ${normalized}`).digest("hex");
12555
10749
  }
12556
10750
  shouldQueueExtraction(turns, options = {}) {
@@ -13075,7 +11269,7 @@ ${normalized}`).digest("hex");
13075
11269
  );
13076
11270
  this.tierMigrationInFlight = true;
13077
11271
  try {
13078
- const coldStorage = new StorageManager(path4.join(storage.dir, "cold"));
11272
+ const coldStorage = new StorageManager(path2.join(storage.dir, "cold"));
13079
11273
  const [hotMemories, coldMemories] = await Promise.all([
13080
11274
  storage.readAllMemories(),
13081
11275
  coldStorage.readAllMemories()
@@ -14491,7 +12685,7 @@ ${normalized}`).digest("hex");
14491
12685
  const allMems = allMemsForGraph ?? [];
14492
12686
  for (const m of allMems) {
14493
12687
  if (m.frontmatter.entityRef === entityRef) {
14494
- const rel = path4.relative(storage.dir, m.path);
12688
+ const rel = path2.relative(storage.dir, m.path);
14495
12689
  if (rel !== memoryRelPath) entitySiblings.push(rel);
14496
12690
  }
14497
12691
  }
@@ -14813,14 +13007,14 @@ ${normalized}`).digest("hex");
14813
13007
  }
14814
13008
  if (this.config.semanticConsolidationEnabled) {
14815
13009
  try {
14816
- const stateFilePath = path4.join(
13010
+ const stateFilePath = path2.join(
14817
13011
  this.config.memoryDir,
14818
13012
  "state",
14819
13013
  "semantic-consolidation-last-run.json"
14820
13014
  );
14821
13015
  let shouldRun = true;
14822
13016
  try {
14823
- const stateRaw = await readFile4(stateFilePath, "utf-8");
13017
+ const stateRaw = await readFile2(stateFilePath, "utf-8");
14824
13018
  const stateData = JSON.parse(stateRaw);
14825
13019
  if (stateData.lastRunAt) {
14826
13020
  const lastRunMs = new Date(stateData.lastRunAt).getTime();
@@ -14861,9 +13055,9 @@ ${normalized}`).digest("hex");
14861
13055
  );
14862
13056
  }
14863
13057
  if (semResult.errors === 0 || semResult.memoriesArchived > 0) {
14864
- const stateDir = path4.join(this.config.memoryDir, "state");
14865
- await mkdir4(stateDir, { recursive: true });
14866
- await writeFile4(
13058
+ const stateDir = path2.join(this.config.memoryDir, "state");
13059
+ await mkdir2(stateDir, { recursive: true });
13060
+ await writeFile2(
14867
13061
  stateFilePath,
14868
13062
  JSON.stringify({ lastRunAt: (/* @__PURE__ */ new Date()).toISOString() }),
14869
13063
  "utf-8"
@@ -15059,7 +13253,7 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
15059
13253
  }
15060
13254
  });
15061
13255
  const content = renderCompressionGuidelinesMarkdown(refinedCandidate);
15062
- const contentHash = createHash2("sha256").update(content).digest("hex");
13256
+ const contentHash = createHash("sha256").update(content).digest("hex");
15063
13257
  const semanticRefinementApplied = JSON.stringify(refinedCandidate.ruleUpdates) !== JSON.stringify(candidate.ruleUpdates);
15064
13258
  const changedRules = refinedCandidate.ruleUpdates.filter(
15065
13259
  (rule) => rule.delta !== 0
@@ -15361,13 +13555,13 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
15361
13555
  protectedCategories: this.config.lifecycleProtectedCategories
15362
13556
  }
15363
13557
  };
15364
- const metricsPath = path4.join(
13558
+ const metricsPath = path2.join(
15365
13559
  storage.dir,
15366
13560
  "state",
15367
13561
  "lifecycle-metrics.json"
15368
13562
  );
15369
- await mkdir4(path4.dirname(metricsPath), { recursive: true });
15370
- await writeFile4(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
13563
+ await mkdir2(path2.dirname(metricsPath), { recursive: true });
13564
+ await writeFile2(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
15371
13565
  return updatedCount;
15372
13566
  }
15373
13567
  /**
@@ -15890,7 +14084,7 @@ ${lines.join("\n\n")}`;
15890
14084
  const seenStorageDirs = /* @__PURE__ */ new Set();
15891
14085
  const addStorage = (storage) => {
15892
14086
  const storageDir = storageDirFor(storage);
15893
- const storageKey = storageDir ? path4.resolve(storageDir) : `storage-without-dir-${storages.length}`;
14087
+ const storageKey = storageDir ? path2.resolve(storageDir) : `storage-without-dir-${storages.length}`;
15894
14088
  if (seenStorageDirs.has(storageKey)) return;
15895
14089
  seenStorageDirs.add(storageKey);
15896
14090
  storages.push(storage);
@@ -15920,7 +14114,7 @@ ${lines.join("\n\n")}`;
15920
14114
  continue;
15921
14115
  }
15922
14116
  try {
15923
- const coldRoot = path4.join(storageDir, "cold");
14117
+ const coldRoot = path2.join(storageDir, "cold");
15924
14118
  for (const candidate of qmdResultPathCandidates(
15925
14119
  coldRoot,
15926
14120
  parts.relativePath
@@ -15961,7 +14155,7 @@ ${lines.join("\n\n")}`;
15961
14155
  return null;
15962
14156
  }
15963
14157
  }
15964
- if (path4.isAbsolute(resultPath)) {
14158
+ if (path2.isAbsolute(resultPath)) {
15965
14159
  if (!fallbackStorageDir) {
15966
14160
  return await fallbackStorage.readMemoryByPath(resultPath);
15967
14161
  }
@@ -16000,7 +14194,7 @@ ${lines.join("\n\n")}`;
16000
14194
  );
16001
14195
  if (!memory) return null;
16002
14196
  let ownerNamespace = null;
16003
- if (path4.isAbsolute(memory.path)) {
14197
+ if (path2.isAbsolute(memory.path)) {
16004
14198
  const ownerStorage = await this.storageForAbsoluteQmdResultPath(
16005
14199
  memory.path,
16006
14200
  fallbackStorage,
@@ -16024,16 +14218,16 @@ ${lines.join("\n\n")}`;
16024
14218
  };
16025
14219
  }
16026
14220
  async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
16027
- const resolvedPath = path4.resolve(resultPath);
16028
- const memoryRoot = path4.resolve(this.config.memoryDir);
16029
- const namespacesRoot = path4.join(memoryRoot, "namespaces");
14221
+ const resolvedPath = path2.resolve(resultPath);
14222
+ const memoryRoot = path2.resolve(this.config.memoryDir);
14223
+ const namespacesRoot = path2.join(memoryRoot, "namespaces");
16030
14224
  const fallbackStorageDir = typeof fallbackStorage.dir === "string" && fallbackStorage.dir ? fallbackStorage.dir : null;
16031
14225
  const matches = [];
16032
14226
  const seenDirs = /* @__PURE__ */ new Set();
16033
14227
  const maybeAddStorage = (storage, namespace) => {
16034
14228
  const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
16035
14229
  if (!storageDir) return;
16036
- const candidateRoot = path4.resolve(storageDir);
14230
+ const candidateRoot = path2.resolve(storageDir);
16037
14231
  if (seenDirs.has(candidateRoot)) return;
16038
14232
  if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
16039
14233
  if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
@@ -16051,7 +14245,7 @@ ${lines.join("\n\n")}`;
16051
14245
  candidateNamespaces.add(ns);
16052
14246
  }
16053
14247
  if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
16054
- const relativeToNamespaces = path4.relative(namespacesRoot, resolvedPath);
14248
+ const relativeToNamespaces = path2.relative(namespacesRoot, resolvedPath);
16055
14249
  const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
16056
14250
  if (namespaceSegment) {
16057
14251
  candidateNamespaces.add(
@@ -16096,7 +14290,7 @@ ${lines.join("\n\n")}`;
16096
14290
  nsMap = buildMemoryWorthCounterMap(memories);
16097
14291
  this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
16098
14292
  }
16099
- for (const [path5, c] of nsMap) counters.set(path5, c);
14293
+ for (const [path3, c] of nsMap) counters.set(path3, c);
16100
14294
  } catch (err) {
16101
14295
  log.debug("memory-worth: failed to read namespace, skipping", {
16102
14296
  namespace: ns,
@@ -16267,12 +14461,12 @@ ${lines.join("\n\n")}`;
16267
14461
  */
16268
14462
  semanticDedupScopeFor(targetStorage) {
16269
14463
  if (!this.config.namespacesEnabled) return {};
16270
- const memoryDir = path4.resolve(this.config.memoryDir);
16271
- const storageDir = path4.resolve(targetStorage.dir);
14464
+ const memoryDir = path2.resolve(this.config.memoryDir);
14465
+ const storageDir = path2.resolve(targetStorage.dir);
16272
14466
  if (storageDir === memoryDir) {
16273
14467
  return { pathExcludePrefixes: ["namespaces/"] };
16274
14468
  }
16275
- let rel = path4.relative(memoryDir, storageDir);
14469
+ let rel = path2.relative(memoryDir, storageDir);
16276
14470
  if (!rel || rel.startsWith("..")) {
16277
14471
  log.debug(
16278
14472
  `semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
@@ -16291,7 +14485,7 @@ ${lines.join("\n\n")}`;
16291
14485
  if (hits.length === 0) return [];
16292
14486
  const results = [];
16293
14487
  for (const hit of hits) {
16294
- const fullPath = path4.isAbsolute(hit.path) ? hit.path : path4.join(this.config.memoryDir, hit.path);
14488
+ const fullPath = path2.isAbsolute(hit.path) ? hit.path : path2.join(this.config.memoryDir, hit.path);
16295
14489
  const memory = await this.storage.readMemoryByPath(fullPath);
16296
14490
  if (!memory) continue;
16297
14491
  results.push({
@@ -16495,7 +14689,7 @@ ${lines.join("\n\n")}`;
16495
14689
  const storage = await this.storageRouter.storageFor(namespace);
16496
14690
  const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
16497
14691
  if (!storageDir) continue;
16498
- const recallRoot = path4.resolve(storageDir);
14692
+ const recallRoot = path2.resolve(storageDir);
16499
14693
  if (seenRecallRoots.has(recallRoot)) continue;
16500
14694
  seenRecallRoots.add(recallRoot);
16501
14695
  recallRoots.push(recallRoot);
@@ -16519,8 +14713,8 @@ ${lines.join("\n\n")}`;
16519
14713
  if (resolvedCold) scopedResults.push(resolvedCold.result);
16520
14714
  continue;
16521
14715
  }
16522
- if (path4.isAbsolute(result.path)) {
16523
- const resolvedPath = path4.resolve(result.path);
14716
+ if (path2.isAbsolute(result.path)) {
14717
+ const resolvedPath = path2.resolve(result.path);
16524
14718
  if (recallRoots.some(
16525
14719
  (recallRoot) => isPathInsideStorageRoot(recallRoot, resolvedPath)
16526
14720
  )) {
@@ -16807,6 +15001,7 @@ ${lines.join("\n\n")}`;
16807
15001
  filterSearchResultsByRecallSafety(results, memoryByPath, options) {
16808
15002
  let lifecycleFilteredCount = 0;
16809
15003
  let temporalSupersededFilteredCount = 0;
15004
+ let biTemporalExpiredFilteredCount = 0;
16810
15005
  let dedicatedSurfaceFilteredCount = 0;
16811
15006
  let forgottenFilteredCount = 0;
16812
15007
  let blockedPathFilteredCount = 0;
@@ -16854,6 +15049,10 @@ ${lines.join("\n\n")}`;
16854
15049
  temporalSupersededFilteredCount += 1;
16855
15050
  continue;
16856
15051
  }
15052
+ if (!asOfActive && this.config.temporalBiTemporal && !this.config.temporalExpiredInInjection && isValidityExpiredNow(memory.frontmatter, Date.now())) {
15053
+ biTemporalExpiredFilteredCount += 1;
15054
+ continue;
15055
+ }
16857
15056
  if (options?.allowDedicatedSurface !== true && (memory.frontmatter.memoryKind === "dream" || memory.frontmatter.memoryKind === "procedural")) {
16858
15057
  dedicatedSurfaceFilteredCount += 1;
16859
15058
  continue;
@@ -16871,6 +15070,11 @@ ${lines.join("\n\n")}`;
16871
15070
  `temporal supersession filter removed ${temporalSupersededFilteredCount} superseded candidates`
16872
15071
  );
16873
15072
  }
15073
+ if (biTemporalExpiredFilteredCount > 0) {
15074
+ log.debug(
15075
+ `bi-temporal validity filter removed ${biTemporalExpiredFilteredCount} expired-validity candidates from injection (temporal.biTemporal on)`
15076
+ );
15077
+ }
16874
15078
  if (dedicatedSurfaceFilteredCount > 0) {
16875
15079
  log.debug(
16876
15080
  `dedicated surface filter removed ${dedicatedSurfaceFilteredCount} dream/procedural candidates from generic recall`
@@ -17262,8 +15466,8 @@ ${lines.join("\n\n")}`;
17262
15466
  }
17263
15467
  namespaceFromStorageDir(storageDir) {
17264
15468
  if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
17265
- const resolvedStorageDir = path4.resolve(storageDir);
17266
- const resolvedMemoryDir = path4.resolve(this.config.memoryDir);
15469
+ const resolvedStorageDir = path2.resolve(storageDir);
15470
+ const resolvedMemoryDir = path2.resolve(this.config.memoryDir);
17267
15471
  if (resolvedStorageDir === resolvedMemoryDir)
17268
15472
  return this.config.defaultNamespace;
17269
15473
  const m = resolvedStorageDir.match(/[\\/]namespaces[\\/]([^\\/]+)$/);
@@ -17343,7 +15547,6 @@ export {
17343
15547
  ensureBuiltInWearableConnectors,
17344
15548
  WearablesService,
17345
15549
  locateTranscriptPath,
17346
- NamespaceCatalog,
17347
15550
  BulkImportBatchPartialFailureError,
17348
15551
  dedupeEntitySynthesisEvidenceEntries,
17349
15552
  defaultWorkspaceDir,
@@ -17374,4 +15577,4 @@ export {
17374
15577
  resolvePersistedMemoryRelativePath,
17375
15578
  Orchestrator
17376
15579
  };
17377
- //# sourceMappingURL=chunk-JZBFL7RI.js.map
15580
+ //# sourceMappingURL=chunk-TJ5IIQIR.js.map