@snoai/memory 0.0.0-stage → 1.0.1

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 (478) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +33 -2
  3. package/config/b-profile-section-registry.json +118 -0
  4. package/config/llm/compiled/2026-05-19T230932Z/manifest.json +25 -0
  5. package/config/llm/compiled/2026-05-19T230932Z/registry-root.json +67 -0
  6. package/config/llm/compiled/2026-05-19T230932Z/resolved/mem_claw/openai_gpt_5_nano.json +5 -0
  7. package/config/llm/compiled/2026-05-19T230932Z/resolved/mem_claw/openrouter_auto.json +5 -0
  8. package/config/llm/compiled/2026-05-19T230932Z/resolved/mem_claw/sno_ai_extract.json +5 -0
  9. package/config/llm/compiled/2026-05-19T230932Z/source/mem_claw/openai_gpt_5_nano.mda +24 -0
  10. package/config/llm/compiled/2026-05-19T230932Z/source/mem_claw/openrouter_auto.mda +24 -0
  11. package/config/llm/compiled/2026-05-19T230932Z/source/mem_claw/sno_ai_extract.mda +24 -0
  12. package/config/llm/compiled/2026-07-21T001311Z/manifest.json +43 -0
  13. package/config/llm/compiled/2026-07-21T001311Z/registry-root.json +97 -0
  14. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/openai_gpt_5_nano.json +5 -0
  15. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/openrouter_auto.json +5 -0
  16. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/sno_ai_extract.json +5 -0
  17. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/sno_conflict_verdict.json +10 -0
  18. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/sno_extract_chat.json +10 -0
  19. package/config/llm/compiled/2026-07-21T001311Z/resolved/mem_claw/sno_extract_profile.json +10 -0
  20. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/openai_gpt_5_nano.mda +24 -0
  21. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/openrouter_auto.mda +24 -0
  22. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/sno_ai_extract.mda +24 -0
  23. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/sno_conflict_verdict.mda +27 -0
  24. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/sno_extract_chat.mda +27 -0
  25. package/config/llm/compiled/2026-07-21T001311Z/source/mem_claw/sno_extract_profile.mda +27 -0
  26. package/config/llm/compiled/2026-08-29T060335Z/manifest.json +43 -0
  27. package/config/llm/compiled/2026-08-29T060335Z/registry-root.json +97 -0
  28. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/openai_gpt_5_nano.json +5 -0
  29. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/openrouter_auto.json +5 -0
  30. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/sno_ai_extract.json +5 -0
  31. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/sno_conflict_verdict.json +10 -0
  32. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/sno_extract_chat.json +10 -0
  33. package/config/llm/compiled/2026-08-29T060335Z/resolved/mem_claw/sno_extract_profile.json +10 -0
  34. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/openai_gpt_5_nano.mda +24 -0
  35. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/openrouter_auto.mda +24 -0
  36. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/sno_ai_extract.mda +24 -0
  37. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/sno_conflict_verdict.mda +27 -0
  38. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/sno_extract_chat.mda +27 -0
  39. package/config/llm/compiled/2026-08-29T060335Z/source/mem_claw/sno_extract_profile.mda +27 -0
  40. package/config/llm/compiled/2026-09-04T004828Z/manifest.json +43 -0
  41. package/config/llm/compiled/2026-09-04T004828Z/registry-root.json +97 -0
  42. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/openai_gpt_5_nano.json +5 -0
  43. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/openrouter_auto.json +5 -0
  44. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/sno_ai_extract.json +5 -0
  45. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/sno_conflict_verdict.json +10 -0
  46. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/sno_extract_chat.json +10 -0
  47. package/config/llm/compiled/2026-09-04T004828Z/resolved/mem_claw/sno_extract_profile.json +10 -0
  48. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/openai_gpt_5_nano.mda +24 -0
  49. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/openrouter_auto.mda +24 -0
  50. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/sno_ai_extract.mda +24 -0
  51. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/sno_conflict_verdict.mda +27 -0
  52. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/sno_extract_chat.mda +27 -0
  53. package/config/llm/compiled/2026-09-04T004828Z/source/mem_claw/sno_extract_profile.mda +27 -0
  54. package/config/llm/current.json +4 -0
  55. package/config/llm/source/mem_claw/openai_gpt_5_nano.mda +24 -0
  56. package/config/llm/source/mem_claw/openrouter_auto.mda +24 -0
  57. package/config/llm/source/mem_claw/sno_ai_extract.mda +24 -0
  58. package/config/llm/source/mem_claw/sno_conflict_verdict.mda +27 -0
  59. package/config/llm/source/mem_claw/sno_extract_chat.mda +27 -0
  60. package/config/llm/source/mem_claw/sno_extract_profile.mda +27 -0
  61. package/config/llm.trust.json +37 -0
  62. package/dist/_types-CUKHSRkS.d.ts +103 -0
  63. package/dist/access-tracker-BbEvEocb.d.ts +110 -0
  64. package/dist/adapter-B7Jox9-6.d.ts +69 -0
  65. package/dist/adapter-XaRHZWZ2.js +333 -0
  66. package/dist/agent-llm-port-5oXgBpzJ.d.ts +53 -0
  67. package/dist/all-resources-BQK2ugjB.js +6379 -0
  68. package/dist/all-resources-Dij6l5h0.d.ts +18 -0
  69. package/dist/ambient-learning-text-normalizer-CxjATmMp.js +170 -0
  70. package/dist/ambient-learning-text-normalizer-_fH3LeuK.d.ts +10 -0
  71. package/dist/atomic-extraction-reply-BJ9XV_9s.js +863 -0
  72. package/dist/atomic-extraction-skill-CcXmuCqL.js +56 -0
  73. package/dist/atomic-memory-extraction-C5rmogoY.js +3605 -0
  74. package/dist/atomic-memory-extraction-d9EG-4ti.d.ts +327 -0
  75. package/dist/b-profile-section-dictionary-provider-BHkoNqgk.js +1812 -0
  76. package/dist/b-profile-section-dictionary-provider-BxbksLj3.d.ts +62 -0
  77. package/dist/best-effort-BPZSzKk0.d.ts +9 -0
  78. package/dist/calendar-instruction-CzqZbAPG.js +194 -0
  79. package/dist/capture-policy-detector-CxbHPkMX.d.ts +53 -0
  80. package/dist/capture-policy-detector-DaMfy9CZ.js +357 -0
  81. package/dist/classifier-DnbbtwTS.d.ts +272 -0
  82. package/dist/client.d.ts +34 -0
  83. package/dist/client.js +306 -0
  84. package/dist/coding-skin.d.ts +51 -0
  85. package/dist/coding-skin.js +55 -0
  86. package/dist/config-CL90qYva.js +2576 -0
  87. package/dist/connection-3IVXpuPE.d.ts +391 -0
  88. package/dist/connection-C9YD5sgm.js +297 -0
  89. package/dist/daily-log-generator-CH_KuWHs.js +469 -0
  90. package/dist/daily-log-generator-CsQPHl4A.d.ts +73 -0
  91. package/dist/daily-spend-estimator-DiwzBlo1.d.ts +8 -0
  92. package/dist/data-bootstrap-Bl3lthzE.d.ts +30 -0
  93. package/dist/data-bootstrap-P8sie9WA.js +172 -0
  94. package/dist/data-paths-C7sNdqvP.d.ts +22 -0
  95. package/dist/derived-suppression-cache-CI0durYx.d.ts +36 -0
  96. package/dist/diagnostic-encoder.d.ts +6 -0
  97. package/dist/diagnostic-encoder.js +29 -0
  98. package/dist/embedding-provider-client-Q_hJonK9.js +294 -0
  99. package/dist/embedding-provider-client-vynZMuit.d.ts +94 -0
  100. package/dist/entry-metadata-parser-BIVSMCIk.js +20 -0
  101. package/dist/error-D-wb-rZw.js +23 -0
  102. package/dist/error-signals-D8_3vYu4.d.ts +28 -0
  103. package/dist/error-signals-lMH2uAdV.js +134 -0
  104. package/dist/errors-CeVRxf9V.d.ts +49 -0
  105. package/dist/extraction-text-sanitizer-B_AhIecT.d.ts +32 -0
  106. package/dist/i18n-text-DbD6Qify.d.ts +13 -0
  107. package/dist/index-0aqS8pVF.d.ts +351 -0
  108. package/dist/index-BO4RU_mc.d.ts +650 -0
  109. package/dist/index-DL8AVj0Z.d.ts +571 -0
  110. package/dist/index.d.ts +65 -0
  111. package/dist/index.js +1201 -0
  112. package/dist/intent-analyzer-B5XVrWwe.d.ts +28 -0
  113. package/dist/internal/config/index.d.ts +2 -0
  114. package/dist/internal/config/index.js +581 -0
  115. package/dist/internal/config/plugin-config-schema.d.ts +2 -0
  116. package/dist/internal/config/plugin-config-schema.js +2 -0
  117. package/dist/internal/config/product-mode.d.ts +2 -0
  118. package/dist/internal/config/product-mode.js +36 -0
  119. package/dist/internal/config/settings.d.ts +2 -0
  120. package/dist/internal/config/settings.js +265 -0
  121. package/dist/internal/config/skill-categories.d.ts +4 -0
  122. package/dist/internal/config/skill-categories.js +68 -0
  123. package/dist/internal/config/skin-defaults.d.ts +6 -0
  124. package/dist/internal/config/skin-defaults.js +6 -0
  125. package/dist/internal/contract/discovery.d.ts +10 -0
  126. package/dist/internal/contract/discovery.js +53 -0
  127. package/dist/internal/contract/profile.d.ts +2 -0
  128. package/dist/internal/contract/profile.js +44 -0
  129. package/dist/internal/contract/routes.d.ts +2 -0
  130. package/dist/internal/contract/routes.js +62 -0
  131. package/dist/internal/engine/bindings/memory-cli-shared.d.ts +47 -0
  132. package/dist/internal/engine/bindings/memory-cli-shared.js +84 -0
  133. package/dist/internal/engine/bindings/memory-forget-tool.d.ts +5 -0
  134. package/dist/internal/engine/bindings/memory-forget-tool.js +66 -0
  135. package/dist/internal/engine/bindings/memory-list-tool.d.ts +5 -0
  136. package/dist/internal/engine/bindings/memory-list-tool.js +57 -0
  137. package/dist/internal/engine/bindings/memory-recall-tool.d.ts +10 -0
  138. package/dist/internal/engine/bindings/memory-recall-tool.js +283 -0
  139. package/dist/internal/engine/bindings/memory-reflection-resolve-tool.d.ts +5 -0
  140. package/dist/internal/engine/bindings/memory-reflection-resolve-tool.js +150 -0
  141. package/dist/internal/engine/bindings/memory-stats-tool.d.ts +5 -0
  142. package/dist/internal/engine/bindings/memory-stats-tool.js +43 -0
  143. package/dist/internal/engine/bindings/memory-store-tool.d.ts +5 -0
  144. package/dist/internal/engine/bindings/memory-store-tool.js +165 -0
  145. package/dist/internal/engine/bindings/memory-tool-access.d.ts +16 -0
  146. package/dist/internal/engine/bindings/memory-tool-access.js +46 -0
  147. package/dist/internal/engine/bindings/memory-tool-formatting.d.ts +62 -0
  148. package/dist/internal/engine/bindings/memory-tool-formatting.js +106 -0
  149. package/dist/internal/engine/bindings/memory-tool-results.d.ts +8 -0
  150. package/dist/internal/engine/bindings/memory-tool-results.js +41 -0
  151. package/dist/internal/engine/bindings/memory-tool-schemas.d.ts +109 -0
  152. package/dist/internal/engine/bindings/memory-tool-schemas.js +75 -0
  153. package/dist/internal/engine/bindings/memory-update-tool.d.ts +5 -0
  154. package/dist/internal/engine/bindings/memory-update-tool.js +127 -0
  155. package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.d.ts +14 -0
  156. package/dist/internal/engine/bindings/sno-station-mem-ambient-learning-hook.js +248 -0
  157. package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.d.ts +12 -0
  158. package/dist/internal/engine/bindings/sno-station-mem-auto-recall-hook.js +324 -0
  159. package/dist/internal/engine/bindings/sno-station-mem-hook-types.d.ts +56 -0
  160. package/dist/internal/engine/bindings/sno-station-mem-hook-types.js +1 -0
  161. package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.d.ts +14 -0
  162. package/dist/internal/engine/bindings/sno-station-mem-insight-distill-factory.js +119 -0
  163. package/dist/internal/engine/bindings/sno-station-mem-message-transcript.d.ts +52 -0
  164. package/dist/internal/engine/bindings/sno-station-mem-message-transcript.js +145 -0
  165. package/dist/internal/engine/bindings/sno-station-mem-reset-hook.d.ts +10 -0
  166. package/dist/internal/engine/bindings/sno-station-mem-reset-hook.js +9 -0
  167. package/dist/internal/engine/bindings/sno-station-mem-runtime-mode.d.ts +36 -0
  168. package/dist/internal/engine/bindings/sno-station-mem-runtime-mode.js +104 -0
  169. package/dist/internal/engine/bindings/sno-station-mem-session-state.d.ts +10 -0
  170. package/dist/internal/engine/bindings/sno-station-mem-session-state.js +27 -0
  171. package/dist/internal/engine/eval/trace.d.ts +2 -0
  172. package/dist/internal/engine/eval/trace.js +151 -0
  173. package/dist/internal/engine/extraction/ambient-learning-text-normalizer.d.ts +2 -0
  174. package/dist/internal/engine/extraction/ambient-learning-text-normalizer.js +2 -0
  175. package/dist/internal/engine/extraction/atomic-memory-extraction.d.ts +2 -0
  176. package/dist/internal/engine/extraction/atomic-memory-extraction.js +2 -0
  177. package/dist/internal/engine/extraction/b-profile-section-dictionary-provider.d.ts +2 -0
  178. package/dist/internal/engine/extraction/b-profile-section-dictionary-provider.js +2 -0
  179. package/dist/internal/engine/extraction/b-profile-section-rekey.d.ts +49 -0
  180. package/dist/internal/engine/extraction/b-profile-section-rekey.js +730 -0
  181. package/dist/internal/engine/extraction/capture-policy-detector.d.ts +2 -0
  182. package/dist/internal/engine/extraction/capture-policy-detector.js +2 -0
  183. package/dist/internal/engine/extraction/embedding-provider-client.d.ts +2 -0
  184. package/dist/internal/engine/extraction/embedding-provider-client.js +2 -0
  185. package/dist/internal/engine/extraction/extraction-text-sanitizer.d.ts +2 -0
  186. package/dist/internal/engine/extraction/extraction-text-sanitizer.js +114 -0
  187. package/dist/internal/engine/extraction/memory-metadata-codec.d.ts +3 -0
  188. package/dist/internal/engine/extraction/memory-metadata-codec.js +2 -0
  189. package/dist/internal/engine/extraction/memory-temporality-classifier.d.ts +2 -0
  190. package/dist/internal/engine/extraction/memory-temporality-classifier.js +2 -0
  191. package/dist/internal/engine/i18n/all-resources.d.ts +2 -0
  192. package/dist/internal/engine/i18n/all-resources.js +2 -0
  193. package/dist/internal/engine/i18n/locales.d.ts +2 -0
  194. package/dist/internal/engine/i18n/locales.js +19 -0
  195. package/dist/internal/engine/i18n/res/_types.d.ts +2 -0
  196. package/dist/internal/engine/i18n/res/_types.js +11 -0
  197. package/dist/internal/engine/observability/adapter.d.ts +2 -0
  198. package/dist/internal/engine/observability/adapter.js +2 -0
  199. package/dist/internal/engine/observability/best-effort.d.ts +2 -0
  200. package/dist/internal/engine/observability/best-effort.js +41 -0
  201. package/dist/internal/engine/observability/early-diagnostics.js +2 -0
  202. package/dist/internal/engine/observability/memory-snapshot.d.ts +2 -0
  203. package/dist/internal/engine/observability/memory-snapshot.js +44 -0
  204. package/dist/internal/engine/observability/observable-embedder.d.ts +2 -0
  205. package/dist/internal/engine/observability/observable-embedder.js +43 -0
  206. package/dist/internal/engine/observability/observable-llm-client.d.ts +2 -0
  207. package/dist/internal/engine/observability/observable-llm-client.js +98 -0
  208. package/dist/internal/engine/observability/observable-memory-store.d.ts +2 -0
  209. package/dist/internal/engine/observability/observable-memory-store.js +701 -0
  210. package/dist/internal/engine/observability/observable-retriever.d.ts +2 -0
  211. package/dist/internal/engine/observability/observable-retriever.js +95 -0
  212. package/dist/internal/engine/observability/runtime-diagnostics.d.ts +13 -0
  213. package/dist/internal/engine/observability/runtime-diagnostics.js +96 -0
  214. package/dist/internal/engine/observability/session-registry.d.ts +2 -0
  215. package/dist/internal/engine/observability/session-registry.js +58 -0
  216. package/dist/internal/engine/observability/version-metadata.d.ts +7 -0
  217. package/dist/internal/engine/observability/version-metadata.js +59 -0
  218. package/dist/internal/engine/operations/daily-spend-estimator.d.ts +2 -0
  219. package/dist/internal/engine/operations/daily-spend-estimator.js +97 -0
  220. package/dist/internal/engine/operations/learning-file-maintenance.d.ts +2 -0
  221. package/dist/internal/engine/operations/learning-file-maintenance.js +255 -0
  222. package/dist/internal/engine/operations/memory-tier-promoter.d.ts +2 -0
  223. package/dist/internal/engine/operations/memory-tier-promoter.js +95 -0
  224. package/dist/internal/engine/operations/runtime-audit-log.d.ts +3 -0
  225. package/dist/internal/engine/operations/runtime-audit-log.js +3 -0
  226. package/dist/internal/engine/provider/canonical-memory-corpus.d.ts +38 -0
  227. package/dist/internal/engine/provider/canonical-memory-corpus.js +253 -0
  228. package/dist/internal/engine/provider/provider-registration.d.ts +39 -0
  229. package/dist/internal/engine/provider/provider-registration.js +2 -0
  230. package/dist/internal/engine/provider/provider-row-renderer.d.ts +7 -0
  231. package/dist/internal/engine/provider/provider-row-renderer.js +53 -0
  232. package/dist/internal/engine/provider/provider-types.d.ts +2 -0
  233. package/dist/internal/engine/provider/provider-types.js +1 -0
  234. package/dist/internal/engine/reflection/daily-log-generator.d.ts +2 -0
  235. package/dist/internal/engine/reflection/daily-log-generator.js +2 -0
  236. package/dist/internal/engine/reflection/reflection-command-hooks.d.ts +2 -0
  237. package/dist/internal/engine/reflection/reflection-command-hooks.js +2 -0
  238. package/dist/internal/engine/reflection/reflection-deps.d.ts +2 -0
  239. package/dist/internal/engine/reflection/reflection-deps.js +1 -0
  240. package/dist/internal/engine/reflection/reflection-embedded-generator.d.ts +2 -0
  241. package/dist/internal/engine/reflection/reflection-embedded-generator.js +2 -0
  242. package/dist/internal/engine/reflection/reflection-injection-hooks.d.ts +2 -0
  243. package/dist/internal/engine/reflection/reflection-injection-hooks.js +219 -0
  244. package/dist/internal/engine/reflection/reflection-lifecycle-hooks.d.ts +2 -0
  245. package/dist/internal/engine/reflection/reflection-lifecycle-hooks.js +63 -0
  246. package/dist/internal/engine/reflection/strategy-hook-runner.d.ts +4 -0
  247. package/dist/internal/engine/reflection/strategy-hook-runner.js +3 -0
  248. package/dist/internal/engine/rem/classifier.d.ts +2 -0
  249. package/dist/internal/engine/rem/classifier.js +180 -0
  250. package/dist/internal/engine/rem/index.d.ts +4 -0
  251. package/dist/internal/engine/rem/index.js +2551 -0
  252. package/dist/internal/engine/rem/types.d.ts +2 -0
  253. package/dist/internal/engine/rem/types.js +40 -0
  254. package/dist/internal/engine/retrieval/access-tracker.d.ts +2 -0
  255. package/dist/internal/engine/retrieval/access-tracker.js +314 -0
  256. package/dist/internal/engine/retrieval/intent-analyzer.d.ts +2 -0
  257. package/dist/internal/engine/retrieval/intent-analyzer.js +27 -0
  258. package/dist/internal/engine/retrieval/retrieval-gate.d.ts +2 -0
  259. package/dist/internal/engine/retrieval/retrieval-gate.js +47 -0
  260. package/dist/internal/engine/retrieval/retriever.d.ts +2 -0
  261. package/dist/internal/engine/retrieval/retriever.js +2 -0
  262. package/dist/internal/engine/security/memory-scope-policy.d.ts +2 -0
  263. package/dist/internal/engine/security/memory-scope-policy.js +2 -0
  264. package/dist/internal/engine/security/redact.d.ts +2 -0
  265. package/dist/internal/engine/security/redact.js +34 -0
  266. package/dist/internal/engine/security/scopes.d.ts +3 -0
  267. package/dist/internal/engine/security/scopes.js +3 -0
  268. package/dist/internal/engine/shared/errors.d.ts +2 -0
  269. package/dist/internal/engine/shared/errors.js +66 -0
  270. package/dist/internal/engine/shared/i18n-text.d.ts +2 -0
  271. package/dist/internal/engine/shared/i18n-text.js +58 -0
  272. package/dist/internal/engine/shared/iso-date-time.d.ts +16 -0
  273. package/dist/internal/engine/shared/iso-date-time.js +40 -0
  274. package/dist/internal/engine/shared/lru.d.ts +2 -0
  275. package/dist/internal/engine/shared/lru.js +30 -0
  276. package/dist/internal/engine/shared/memory-kind-policy.d.ts +2 -0
  277. package/dist/internal/engine/shared/memory-kind-policy.js +64 -0
  278. package/dist/internal/engine/shared/paths.d.ts +3 -0
  279. package/dist/internal/engine/shared/paths.js +2 -0
  280. package/dist/internal/engine/shared/types.d.ts +3 -0
  281. package/dist/internal/engine/shared/types.js +25 -0
  282. package/dist/internal/engine/shared/utils.d.ts +2 -0
  283. package/dist/internal/engine/shared/utils.js +175 -0
  284. package/dist/internal/engine/telemetry/memory-telemetry-api.d.ts +134 -0
  285. package/dist/internal/engine/telemetry/memory-telemetry-api.js +730 -0
  286. package/dist/internal/engine/telemetry/memory-telemetry-observability.d.ts +24 -0
  287. package/dist/internal/engine/telemetry/memory-telemetry-observability.js +133 -0
  288. package/dist/internal/engine/telemetry/memory-telemetry-outbox.d.ts +2 -0
  289. package/dist/internal/engine/telemetry/memory-telemetry-outbox.js +443 -0
  290. package/dist/internal/engine/telemetry/memory-telemetry-types.d.ts +2 -0
  291. package/dist/internal/engine/telemetry/memory-telemetry-types.js +21 -0
  292. package/dist/internal/engine/telemetry/observe-ledger.d.ts +30 -0
  293. package/dist/internal/engine/telemetry/observe-ledger.js +111 -0
  294. package/dist/internal/model/agent-llm-port.d.ts +2 -0
  295. package/dist/internal/model/agent-llm-port.js +1 -0
  296. package/dist/internal/model/llm-client.d.ts +3 -0
  297. package/dist/internal/model/llm-client.js +3 -0
  298. package/dist/internal/model/llm-failure.d.ts +26 -0
  299. package/dist/internal/model/llm-failure.js +63 -0
  300. package/dist/internal/model/llm-mode-routing.d.ts +22 -0
  301. package/dist/internal/model/llm-mode-routing.js +37 -0
  302. package/dist/internal/store/atomic-memory-cutover-sql.d.ts +13 -0
  303. package/dist/internal/store/atomic-memory-cutover-sql.js +237 -0
  304. package/dist/internal/store/connection.d.ts +2 -0
  305. package/dist/internal/store/connection.js +2 -0
  306. package/dist/internal/store/data-bootstrap.d.ts +2 -0
  307. package/dist/internal/store/data-bootstrap.js +2 -0
  308. package/dist/internal/store/data-paths.d.ts +2 -0
  309. package/dist/internal/store/data-paths.js +65 -0
  310. package/dist/internal/store/maintenance.d.ts +75 -0
  311. package/dist/internal/store/maintenance.js +1401 -0
  312. package/dist/internal/store/memory-kinds-cutover-migrator.d.ts +28 -0
  313. package/dist/internal/store/memory-kinds-cutover-migrator.js +415 -0
  314. package/dist/internal/store/migrations.d.ts +27 -0
  315. package/dist/internal/store/migrations.js +201 -0
  316. package/dist/internal/store/sqlite-runtime.d.ts +2 -0
  317. package/dist/internal/store/sqlite-runtime.js +109 -0
  318. package/dist/internal/store/store.d.ts +3 -0
  319. package/dist/internal/store/store.js +2 -0
  320. package/dist/learning-file-maintenance-9b-Ut6Kv.d.ts +30 -0
  321. package/dist/llm-client-BSYUAcUb.js +1374 -0
  322. package/dist/llm-client-DOTSGzhe.d.ts +51 -0
  323. package/dist/locales-JxGmkG6t.d.ts +7 -0
  324. package/dist/log-site-catalog.generated-BMKWj-Nx.js +2321 -0
  325. package/dist/log-site-catalog.json +1 -0
  326. package/dist/lru-DyoaTyIs.d.ts +14 -0
  327. package/dist/memdump.d.ts +52 -0
  328. package/dist/memdump.js +163 -0
  329. package/dist/memory-entry-projector-DHo8jH10.js +1015 -0
  330. package/dist/memory-kind-policy-BiuRjSJc.d.ts +23 -0
  331. package/dist/memory-metadata-codec-DL1sbJiN.js +577 -0
  332. package/dist/memory-metadata-codec-DUvsYe_X.d.ts +68 -0
  333. package/dist/memory-runtime-DqUS4cWi.js +19459 -0
  334. package/dist/memory-scope-policy-1gg5QlWZ.d.ts +67 -0
  335. package/dist/memory-scope-policy-DICSCURA.js +283 -0
  336. package/dist/memory-snapshot-CqY1BUUA.d.ts +16 -0
  337. package/dist/memory-source-order-YMRoeDLd.js +134 -0
  338. package/dist/memory-store-base-CQ_bHlYA.d.ts +1094 -0
  339. package/dist/memory-store-shared-DqpWJXZZ.js +158 -0
  340. package/dist/memory-store-write-validation-B_hDG2zf.js +243 -0
  341. package/dist/memory-telemetry-incidents-BWA9kcZx.js +8 -0
  342. package/dist/memory-telemetry-metadata-CA7W8tK6.js +77 -0
  343. package/dist/memory-telemetry-outbox-ddTdImt7.d.ts +74 -0
  344. package/dist/memory-telemetry-receipts-MAJecvXQ.d.ts +34 -0
  345. package/dist/memory-telemetry-receipts-YsxbwOAf.js +91 -0
  346. package/dist/memory-telemetry-types-CufVmrCs.d.ts +14 -0
  347. package/dist/memory-temporality-classifier-BUjXG5o7.js +184 -0
  348. package/dist/memory-temporality-classifier-Brht5jyK.d.ts +43 -0
  349. package/dist/memory-tier-promoter-CqGxCkue.d.ts +43 -0
  350. package/dist/memory-tool-dependencies-CatGzI9R.d.ts +14 -0
  351. package/dist/memory-tool-dependencies-DjUoelnm.js +19 -0
  352. package/dist/model-call-table-CspopkBg.js +563 -0
  353. package/dist/model-reply-text-CKopStP2.js +334 -0
  354. package/dist/observable-embedder-CNNe9gkg.d.ts +17 -0
  355. package/dist/observable-llm-client-CRuKlefQ.d.ts +21 -0
  356. package/dist/observable-memory-store-CnGDpOY5.d.ts +73 -0
  357. package/dist/observable-retriever-Cooc62Qw.d.ts +23 -0
  358. package/dist/operation-cancellation-CTKWwNHk.js +81 -0
  359. package/dist/paths-qVF-5uBq.d.ts +1 -0
  360. package/dist/plugin-config-schema-C0UUbOmG.d.ts +413 -0
  361. package/dist/plugin-config-schema-YJLyIIcJ.js +361 -0
  362. package/dist/profile-TWPJ_myr.d.ts +13 -0
  363. package/dist/profile-section-writer-BhGKPBjw.js +2149 -0
  364. package/dist/provider-registration-CF1tg8Xy.js +721 -0
  365. package/dist/provider-runtime-types-DFhtdA6d.d.ts +134 -0
  366. package/dist/provider-types-CZGm6v88.d.ts +23 -0
  367. package/dist/redact-BTIZcHQL.d.ts +5 -0
  368. package/dist/reflection-command-hooks-ClUl-iv6.js +644 -0
  369. package/dist/reflection-command-hooks-G1-dI2Av.d.ts +32 -0
  370. package/dist/reflection-deps-Ccm2Jf5W.d.ts +19 -0
  371. package/dist/reflection-embedded-generator-BRvqV5N-.js +130 -0
  372. package/dist/reflection-embedded-generator-kfQfKEYV.d.ts +14 -0
  373. package/dist/reflection-injection-hooks-CS_uhjoD.d.ts +43 -0
  374. package/dist/reflection-lifecycle-hooks-CRSpUdLi.d.ts +27 -0
  375. package/dist/relation-dictionary-Cev1kyLe.js +4248 -0
  376. package/dist/release-download.d.ts +8 -0
  377. package/dist/release-download.js +32 -0
  378. package/dist/relevant-memories-context-m8nuFZcM.js +23 -0
  379. package/dist/rem-batch-executor-Dg2HgROM.js +2 -0
  380. package/dist/rem-batch-executor-DuC4nYyf.js +31640 -0
  381. package/dist/rem-consumer-retrieval-CR0dtcHR.js +246 -0
  382. package/dist/rem-trigger-Hn6od8IV.js +730 -0
  383. package/dist/resolver-Dy-hnjCR.js +103 -0
  384. package/dist/results-C3ZLroMb.js +140 -0
  385. package/dist/retrieval-gate-Bx2Zg7jD.d.ts +31 -0
  386. package/dist/retriever-Dxss5fRv.d.ts +469 -0
  387. package/dist/retriever-ZoLvPRHq.js +1757 -0
  388. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  389. package/dist/routes-DLHT__Ay.js +60 -0
  390. package/dist/runtime-audit-log-BCeujlEp.js +430 -0
  391. package/dist/runtime-audit-log-zpQiR9HT.d.ts +149 -0
  392. package/dist/scopes-CSOn6zdd.js +71 -0
  393. package/dist/scopes-CtrY-YQ3.d.ts +23 -0
  394. package/dist/server-CSvxchqg.js +1412 -0
  395. package/dist/session-registry-DBoE7AvZ.d.ts +14 -0
  396. package/dist/sidecar/main.d.ts +1 -0
  397. package/dist/sidecar/main.js +56 -0
  398. package/dist/signed-registry-constants-Bv0fgZ8Q.d.ts +12 -0
  399. package/dist/signed-registry-constants-yq3DlG3B.js +82 -0
  400. package/dist/sno-station-mem-runtime-dependencies-CoWqUMbK.js +30 -0
  401. package/dist/sno-station-mem-runtime-dependencies-DHESIuPn.d.ts +31 -0
  402. package/dist/sqlite-runtime-L_ywirTm.d.ts +58 -0
  403. package/dist/store-C8sUTssE.d.ts +27 -0
  404. package/dist/store-nPql26YM.js +5830 -0
  405. package/dist/strategy-hook-runner-Bxy851Xx.d.ts +16 -0
  406. package/dist/strategy-hook-runner-Djify5Rw.js +110 -0
  407. package/dist/token-counter-BD1f48Km.js +73 -0
  408. package/dist/trace-C1JxMfs-.d.ts +86 -0
  409. package/dist/types-CWdhXLxr.d.ts +477 -0
  410. package/dist/types-pVomjzqR.d.ts +132 -0
  411. package/dist/unplaced-candidate-migration-CbQHzcEM.js +940 -0
  412. package/dist/utils-Ctb9aevJ.d.ts +34 -0
  413. package/drizzle/0000_initial.sql +10 -0
  414. package/drizzle/0001_virtual_tables.sql +34 -0
  415. package/drizzle/0002_add_indexes.sql +11 -0
  416. package/drizzle/0003_content_hash_lookup_index.sql +2 -0
  417. package/drizzle/0004_chunk_swap.sql +60 -0
  418. package/drizzle/0005_drop_legacy_vec_fts.sql +10 -0
  419. package/drizzle/0006_fts_simple.sql +45 -0
  420. package/drizzle/0007_restore_simple_fts.sql +40 -0
  421. package/drizzle/0008_widen_memory_hash_unique_index.sql +4 -0
  422. package/drizzle/0009_memory_telemetry.sql +109 -0
  423. package/drizzle/0010_provider_mappings.sql +27 -0
  424. package/drizzle/0011_db_optimization.sql +82 -0
  425. package/drizzle/0012_b_profile_lanes.sql +9 -0
  426. package/drizzle/0013_extraction_timestamps.sql +6 -0
  427. package/drizzle/0014_extraction_timestamp_source.sql +3 -0
  428. package/drizzle/0015_task_lifecycle_state.sql +101 -0
  429. package/drizzle/0016_task_lifecycle_legacy_migration.sql +144 -0
  430. package/drizzle/0017_unplaced_candidates.sql +15 -0
  431. package/drizzle/0018_rem_batch_chassis.sql +71 -0
  432. package/drizzle/0019_profile_recovery_entries.sql +17 -0
  433. package/drizzle/0020_rem_scan_retry_progress.down.sql +14 -0
  434. package/drizzle/0020_rem_scan_retry_progress.sql +24 -0
  435. package/drizzle/0021_rem_two_facet_storage.down.sql +14 -0
  436. package/drizzle/0021_rem_two_facet_storage.sql +42 -0
  437. package/drizzle/0022_rem_facet_chunk_versions.down.sql +4 -0
  438. package/drizzle/0022_rem_facet_chunk_versions.sql +4 -0
  439. package/drizzle/0023_rem_defect_ledger.sql +40 -0
  440. package/drizzle/0024_rem_unbuilt_foundations.down.sql +13 -0
  441. package/drizzle/0024_rem_unbuilt_foundations.sql +65 -0
  442. package/drizzle/0025_rem_journal_operation_constraint.sql +28 -0
  443. package/drizzle/0026_rem_journal_detail.sql +1 -0
  444. package/drizzle/0027_legacy_nodix_namespace.sql +3 -0
  445. package/drizzle/0028_memory_timezone.sql +23 -0
  446. package/drizzle/0029_rem_write_hash_symmetry.sql +17 -0
  447. package/drizzle/0030_atomic_memory_additive.sql +105 -0
  448. package/drizzle/0031_atomic_extraction_ledger.sql +23 -0
  449. package/drizzle/0032_atomic_memory_entities.sql +12 -0
  450. package/drizzle/0033_todo_own_store.sql +99 -0
  451. package/drizzle/meta/0000_snapshot.json +87 -0
  452. package/drizzle/meta/_journal.json +244 -0
  453. package/generated/rem-operations.json +1 -0
  454. package/package.json +126 -4
  455. package/scripts/download-simple-tokenizer.mjs +254 -0
  456. package/settings.default.json +243 -0
  457. package/skills/extract-atomic-memory/SKILL.md +298 -0
  458. package/skills/extract-atomic-memory/references/account-for-turns.md +9 -0
  459. package/skills/extract-atomic-memory/references/calendar-meaning.md +64 -0
  460. package/skills/extract-atomic-memory/references/capture.md +120 -0
  461. package/skills/extract-atomic-memory/references/enrichment.md +92 -0
  462. package/skills/extract-atomic-memory/references/missing-durable-half.md +6 -0
  463. package/skills/extract-atomic-memory/references/progress-classification.md +21 -0
  464. package/skills/extract-atomic-memory/references/resolve-subject.md +19 -0
  465. package/skills/extract-atomic-memory/references/surrounding-context.md +10 -0
  466. package/skills/extract-atomic-memory/references/user-subject-guard.md +29 -0
  467. package/skills/judge-group-closure/SKILL.md +25 -0
  468. package/skills/judge-rem-memory-updates/SKILL.md +56 -0
  469. package/skills/judge-rem-memory-updates/agents/openai.yaml +4 -0
  470. package/skills/judge-retired-position/SKILL.md +25 -0
  471. package/skills/judge-task-lifecycle/SKILL.md +45 -0
  472. package/skills/key-state-attribute/SKILL.md +16 -0
  473. package/skills/resolve-entity-identity/SKILL.md +24 -0
  474. package/skills/resolve-relative-time/SKILL.md +22 -0
  475. package/sqlite-extensions/LICENSE-simple-tokenizer +28 -0
  476. package/sqlite-extensions/LICENSE-sqlite-vec +21 -0
  477. package/sqlite-extensions/linux-arm64/vec0.so +0 -0
  478. package/sqlite-extensions/linux-x64/vec0.so +0 -0
@@ -0,0 +1,1401 @@
1
+ import { BACKUP_INTERVAL_MS, BACKUP_MIN_FREE_DISK_RATIO, FTS_MERGE_INTERVAL_MS, INTEGRITY_CHECK_INTERVAL_MS, MAINTENANCE_TICK_MS, PLANNER_STATISTICS_INTERVAL_MS, REM_TRIGGER_CHECK_INTERVAL_MS, USAGE_EVENT_RETENTION_INTERVAL_MS, USAGE_OUTBOX_INTERVAL_MS } from "../config/index.js";
2
+ import { REM_SIDECAR_TOKEN_HEADER } from "../contract/routes.js";
3
+ import "../engine/shared/utils.js";
4
+ import "../../coding-skin.js";
5
+ import "../config/settings.js";
6
+ import "../../llm-client-BSYUAcUb.js";
7
+ import "../model/llm-mode-routing.js";
8
+ import { n as appendAuditEntryStrict, o as getAuditPath, t as appendAuditEntry } from "../../runtime-audit-log-BCeujlEp.js";
9
+ import { getDiscoveryPath, getSnoStationMemStateDir } from "../contract/profile.js";
10
+ import "../engine/shared/paths.js";
11
+ import { defineRemSafetyGuard, requireRemOwnerDecisions } from "../engine/rem/index.js";
12
+ import { n as state_vocabulary_default, r as attribute_dictionary_default } from "../../relation-dictionary-Cev1kyLe.js";
13
+ import "../../memory-store-shared-DqpWJXZZ.js";
14
+ import { n as loadSqliteVecExtension } from "../../unplaced-candidate-migration-CbQHzcEM.js";
15
+ import { openSqliteDatabase } from "./sqlite-runtime.js";
16
+ import "../../connection-C9YD5sgm.js";
17
+ import "../../store-nPql26YM.js";
18
+ import "../../embedding-provider-client-Q_hJonK9.js";
19
+ import "../../retriever-ZoLvPRHq.js";
20
+ import { t as recordMemoryTelemetryIncident } from "../../memory-telemetry-incidents-BWA9kcZx.js";
21
+ import { z } from "zod";
22
+ import "@snoai/chunking";
23
+ import { createLogger } from "@snoai/utils/logger";
24
+ import path from "node:path";
25
+ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
26
+ import { createHash, randomUUID } from "node:crypto";
27
+ import { AsyncLocalStorage } from "node:async_hooks";
28
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync } from "node:fs";
29
+ import { fileURLToPath } from "node:url";
30
+ import { runIntegrityCheck } from "@snoai/sqlite-crypto";
31
+ import { withLogContext as withLogContext$1 } from "@snoai/utils/log-context";
32
+ //#region src/store/backup.ts
33
+ /** @file backup.ts
34
+ * @purpose Exports, imports, and validates persisted memory data for backup workflows.
35
+ * @boundary Memory store serialization, schema compatibility, and CLI operations.
36
+ * @see store.ts, memory-management-cli.ts, schema.ts.
37
+ */
38
+ const log$4 = createLogger("sno-station-mem:backup");
39
+ const BACKUP_PREFIX = "sno-station-mem-";
40
+ const BACKUP_SUFFIX = ".sqlite";
41
+ const BACKUP_FILE_REGEX = /^sno-station-mem-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}-p\d+-[a-z0-9]+\.sqlite$/;
42
+ /** Format `Date` as a unique UTC timestamp (colon-free for FS-safety). */
43
+ function formatBackupTimestamp(now) {
44
+ return `${now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "")}-p${process.pid}-${process.hrtime.bigint().toString(36)}`;
45
+ }
46
+ /** Escapes a value for SQLite string-literal use in backup statements. */
47
+ function quoteSqliteStringLiteral(value) {
48
+ return `'${value.replaceAll("'", "''")}'`;
49
+ }
50
+ /**
51
+ * Validates backup file placement before SQLite is allowed to write outside the state directory.
52
+ */
53
+ function assertSafeBackupPath(stateDir, filePath) {
54
+ const resolvedStateDir = realpathSync(stateDir);
55
+ const resolvedBackupPath = path.resolve(filePath);
56
+ const relativePath = path.relative(resolvedStateDir, resolvedBackupPath);
57
+ if (relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath)) throw new Error(`Backup path escapes configured state directory: ${filePath}`);
58
+ if (!BACKUP_FILE_REGEX.test(path.basename(resolvedBackupPath))) throw new Error(`Unexpected backup filename: ${path.basename(resolvedBackupPath)}`);
59
+ return resolvedBackupPath;
60
+ }
61
+ /** Thrown when the volume cannot hold a backup even after old backups are removed. */
62
+ var BackupSkippedLowDiskError = class extends Error {
63
+ neededBytes;
64
+ freeBytes;
65
+ reserveBytes;
66
+ constructor(neededBytes, freeBytes, reserveBytes) {
67
+ super(`backup skipped: needs ${neededBytes} bytes above a ${reserveBytes}-byte reserve, ${freeBytes} free`);
68
+ this.neededBytes = neededBytes;
69
+ this.freeBytes = freeBytes;
70
+ this.reserveBytes = reserveBytes;
71
+ this.name = "BackupSkippedLowDiskError";
72
+ }
73
+ };
74
+ function readVolumeSpace(dir) {
75
+ const info = statfsSync(dir);
76
+ return {
77
+ freeBytes: info.bavail * info.bsize,
78
+ totalBytes: info.blocks * info.bsize
79
+ };
80
+ }
81
+ /** Backup file names in the directory, oldest first (names sort by their UTC timestamp). */
82
+ function listBackups(stateDir) {
83
+ return readdirSync(stateDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith(BACKUP_PREFIX) && entry.name.endsWith(BACKUP_SUFFIX)).map((entry) => entry.name).sort();
84
+ }
85
+ function removeBackups(stateDir, names, reason) {
86
+ for (const name of names) rmSync(path.join(stateDir, name), { force: true });
87
+ if (names.length > 0) log$4.debug("pruned old backups", {
88
+ removed: names.length,
89
+ reason
90
+ }, {
91
+ event_name: "sno_station_mem.backup.pruned.old.backups",
92
+ file: "packages/memory/src/store/backup.ts",
93
+ function: "removeBackups",
94
+ site_id: "backup.pruneOldBackups.1dca69b51b"
95
+ });
96
+ }
97
+ /**
98
+ * Deletes the oldest backups until `neededBytes` fits above the volume's reserve. The newest backup
99
+ * is never deleted here: a full disk must not cost the user their last good copy. Returns whether
100
+ * the space now fits.
101
+ */
102
+ function freeSpaceForBackup(stateDir, neededBytes, readDiskSpace) {
103
+ const backups = listBackups(stateDir);
104
+ let space = readDiskSpace(stateDir);
105
+ const reserve = space.totalBytes * BACKUP_MIN_FREE_DISK_RATIO;
106
+ while (space.freeBytes - neededBytes < reserve && backups.length > 1) {
107
+ const oldest = backups.shift();
108
+ if (oldest === void 0) break;
109
+ removeBackups(stateDir, [oldest], "low-disk");
110
+ space = readDiskSpace(stateDir);
111
+ }
112
+ return space.freeBytes - neededBytes >= reserve;
113
+ }
114
+ /** Size a fresh copy of the store can reach: the main file plus its unmerged write-ahead log. */
115
+ function storeBytes(dbPath) {
116
+ const walPath = `${dbPath}-wal`;
117
+ return statSync(dbPath).size + (existsSync(walPath) ? statSync(walPath).size : 0);
118
+ }
119
+ /**
120
+ * Whether a backup is due: none exists yet, or the newest is at least BACKUP_INTERVAL_MS old.
121
+ * Read from the files so a restart never takes an extra backup.
122
+ */
123
+ function isBackupDue(stateDir, now, intervalMs = BACKUP_INTERVAL_MS) {
124
+ if (!existsSync(stateDir)) return true;
125
+ const newest = listBackups(stateDir).at(-1);
126
+ if (newest === void 0) return true;
127
+ return now - statSync(path.join(stateDir, newest)).mtimeMs >= intervalMs;
128
+ }
129
+ /** Implements run backup as the local SQLite backup scheduling operation. */
130
+ function runBackup(dbPath, stateDir, options = {}) {
131
+ const normalizedStateDir = stateDir.replaceAll("\\", path.sep);
132
+ mkdirSync(normalizedStateDir, { recursive: true });
133
+ const resolvedStateDir = realpathSync(normalizedStateDir);
134
+ const fileName = `${BACKUP_PREFIX}${formatBackupTimestamp(/* @__PURE__ */ new Date())}${BACKUP_SUFFIX}`;
135
+ const safeBackupPath = assertSafeBackupPath(resolvedStateDir, path.join(resolvedStateDir, fileName));
136
+ const tempBackupPath = `${safeBackupPath}.tmp`;
137
+ const readDiskSpace = options.readDiskSpace ?? readVolumeSpace;
138
+ const neededBytes = storeBytes(dbPath);
139
+ if (!freeSpaceForBackup(resolvedStateDir, neededBytes, readDiskSpace)) {
140
+ const space = readDiskSpace(resolvedStateDir);
141
+ throw new BackupSkippedLowDiskError(neededBytes, space.freeBytes, space.totalBytes * BACKUP_MIN_FREE_DISK_RATIO);
142
+ }
143
+ const sqlite = openSqliteDatabase(dbPath, {
144
+ readonly: true,
145
+ fileMustExist: true
146
+ });
147
+ loadSqliteVecExtension(sqlite.raw);
148
+ try {
149
+ sqlite.db.exec(`VACUUM INTO ${quoteSqliteStringLiteral(tempBackupPath)}`);
150
+ } catch (error) {
151
+ rmSync(tempBackupPath, { force: true });
152
+ throw error;
153
+ } finally {
154
+ sqlite.db.close();
155
+ }
156
+ renameSync(tempBackupPath, safeBackupPath);
157
+ const backups = listBackups(resolvedStateDir);
158
+ removeBackups(resolvedStateDir, backups.slice(0, Math.max(0, backups.length - 6)), "retention");
159
+ freeSpaceForBackup(resolvedStateDir, 0, readDiskSpace);
160
+ log$4.info("backup created", { backupPath: safeBackupPath }, {
161
+ event_name: "sno_station_mem.backup.backup.created",
162
+ file: "packages/memory/src/store/backup.ts",
163
+ function: "runBackup",
164
+ site_id: "backup.runBackup.ca114973f1"
165
+ });
166
+ return safeBackupPath;
167
+ }
168
+ //#endregion
169
+ //#region src/sidecar/config.ts
170
+ const REM_SIDECAR_HOST = "127.0.0.1";
171
+ const REM_CORRELATION_ID_HEADER = "x-rem-correlation-id";
172
+ const REM_RUN_PATH = "/rem/run";
173
+ function getRemDiscoveryPath() {
174
+ return getDiscoveryPath();
175
+ }
176
+ "0".repeat(64), "0".repeat(64), "0".repeat(64), "0".repeat(64), "0".repeat(64), "0".repeat(64);
177
+ //#endregion
178
+ //#region src/sidecar/rem-entry-foundations.ts
179
+ const sha256Schema = z.string().regex(/^[0-9a-f]{64}$/u);
180
+ const finiteRecordSchema = z.record(z.string(), z.number().finite()).refine((value) => Object.keys(value).length > 0);
181
+ const commonArtifactShape = {
182
+ schemaVersion: z.literal(1),
183
+ artifactId: z.string().min(1),
184
+ profileId: z.string().min(1),
185
+ configurationSha256: sha256Schema,
186
+ createdAt: z.string().datetime()
187
+ };
188
+ z.object({
189
+ ...commonArtifactShape,
190
+ artifactId: z.literal("p5-production-config"),
191
+ result: z.enum(["pass", "fail"]),
192
+ servedModel: z.string().min(1),
193
+ resolvedParameters: z.record(z.string(), z.unknown()).refine((value) => Object.keys(value).length > 0),
194
+ readbackSource: z.enum(["wrapper", "immutable-profile"]),
195
+ immutableProfileSha256: sha256Schema.nullable(),
196
+ wrapperInvocationId: z.string().min(1).optional(),
197
+ wrapperReadbackSha256: sha256Schema.optional()
198
+ }).strict();
199
+ z.object({
200
+ ...commonArtifactShape,
201
+ artifactId: z.literal("p6-monthly-non-regression"),
202
+ period: z.string().regex(/^\d{4}-\d{2}$/u),
203
+ minima: finiteRecordSchema,
204
+ measured: finiteRecordSchema,
205
+ result: z.enum(["pass", "fail"])
206
+ }).strict();
207
+ z.object({
208
+ ...commonArtifactShape,
209
+ artifactId: z.literal("p6-monthly-non-regression"),
210
+ kind: z.literal("owner-waiver"),
211
+ result: z.literal("pass"),
212
+ waivedBy: z.string().min(1),
213
+ waiverReason: z.string().min(1)
214
+ }).strict();
215
+ z.object({
216
+ ...commonArtifactShape,
217
+ artifactId: z.literal("p7-detector-gate-verdict"),
218
+ kind: z.literal("measured-pass"),
219
+ minima: finiteRecordSchema,
220
+ measured: finiteRecordSchema,
221
+ result: z.enum(["pass", "fail"])
222
+ }).strict();
223
+ z.object({
224
+ ...commonArtifactShape,
225
+ artifactId: z.literal("p7-detector-gate-verdict"),
226
+ kind: z.literal("owner-waiver"),
227
+ result: z.literal("pass"),
228
+ waivedBy: z.string().min(1),
229
+ waiverReason: z.string().min(1)
230
+ }).strict();
231
+ z.object({
232
+ ...commonArtifactShape,
233
+ artifactId: z.literal("population-routing"),
234
+ producedAtHead: z.string().regex(/^[0-9a-f]{40}$/u),
235
+ classifierSha256: sha256Schema,
236
+ fixtureSetSha256: sha256Schema,
237
+ derivationCommand: z.string().min(1),
238
+ invocationId: z.string().min(1),
239
+ observations: z.array(z.object({
240
+ fixtureId: z.string().min(1),
241
+ inputSha256: sha256Schema,
242
+ observedRoute: z.string().min(1),
243
+ observedVerdict: z.string().min(1)
244
+ }).strict())
245
+ }).strict();
246
+ z.object({
247
+ ...commonArtifactShape,
248
+ artifactId: z.literal("population-routing"),
249
+ kind: z.literal("owner-waiver"),
250
+ result: z.literal("pass"),
251
+ waivedBy: z.string().min(1),
252
+ waiverReason: z.string().min(1)
253
+ }).strict();
254
+ defineRemSafetyGuard({ guardId: "guard.rem.ordered-wave-entry" }, requireRemOwnerDecisions);
255
+ //#endregion
256
+ //#region src/sidecar/rem-update-judgment-skill.ts
257
+ /** @file rem-update-judgment-skill.ts
258
+ * @purpose Loads the model-readable REM update judgment method shipped with the plugin.
259
+ * @boundary Package-relative read of one trusted bundled skill.
260
+ */
261
+ const REM_UPDATE_SKILL_PATH = path.join("skills", "judge-rem-memory-updates", "SKILL.md");
262
+ const GROUP_CLOSURE_SKILL_PATH = path.join("skills", "judge-group-closure", "SKILL.md");
263
+ function resolveSkillPath(skillPath) {
264
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
265
+ for (const root of [
266
+ path.resolve(moduleDir, ".."),
267
+ path.resolve(moduleDir, "../.."),
268
+ path.resolve(moduleDir, "../../..")
269
+ ]) {
270
+ const candidate = path.join(root, skillPath);
271
+ if (existsSync(candidate)) return candidate;
272
+ }
273
+ throw new Error(`sno-station-mem REM update judgment skill is missing: ${skillPath}`);
274
+ }
275
+ const skillText = readFileSync(resolveSkillPath(REM_UPDATE_SKILL_PATH), "utf8");
276
+ readFileSync(resolveSkillPath(GROUP_CLOSURE_SKILL_PATH), "utf8");
277
+ function readSection(heading) {
278
+ const marker = `## ${heading}\n`;
279
+ const start = skillText.indexOf(marker);
280
+ if (start === -1) throw new Error(`REM update judgment skill is missing section: ${heading}`);
281
+ const bodyStart = start + marker.length;
282
+ const nextSection = skillText.indexOf("\n## ", bodyStart);
283
+ return skillText.slice(bodyStart, nextSection === -1 ? void 0 : nextSection).trim();
284
+ }
285
+ readSection("Relation Judgment"), readSection("Source Rewrite"), readSection("Rewrite Verification"), readSection("Clause Carry");
286
+ createLogger("sno-station-mem:rem-sqlite-adapter");
287
+ new AsyncLocalStorage();
288
+ createLogger("sno-station-mem:rem-sidecar:batch");
289
+ const retirementAttributeConfigSchema = z.object({ slugs: z.array(z.object({
290
+ slug: z.string(),
291
+ family: z.string().optional(),
292
+ cardinality: z.enum(["one", "many"]).optional()
293
+ })) });
294
+ const personRetirementAttributes = retirementAttributeConfigSchema.parse(attribute_dictionary_default).slugs;
295
+ const stateRetirementAttributes = retirementAttributeConfigSchema.parse(state_vocabulary_default).slugs;
296
+ const attributeFamilyBySlug = new Map(personRetirementAttributes.map((entry) => [entry.slug, entry.family]));
297
+ for (const entry of stateRetirementAttributes) attributeFamilyBySlug.set(entry.slug, entry.slug.split(".", 1)[0]);
298
+ new Set([...personRetirementAttributes, ...stateRetirementAttributes].filter((entry) => entry.cardinality === "one").map((entry) => entry.slug));
299
+ const REM_CANDIDATE_PREDICATE_SQL = `lane = 'active'
300
+ AND length(trim(text)) > 0
301
+ AND category IN ('profile', 'episodic', 'state')
302
+ AND json_valid(metadata)
303
+ AND json_extract(metadata, '$.superseded_by') IS NULL`;
304
+ function enumerateRemCandidateScopes(database) {
305
+ return database.prepare(`SELECT project_id AS scope, count(*) AS candidate_count
306
+ FROM nodix_memories
307
+ WHERE ${REM_CANDIDATE_PREDICATE_SQL}
308
+ GROUP BY project_id
309
+ ORDER BY project_id`).all().map((row) => ({
310
+ scope: row.scope,
311
+ candidateCount: row.candidate_count
312
+ }));
313
+ }
314
+ //#endregion
315
+ //#region src/sidecar/rem-trigger-state.ts
316
+ /** @file rem-trigger-state.ts
317
+ * @purpose Persists the automatic REM scheduler's strict per-scope state.
318
+ * @boundary One atomically replaced JSON file under the sno-station-mem state directory.
319
+ */
320
+ const remTriggerScopeStateSchema = z.object({
321
+ last_pass_at: z.string().datetime(),
322
+ schedule_zone: z.string().min(1).refine(isValidScheduleZone, "invalid IANA time zone"),
323
+ last_covered_count: z.number().int().nonnegative(),
324
+ last_volume_pass_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u).nullable(),
325
+ missed_window: z.object({
326
+ due_at: z.string().datetime(),
327
+ trigger: z.enum(["daily", "volume"]),
328
+ recorded_at: z.string().datetime()
329
+ }).strict().nullable().default(null),
330
+ attempts: z.object({
331
+ identity: z.string().min(1).nullable(),
332
+ count: z.number().int().min(0).max(3)
333
+ }).strict()
334
+ }).strict();
335
+ const remTriggerStateDocumentSchema = z.object({
336
+ version: z.literal(1),
337
+ scopes: z.record(z.string().min(1), remTriggerScopeStateSchema)
338
+ }).strict();
339
+ var RemTriggerStateError = class extends Error {
340
+ statePath;
341
+ constructor(statePath, cause) {
342
+ super(`REM trigger state at ${statePath} is invalid or unreadable: ${errorMessage$1(cause)}`, { cause: cause instanceof Error ? cause : void 0 });
343
+ this.statePath = statePath;
344
+ this.name = "RemTriggerStateError";
345
+ }
346
+ };
347
+ function remTriggerStatePath(stateDir) {
348
+ return path.join(stateDir, "rem-trigger-state.json");
349
+ }
350
+ async function loadRemTriggerState(stateDir) {
351
+ const statePath = remTriggerStatePath(stateDir);
352
+ let raw;
353
+ try {
354
+ raw = await readFile(statePath, "utf8");
355
+ } catch (error) {
356
+ if (isNodeError$1(error) && error.code === "ENOENT") return {
357
+ version: 1,
358
+ scopes: {}
359
+ };
360
+ throw new RemTriggerStateError(statePath, error);
361
+ }
362
+ try {
363
+ return remTriggerStateDocumentSchema.parse(JSON.parse(raw));
364
+ } catch (error) {
365
+ throw new RemTriggerStateError(statePath, error);
366
+ }
367
+ }
368
+ function ensureRemTriggerScope(state, input) {
369
+ const existing = state.scopes[input.scope];
370
+ if (existing !== void 0) return {
371
+ state,
372
+ scopeState: existing,
373
+ initialized: false
374
+ };
375
+ if (input.scope.trim().length === 0) throw new Error("REM trigger scope is required");
376
+ if (!Number.isInteger(input.candidateCount) || input.candidateCount < 0) throw new Error("REM trigger candidate count must be a nonnegative integer");
377
+ if (Number.isNaN(input.now.getTime())) throw new Error("REM trigger initialization time is invalid");
378
+ const scheduleZone = (input.resolveScheduleZone ?? resolveHostScheduleZone)();
379
+ if (!isValidScheduleZone(scheduleZone)) throw new Error(`invalid REM schedule zone: ${scheduleZone}`);
380
+ const scopeState = {
381
+ last_pass_at: input.now.toISOString(),
382
+ schedule_zone: scheduleZone,
383
+ last_covered_count: input.candidateCount,
384
+ last_volume_pass_date: null,
385
+ missed_window: null,
386
+ attempts: {
387
+ identity: null,
388
+ count: 0
389
+ }
390
+ };
391
+ return {
392
+ state: {
393
+ version: 1,
394
+ scopes: {
395
+ ...state.scopes,
396
+ [input.scope]: scopeState
397
+ }
398
+ },
399
+ scopeState,
400
+ initialized: true
401
+ };
402
+ }
403
+ async function writeRemTriggerStateAtomic$1(stateDir, state) {
404
+ const validated = remTriggerStateDocumentSchema.parse(state);
405
+ await mkdir(stateDir, {
406
+ recursive: true,
407
+ mode: 448
408
+ });
409
+ const statePath = remTriggerStatePath(stateDir);
410
+ const temporaryPath = `${statePath}.${process.pid}.${randomUUID()}.tmp`;
411
+ try {
412
+ const handle = await open(temporaryPath, "wx", 384);
413
+ try {
414
+ await handle.writeFile(JSON.stringify(validated), "utf8");
415
+ await handle.sync();
416
+ } finally {
417
+ await handle.close();
418
+ }
419
+ await rename(temporaryPath, statePath);
420
+ await syncDirectory(stateDir);
421
+ } catch (error) {
422
+ await rm(temporaryPath, { force: true });
423
+ throw error;
424
+ }
425
+ }
426
+ function resolveHostScheduleZone() {
427
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
428
+ }
429
+ function isValidScheduleZone(value) {
430
+ if (value.trim().length === 0) return false;
431
+ try {
432
+ new Intl.DateTimeFormat("en-US", { timeZone: value }).format(0);
433
+ return true;
434
+ } catch {
435
+ return false;
436
+ }
437
+ }
438
+ async function syncDirectory(directory) {
439
+ if (process.platform === "win32") return;
440
+ const handle = await open(directory, "r");
441
+ try {
442
+ await handle.sync();
443
+ } finally {
444
+ await handle.close();
445
+ }
446
+ }
447
+ function isNodeError$1(error) {
448
+ return error instanceof Error && "code" in error;
449
+ }
450
+ function errorMessage$1(error) {
451
+ return error instanceof Error ? error.message : String(error);
452
+ }
453
+ const REM_COMPLETION_AUDIT_TAIL_BYTES = 16777216;
454
+ const log$1 = createLogger("sno-station-mem:rem-trigger");
455
+ function remNeedsHost(mode, modelCalls) {
456
+ return Object.keys(modelCalls).some((id) => id.startsWith("REM") && modelCalls[id][mode] === "host");
457
+ }
458
+ const discoverySchema = z.object({
459
+ pid: z.number().int().positive(),
460
+ port: z.number().int().min(1).max(65535),
461
+ token: z.string().min(1)
462
+ }).strict();
463
+ const idleEvaluations = /* @__PURE__ */ new Map();
464
+ async function evaluateRemAutomaticTriggers(input) {
465
+ const now = input.now ?? /* @__PURE__ */ new Date();
466
+ if (!Number.isInteger(input.volumeThreshold ?? 100) || (input.volumeThreshold ?? 100) < 1) throw new Error("REM volume threshold must be a positive integer");
467
+ const auditStateDir = input.auditStateDir ?? input.stateDir;
468
+ if (Number.isNaN(now.getTime())) throw new Error("REM trigger evaluation time is invalid");
469
+ if (input.requestedOperations.length === 0) {
470
+ await recordDecision(auditStateDir, void 0, "skipped", {
471
+ row: "automatic-trigger-skipped",
472
+ reason: "product-mode-disabled"
473
+ });
474
+ return {
475
+ evaluations: 0,
476
+ dispatches: 0
477
+ };
478
+ }
479
+ if (input.mode && remNeedsHost(input.mode, input.modelCalls) && !input.hasConnectedHost?.()) {
480
+ await recordDecision(auditStateDir, void 0, "skipped", {
481
+ row: "automatic-trigger-skipped",
482
+ reason: "skipped: no host model connected"
483
+ });
484
+ return {
485
+ evaluations: 0,
486
+ dispatches: 0
487
+ };
488
+ }
489
+ let scopes;
490
+ try {
491
+ scopes = enumerateRemCandidateScopes(input.database);
492
+ } catch (error) {
493
+ await recordDecision(auditStateDir, void 0, "error", {
494
+ row: "enumeration-failed",
495
+ cause: errorMessage(error)
496
+ });
497
+ return {
498
+ evaluations: 1,
499
+ dispatches: 0
500
+ };
501
+ }
502
+ if (scopes.length === 0) {
503
+ await recordDecision(auditStateDir, void 0, "ok", { row: "no-scopes" });
504
+ return {
505
+ evaluations: 1,
506
+ dispatches: 0
507
+ };
508
+ }
509
+ let state;
510
+ let lostState = false;
511
+ try {
512
+ state = await loadRemTriggerState(input.stateDir);
513
+ } catch (error) {
514
+ for (const { scope, candidateCount } of scopes) {
515
+ const consecutiveIdle = (idleEvaluations.get(scope) ?? 0) + 1;
516
+ idleEvaluations.set(scope, consecutiveIdle);
517
+ await recordDecision(auditStateDir, scope, "error", {
518
+ row: "state-unreadable",
519
+ candidate_count: candidateCount,
520
+ cause: errorMessage(error),
521
+ consecutive_idle: consecutiveIdle
522
+ });
523
+ }
524
+ log$1.error("REM trigger state unavailable; continuing with due work", { cause: errorMessage(error) }, {
525
+ event_name: "memory.rem.trigger.state.unavailable",
526
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
527
+ function: "evaluateRemAutomaticTriggers",
528
+ site_id: "memory.rem.trigger.state.unavailable"
529
+ });
530
+ state = {
531
+ version: 1,
532
+ scopes: {}
533
+ };
534
+ lostState = true;
535
+ }
536
+ state = await applyCompletedBaselines(auditStateDir, input.stateDir, state);
537
+ let dispatches = 0;
538
+ for (const { scope, candidateCount } of scopes) try {
539
+ const ensured = ensureRemTriggerScope(state, {
540
+ scope,
541
+ now: lostState ? previousDay(now) : now,
542
+ candidateCount,
543
+ resolveScheduleZone: input.resolveScheduleZone
544
+ });
545
+ state = ensured.state;
546
+ if (ensured.initialized) await writeRemTriggerStateAtomic(input.stateDir, state);
547
+ const scopeResult = await evaluateScope(input, auditStateDir, state, scope, candidateCount, now);
548
+ state = scopeResult.state;
549
+ dispatches += scopeResult.dispatched ? 1 : 0;
550
+ } catch (error) {
551
+ log$1.error("REM automatic scope evaluation failed", {
552
+ scope,
553
+ error
554
+ }, {
555
+ event_name: "sno_station_mem.rem-trigger.rem.automatic.scope.evaluation.failed",
556
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
557
+ function: "evaluateRemAutomaticTriggers",
558
+ site_id: "rem-trigger.evaluateRemAutomaticTriggers.1d59077a5c"
559
+ });
560
+ try {
561
+ await recordDecision(auditStateDir, scope, "error", {
562
+ row: "scope-evaluation-failed",
563
+ cause: errorMessage(error)
564
+ });
565
+ } catch (auditError) {
566
+ log$1.warn("REM automatic scope failure audit failed", {
567
+ scope,
568
+ error: auditError
569
+ }, {
570
+ event_name: "sno_station_mem.rem-trigger.rem.automatic.scope.failure.audit.failed",
571
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
572
+ function: "evaluateRemAutomaticTriggers",
573
+ site_id: "rem-trigger.evaluateRemAutomaticTriggers.ebe1cdd204"
574
+ });
575
+ }
576
+ try {
577
+ state = await loadRemTriggerState(input.stateDir);
578
+ } catch (stateError) {
579
+ log$1.warn("REM automatic trigger state reload failed", {
580
+ scope,
581
+ error: stateError
582
+ }, {
583
+ event_name: "sno_station_mem.rem-trigger.rem.automatic.trigger.state.reload.failed",
584
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
585
+ function: "evaluateRemAutomaticTriggers",
586
+ site_id: "rem-trigger.evaluateRemAutomaticTriggers.46669e66ff"
587
+ });
588
+ break;
589
+ }
590
+ }
591
+ return {
592
+ evaluations: scopes.length,
593
+ dispatches
594
+ };
595
+ }
596
+ function previousDay(now) {
597
+ const day = new Date(now);
598
+ day.setUTCDate(day.getUTCDate() - 1);
599
+ return day;
600
+ }
601
+ async function writeRemTriggerStateAtomic(stateDir, state) {
602
+ try {
603
+ await writeRemTriggerStateAtomic$1(stateDir, state);
604
+ } catch (error) {
605
+ log$1.error("REM trigger state write failed; continuing dispatch", { cause: errorMessage(error) }, {
606
+ event_name: "memory.rem.trigger.state.write.failed",
607
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
608
+ function: "evaluateRemAutomaticTriggers",
609
+ site_id: "memory.rem.trigger.state.write.failed"
610
+ });
611
+ }
612
+ }
613
+ async function evaluateScope(input, auditStateDir, state, scope, candidateCount, now) {
614
+ const scopeState = requiredScopeState(state, scope);
615
+ const zone = scopeState.schedule_zone;
616
+ const nextDue = nextDailyDue(scopeState);
617
+ const localDate = localDateAt(now, zone);
618
+ const growth = candidateCount - scopeState.last_covered_count;
619
+ const scheduledDue = computeRemDailyDue(scopeState.last_pass_at, zone);
620
+ const pendingVolumeToday = scopeState.attempts.identity === remAutomaticCorrelationId("volume", scope, localDate);
621
+ const pendingDailyToday = localDateAt(scheduledDue, zone) === localDate && scopeState.attempts.identity === remAutomaticCorrelationId("daily", scope, scheduledDue.toISOString());
622
+ const dailyDue = now.getTime() >= nextDue.getTime() && !pendingVolumeToday;
623
+ const volumeDue = growth >= (input.volumeThreshold ?? 100) && scopeState.last_volume_pass_date !== localDate && !pendingDailyToday;
624
+ if (!dailyDue && !volumeDue) {
625
+ const consecutiveIdle = (idleEvaluations.get(scope) ?? 0) + 1;
626
+ idleEvaluations.set(scope, consecutiveIdle);
627
+ await recordDecision(auditStateDir, scope, "ok", {
628
+ row: "waiting-for-schedule",
629
+ next_due: nextDue.toISOString(),
630
+ milliseconds_until_due: Math.max(0, nextDue.getTime() - now.getTime()),
631
+ growth: growthDetails(candidateCount, scopeState.last_covered_count, input.volumeThreshold ?? 100),
632
+ consecutive_idle: consecutiveIdle
633
+ });
634
+ return {
635
+ state,
636
+ dispatched: false
637
+ };
638
+ }
639
+ const trigger = dailyDue ? "daily" : "volume";
640
+ if (input.tickEnabled === false) {
641
+ const nextState = replaceScopeState(state, scope, {
642
+ ...scopeState,
643
+ missed_window: {
644
+ due_at: dailyDue ? nextDue.toISOString() : now.toISOString(),
645
+ trigger,
646
+ recorded_at: now.toISOString()
647
+ }
648
+ });
649
+ await writeRemTriggerStateAtomic(input.stateDir, nextState);
650
+ return {
651
+ state: nextState,
652
+ dispatched: false
653
+ };
654
+ }
655
+ const correlationId = remAutomaticCorrelationId(trigger, scope, trigger === "daily" ? nextDue.toISOString() : localDate);
656
+ const priorAttempts = scopeState.attempts.identity === correlationId ? scopeState.attempts.count : 0;
657
+ await recordDecision(auditStateDir, scope, "ok", {
658
+ row: "dispatch",
659
+ trigger,
660
+ next_due: nextDue.toISOString(),
661
+ pass_at: now.toISOString(),
662
+ local_date: localDate,
663
+ growth: growthDetails(candidateCount, scopeState.last_covered_count, input.volumeThreshold ?? 100),
664
+ correlation_id: correlationId,
665
+ consecutive_idle: 0
666
+ });
667
+ idleEvaluations.set(scope, 0);
668
+ let nextState = replaceScopeState(state, scope, {
669
+ ...scopeState,
670
+ attempts: {
671
+ identity: correlationId,
672
+ count: Math.min(priorAttempts + 1, 3)
673
+ }
674
+ });
675
+ await writeRemTriggerStateAtomic(input.stateDir, nextState);
676
+ let cause;
677
+ try {
678
+ await dispatchRemWave(input, scope, correlationId);
679
+ } catch (error) {
680
+ cause = errorMessage(error);
681
+ }
682
+ if (cause === void 0) {
683
+ nextState = replaceScopeState(nextState, scope, {
684
+ ...requiredScopeState(nextState, scope),
685
+ missed_window: null
686
+ });
687
+ await writeRemTriggerStateAtomic(input.stateDir, nextState);
688
+ return {
689
+ state: nextState,
690
+ dispatched: true
691
+ };
692
+ }
693
+ await appendAuditEntryStrict(auditStateDir, {
694
+ event: "rem_failed",
695
+ resultStatus: "error",
696
+ scope,
697
+ details: {
698
+ outcome: "dispatch-failed",
699
+ cause,
700
+ correlation_id: correlationId,
701
+ source: "gateway-trigger"
702
+ }
703
+ });
704
+ return {
705
+ state: nextState,
706
+ dispatched: true
707
+ };
708
+ }
709
+ async function dispatchRemWave(input, scope, correlationId) {
710
+ const started = performance.now();
711
+ const discovery = discoverySchema.parse(JSON.parse(await readFile(input.discoveryPath ?? getRemDiscoveryPath(), "utf8")));
712
+ const response = await fetch(`http://${REM_SIDECAR_HOST}:${discovery.port}${REM_RUN_PATH}`, {
713
+ method: "POST",
714
+ headers: {
715
+ "content-type": "application/json",
716
+ [REM_SIDECAR_TOKEN_HEADER]: discovery.token,
717
+ [REM_CORRELATION_ID_HEADER]: correlationId
718
+ },
719
+ body: JSON.stringify({
720
+ types: input.requestedOperations,
721
+ scope
722
+ }),
723
+ signal: AbortSignal.timeout(input.dispatchTimeoutMs ?? 1e4)
724
+ });
725
+ if (response.status !== 202) throw new Error(`REM sidecar returned HTTP ${response.status}`);
726
+ const body = z.object({
727
+ job_id: z.string().min(1),
728
+ waveId: z.string().min(1)
729
+ }).passthrough().parse(await response.json());
730
+ if (body.job_id !== body.waveId) throw new Error("REM sidecar confirmation identities disagree");
731
+ withLogContext$1({
732
+ operation_id: body.job_id,
733
+ job_id: body.job_id,
734
+ session_reference: scope,
735
+ external_reference: correlationId
736
+ }, () => {
737
+ log$1.info("REM dispatch accepted", {
738
+ outcome: "success",
739
+ job_id: body.job_id,
740
+ status: response.status,
741
+ duration_ms: performance.now() - started
742
+ }, {
743
+ event_name: "sidecar.trigger.dispatch.accepted",
744
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
745
+ function: "dispatchRemWave",
746
+ site_id: "sidecar.trigger.dispatch.accepted"
747
+ });
748
+ });
749
+ }
750
+ async function applyCompletedBaselines(auditStateDir, triggerStateDir, state) {
751
+ let content;
752
+ try {
753
+ content = await readAuditTail(getAuditPath(auditStateDir));
754
+ } catch (error) {
755
+ if (isNodeError(error) && error.code === "ENOENT") return state;
756
+ log$1.warn("REM completion audit could not be read", {
757
+ error,
758
+ outcome: "skipped",
759
+ reason_code: "audit_read_failed"
760
+ }, {
761
+ event_name: "sidecar.trigger.audit_read.failed",
762
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
763
+ function: "applyCompletedBaselines",
764
+ site_id: "sidecar.trigger.audit_read.failed"
765
+ });
766
+ return state;
767
+ }
768
+ const dispatched = /* @__PURE__ */ new Map();
769
+ let nextState = state;
770
+ let malformedCount = 0;
771
+ for (const line of content.split("\n")) {
772
+ if (line.trim() === "") continue;
773
+ try {
774
+ const entry = JSON.parse(line);
775
+ const details = asRecord(entry["details"]);
776
+ const correlationId = details?.["correlation_id"];
777
+ const scope = entry["scope"];
778
+ if (entry["event"] === "rem_trigger_evaluated" && details?.["row"] === "dispatch" && typeof correlationId === "string" && typeof scope === "string") {
779
+ const trigger = details["trigger"];
780
+ const passAt = details["pass_at"];
781
+ const localDate = details["local_date"];
782
+ dispatched.set(correlationId, {
783
+ scope,
784
+ ...trigger === "daily" || trigger === "volume" ? { trigger } : {},
785
+ ...typeof passAt === "string" ? { passAt } : {},
786
+ ...typeof localDate === "string" ? { localDate } : {}
787
+ });
788
+ continue;
789
+ }
790
+ if (entry["event"] === "rem_skipped" && typeof correlationId === "string") {
791
+ const skipped = dispatched.get(correlationId);
792
+ if (skipped && nextState.scopes[skipped.scope]?.attempts.identity === correlationId) nextState = replaceScopeState(nextState, skipped.scope, {
793
+ ...requiredScopeState(nextState, skipped.scope),
794
+ attempts: {
795
+ identity: null,
796
+ count: 0
797
+ }
798
+ });
799
+ continue;
800
+ }
801
+ if (entry["event"] !== "rem_completed" || typeof correlationId !== "string") continue;
802
+ const dispatch = dispatched.get(correlationId);
803
+ if (dispatch === void 0 || state.scopes[dispatch.scope] === void 0 || entry["scope"] !== dispatch.scope) continue;
804
+ const currentScopeState = requiredScopeState(nextState, dispatch.scope);
805
+ let completedScopeState = {
806
+ ...currentScopeState,
807
+ ...dispatch.trigger === "daily" && isValidInstant(dispatch.passAt) ? { last_pass_at: dispatch.passAt } : {},
808
+ ...dispatch.trigger === "daily" && isLocalDate(dispatch.localDate) ? { last_volume_pass_date: dispatch.localDate } : {},
809
+ ...dispatch.trigger === "volume" && isLocalDate(dispatch.localDate) ? { last_volume_pass_date: dispatch.localDate } : {},
810
+ ...currentScopeState.attempts.identity === correlationId ? { attempts: {
811
+ identity: null,
812
+ count: 0
813
+ } } : {}
814
+ };
815
+ const measured = asRecord(asRecord(details?.["stats"])?.["measured"]);
816
+ const rowsConsidered = measured?.["rows_considered"];
817
+ if (measured?.["pair_cap_binding"] === false && Number.isInteger(rowsConsidered) && Number(rowsConsidered) >= 0) completedScopeState = {
818
+ ...completedScopeState,
819
+ last_covered_count: Number(rowsConsidered)
820
+ };
821
+ if (JSON.stringify(completedScopeState) === JSON.stringify(currentScopeState)) continue;
822
+ nextState = replaceScopeState(nextState, dispatch.scope, completedScopeState);
823
+ } catch {
824
+ malformedCount += 1;
825
+ }
826
+ }
827
+ if (malformedCount > 0) log$1.warn("Malformed REM audit records skipped", {
828
+ malformed_count: malformedCount,
829
+ outcome: "partial"
830
+ }, {
831
+ event_name: "sidecar.trigger.audit_records.skipped",
832
+ file: "packages/memory/src/sidecar/rem-trigger.ts",
833
+ function: "applyCompletedBaselines",
834
+ site_id: "sidecar.trigger.audit_records.skipped"
835
+ });
836
+ if (nextState !== state) await writeRemTriggerStateAtomic(triggerStateDir, nextState);
837
+ return nextState;
838
+ }
839
+ async function readAuditTail(auditPath) {
840
+ const handle = await open(auditPath, "r");
841
+ try {
842
+ const size = (await handle.stat()).size;
843
+ const bytesToRead = Math.min(size, REM_COMPLETION_AUDIT_TAIL_BYTES);
844
+ if (bytesToRead === 0) return "";
845
+ const buffer = Buffer.alloc(bytesToRead);
846
+ const { bytesRead } = await handle.read(buffer, 0, bytesToRead, size - bytesToRead);
847
+ let content = buffer.subarray(0, bytesRead).toString("utf8");
848
+ if (size > bytesToRead) {
849
+ const firstLineEnd = content.indexOf("\n");
850
+ content = firstLineEnd === -1 ? "" : content.slice(firstLineEnd + 1);
851
+ }
852
+ return content;
853
+ } finally {
854
+ await handle.close();
855
+ }
856
+ }
857
+ function remAutomaticCorrelationId(trigger, scope, triggerKey) {
858
+ return `rem-auto-${trigger}-${createHash("sha256").update(JSON.stringify([
859
+ "rem-automatic-v1",
860
+ trigger,
861
+ scope,
862
+ triggerKey
863
+ ])).digest("hex")}`;
864
+ }
865
+ /** The daily due time, pushed to the schedule after the last used-up local day. */
866
+ function nextDailyDue(scopeState) {
867
+ const scheduled = computeRemDailyDue(scopeState.last_pass_at, scopeState.schedule_zone);
868
+ const volumeDate = scopeState.last_volume_pass_date;
869
+ if (volumeDate === null) return scheduled;
870
+ const parsed = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(volumeDate);
871
+ if (parsed === null) return scheduled;
872
+ const following = addCalendarDays({
873
+ year: Number(parsed[1]),
874
+ month: Number(parsed[2]),
875
+ day: Number(parsed[3])
876
+ }, 1);
877
+ const afterVolume = zonedInstant(scopeState.schedule_zone, following.year, following.month, following.day);
878
+ return afterVolume.getTime() > scheduled.getTime() ? afterVolume : scheduled;
879
+ }
880
+ function computeRemDailyDue(lastPassAt, scheduleZone) {
881
+ const lastPass = new Date(lastPassAt);
882
+ if (Number.isNaN(lastPass.getTime())) throw new Error("REM last pass time is invalid");
883
+ const localDate = localDateParts(lastPass, scheduleZone);
884
+ let due = zonedInstant(scheduleZone, localDate.year, localDate.month, localDate.day);
885
+ if (due.getTime() <= lastPass.getTime()) {
886
+ const nextDate = addCalendarDays(localDate, 1);
887
+ due = zonedInstant(scheduleZone, nextDate.year, nextDate.month, nextDate.day);
888
+ }
889
+ return due;
890
+ }
891
+ function zonedInstant(zone, year, month, day) {
892
+ const target = Date.UTC(year, month - 1, day, 3, 0, 0, 0);
893
+ let instant = target;
894
+ let firstExistingAfter;
895
+ for (let iteration = 0; iteration < 5; iteration++) {
896
+ const parts = zonedDateTimeParts(new Date(instant), zone);
897
+ const represented = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
898
+ const adjustment = target - represented;
899
+ if (adjustment === 0) return new Date(instant);
900
+ if (represented > target && represented < (firstExistingAfter?.represented ?? Number.POSITIVE_INFINITY)) firstExistingAfter = {
901
+ instant,
902
+ represented
903
+ };
904
+ instant += adjustment;
905
+ }
906
+ if (firstExistingAfter !== void 0) return new Date(firstExistingAfter.instant);
907
+ throw new Error(`REM scheduled instant cannot be resolved in zone ${zone}`);
908
+ }
909
+ function localDateAt(instant, zone) {
910
+ const parts = localDateParts(instant, zone);
911
+ return `${parts.year.toString().padStart(4, "0")}-${parts.month.toString().padStart(2, "0")}-${parts.day.toString().padStart(2, "0")}`;
912
+ }
913
+ function localDateParts(instant, zone) {
914
+ const parts = zonedDateTimeParts(instant, zone);
915
+ return {
916
+ year: parts.year,
917
+ month: parts.month,
918
+ day: parts.day
919
+ };
920
+ }
921
+ function zonedDateTimeParts(instant, zone) {
922
+ const values = {};
923
+ const formatter = new Intl.DateTimeFormat("en-CA", {
924
+ timeZone: zone,
925
+ year: "numeric",
926
+ month: "2-digit",
927
+ day: "2-digit",
928
+ hour: "2-digit",
929
+ minute: "2-digit",
930
+ second: "2-digit",
931
+ hourCycle: "h23"
932
+ });
933
+ for (const part of formatter.formatToParts(instant)) if (part.type !== "literal") values[part.type] = Number(part.value);
934
+ const year = values["year"];
935
+ const month = values["month"];
936
+ const day = values["day"];
937
+ const hour = values["hour"];
938
+ const minute = values["minute"];
939
+ const second = values["second"];
940
+ if ([
941
+ year,
942
+ month,
943
+ day,
944
+ hour,
945
+ minute,
946
+ second
947
+ ].some((value) => value === void 0)) throw new Error(`REM schedule zone ${zone} did not produce complete date parts`);
948
+ return {
949
+ year: Number(year),
950
+ month: Number(month),
951
+ day: Number(day),
952
+ hour: Number(hour),
953
+ minute: Number(minute),
954
+ second: Number(second)
955
+ };
956
+ }
957
+ function addCalendarDays(date, days) {
958
+ const instant = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
959
+ return {
960
+ year: instant.getUTCFullYear(),
961
+ month: instant.getUTCMonth() + 1,
962
+ day: instant.getUTCDate()
963
+ };
964
+ }
965
+ function growthDetails(candidateCount, coveredCount, threshold) {
966
+ const growth = candidateCount - coveredCount;
967
+ return {
968
+ candidate_count: candidateCount,
969
+ last_covered_count: coveredCount,
970
+ delta: growth,
971
+ threshold,
972
+ remaining: Math.max(0, threshold - growth)
973
+ };
974
+ }
975
+ async function recordDecision(stateDir, scope, resultStatus, details) {
976
+ await appendAuditEntryStrict(stateDir, {
977
+ event: "rem_trigger_evaluated",
978
+ resultStatus,
979
+ ...scope === void 0 ? {} : { scope },
980
+ details
981
+ });
982
+ }
983
+ function requiredScopeState(state, scope) {
984
+ const scopeState = state.scopes[scope];
985
+ if (scopeState === void 0) throw new Error(`REM trigger state missing scope ${scope}`);
986
+ return scopeState;
987
+ }
988
+ function replaceScopeState(state, scope, scopeState) {
989
+ return {
990
+ version: 1,
991
+ scopes: {
992
+ ...state.scopes,
993
+ [scope]: scopeState
994
+ }
995
+ };
996
+ }
997
+ function asRecord(value) {
998
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
999
+ }
1000
+ function isValidInstant(value) {
1001
+ return value !== void 0 && !Number.isNaN(new Date(value).getTime());
1002
+ }
1003
+ function isLocalDate(value) {
1004
+ return value !== void 0 && /^\d{4}-\d{2}-\d{2}$/u.test(value);
1005
+ }
1006
+ function isNodeError(error) {
1007
+ return error instanceof Error && "code" in error;
1008
+ }
1009
+ function errorMessage(error) {
1010
+ return error instanceof Error ? error.message : String(error);
1011
+ }
1012
+ //#endregion
1013
+ //#region src/store/maintenance.ts
1014
+ /** @file maintenance.ts
1015
+ * @purpose Ordered maintenance pass: integrity check, outbox drain, usage-event retention, FTS
1016
+ * merge, planner statistics, backup, REM trigger check. Each job runs on its own interval.
1017
+ * @boundary Owns the gateway maintenance timer; integrity failures are logged without blocking storage.
1018
+ * @see backup.ts, memory-telemetry-outbox.ts, sqlite-runtime.ts.
1019
+ */
1020
+ const log = createLogger("sno-station-mem:maintenance");
1021
+ const DAY_MS = 864e5;
1022
+ /**
1023
+ * Usage events ('recall'/'inject') older than this are pruned from the
1024
+ * `nodix_memory_events` ledger. MUST stay >= the purge-safety recall lookback
1025
+ * (RECENT_RECALL_WINDOW_MS, 30 d) — coupled by a unit test — so retention can
1026
+ * never erase evidence the cascade-purge preview depends on. Lifecycle events
1027
+ * are never pruned (database-enforced by the guarded delete trigger).
1028
+ */
1029
+ const MEMORY_EVENTS_USAGE_RETENTION_MS = 90 * DAY_MS;
1030
+ /** Quarantined outbox rows older than this are deleted by the maintenance pass. */
1031
+ const OUTBOX_QUARANTINE_RETENTION_MS = 30 * DAY_MS;
1032
+ /** The first tick runs shortly after boot, so every job gets near-boot coverage. */
1033
+ const MAINTENANCE_FIRST_TICK_DELAY_MS = 6e4;
1034
+ /** Each job's own cadence; see the MAINTENANCE section of config/index.ts. */
1035
+ const MAINTENANCE_INTERVALS = {
1036
+ integrity: INTEGRITY_CHECK_INTERVAL_MS,
1037
+ "usage-outbox": USAGE_OUTBOX_INTERVAL_MS,
1038
+ "usage-retention": USAGE_EVENT_RETENTION_INTERVAL_MS,
1039
+ "fts-merge": FTS_MERGE_INTERVAL_MS,
1040
+ "planner-statistics": PLANNER_STATISTICS_INTERVAL_MS,
1041
+ backup: BACKUP_INTERVAL_MS,
1042
+ "rem-trigger": REM_TRIGGER_CHECK_INTERVAL_MS
1043
+ };
1044
+ const ALL_MAINTENANCE_JOBS = new Set(Object.keys(MAINTENANCE_INTERVALS));
1045
+ /** Every job on one interval — what the sidecar's maintenance-interval override asks for. */
1046
+ function uniformMaintenanceIntervals(intervalMs) {
1047
+ return Object.fromEntries([...ALL_MAINTENANCE_JOBS].map((job) => [job, intervalMs]));
1048
+ }
1049
+ const OUTBOX_DRAIN_BUDGET_MS = 3e4;
1050
+ const RETENTION_DELETE_BATCH = 5e3;
1051
+ const RETENTION_PRUNE_BUDGET_MS = 5e3;
1052
+ const FTS_MERGE_MAX_ITERATIONS = 10;
1053
+ function integrityMessages(rawDb, table) {
1054
+ const pragma = table ? `integrity_check('${table}')` : "integrity_check";
1055
+ return rawDb.pragma(pragma).map((row) => row["integrity_check"]).filter((value) => typeof value === "string");
1056
+ }
1057
+ function isCleanIntegrityResult(messages) {
1058
+ return messages.length === 1 && messages[0] === "ok";
1059
+ }
1060
+ function hashInventoryRows(hash, rawDb, table) {
1061
+ hash.update(`${table}\0`);
1062
+ const rows = rawDb.prepare(`SELECT rowid, * FROM ${table} ORDER BY rowid`).iterate();
1063
+ for (const row of rows) hash.update(JSON.stringify(row)).update("\0");
1064
+ }
1065
+ function sourceInventory(rawDb) {
1066
+ const hash = createHash("sha256");
1067
+ hashInventoryRows(hash, rawDb, "nodix_memories");
1068
+ hashInventoryRows(hash, rawDb, "nodix_memory_chunks");
1069
+ return hash.digest("hex");
1070
+ }
1071
+ function isDerivedFtsOnlyFailure(rawDb) {
1072
+ const fullMessages = integrityMessages(rawDb);
1073
+ if (isCleanIntegrityResult(fullMessages) || fullMessages.some((message) => !/fts5:.*nodix_memory_chunks_fts/i.test(message))) return false;
1074
+ if (!isCleanIntegrityResult(integrityMessages(rawDb, "nodix_memories"))) return false;
1075
+ if (!isCleanIntegrityResult(integrityMessages(rawDb, "nodix_memory_chunks"))) return false;
1076
+ try {
1077
+ rawDb.exec("INSERT INTO nodix_memory_chunks_fts(nodix_memory_chunks_fts, rank) VALUES('integrity-check', 1)");
1078
+ return false;
1079
+ } catch {
1080
+ return true;
1081
+ }
1082
+ }
1083
+ function recordIntegrityAudit(stateDir, decision, resultStatus, details) {
1084
+ appendAuditEntry(stateDir, {
1085
+ event: "storage_integrity",
1086
+ resultStatus,
1087
+ decision,
1088
+ details
1089
+ });
1090
+ }
1091
+ function tryRecoverDerivedFts(deps) {
1092
+ return deps.store.sqlite.runRecoveryOperation((rawDb) => recoverDerivedFts(deps, rawDb));
1093
+ }
1094
+ function recoverDerivedFts(deps, rawDb) {
1095
+ let inventoryBefore;
1096
+ try {
1097
+ if (!isDerivedFtsOnlyFailure(rawDb)) {
1098
+ recordIntegrityAudit(deps.stateDir, "retained", "error", { reason: "integrity failure is not provably confined to the derived FTS index" });
1099
+ return false;
1100
+ }
1101
+ inventoryBefore = sourceInventory(rawDb);
1102
+ } catch (error) {
1103
+ recordIntegrityAudit(deps.stateDir, "retained", "error", { reason: error instanceof Error ? error.message : String(error) });
1104
+ return false;
1105
+ }
1106
+ recordIntegrityAudit(deps.stateDir, "rebuild_started", "ok");
1107
+ log.warn("rebuilding derived FTS index after verified derived-only integrity failure", void 0, {
1108
+ event_name: "sno_station_mem.maintenance.rebuilding.derived.fts.index.after.verified.derived.only.integrity.fai",
1109
+ file: "packages/memory/src/store/maintenance.ts",
1110
+ function: "recoverDerivedFts",
1111
+ site_id: "maintenance.recoverDerivedFts.753697bd94"
1112
+ });
1113
+ try {
1114
+ rawDb.exec("INSERT INTO nodix_memory_chunks_fts(nodix_memory_chunks_fts) VALUES('rebuild')");
1115
+ if (sourceInventory(rawDb) !== inventoryBefore) throw new Error("source inventory changed during derived FTS rebuild");
1116
+ runIntegrityCheck(rawDb);
1117
+ rawDb.exec("INSERT INTO nodix_memory_chunks_fts(nodix_memory_chunks_fts, rank) VALUES('integrity-check', 1)");
1118
+ recordIntegrityAudit(deps.stateDir, "recovered", "ok");
1119
+ log.info("derived FTS index rebuilt and integrity reverified", void 0, {
1120
+ event_name: "sno_station_mem.maintenance.derived.fts.index.rebuilt.and.integrity.reverified",
1121
+ file: "packages/memory/src/store/maintenance.ts",
1122
+ function: "recoverDerivedFts",
1123
+ site_id: "maintenance.recoverDerivedFts.2cf5f62053"
1124
+ });
1125
+ return true;
1126
+ } catch (error) {
1127
+ recordIntegrityAudit(deps.stateDir, "retained", "error", { reason: error instanceof Error ? error.message : String(error) });
1128
+ log.error("derived FTS recovery failed; storage continues serving", { error }, {
1129
+ event_name: "memory.storage.fts.recovery.failed",
1130
+ file: "packages/memory/src/store/maintenance.ts",
1131
+ function: "recoverDerivedFts",
1132
+ site_id: "maintenance.recoverDerivedFts.c07ad948a2"
1133
+ });
1134
+ return false;
1135
+ }
1136
+ }
1137
+ /** Records an integrity failure without blocking subsequent SQL. */
1138
+ function recordIntegrityFailure(deps, error) {
1139
+ const message = error instanceof Error ? error.message : String(error);
1140
+ try {
1141
+ recordMemoryTelemetryIncident(deps.store.sqlite, {
1142
+ incidentType: "storage_integrity_check_failed",
1143
+ severity: "error",
1144
+ message: "database integrity check failed; storage continues serving",
1145
+ payload: {
1146
+ error_code: "integrity_check_failed",
1147
+ last_error: message.slice(0, 500)
1148
+ }
1149
+ });
1150
+ } catch {}
1151
+ log.error("storage continues serving after integrity failure", { error: message }, {
1152
+ event_name: "memory.storage.integrity.failed",
1153
+ file: "packages/memory/src/store/maintenance.ts",
1154
+ function: "recordIntegrityFailure",
1155
+ site_id: "maintenance.failClosed.dd7955e647"
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Bounded catch-up: a backlog left by a maintenance gap or a first-time
1160
+ * retention rollout can be far larger than one tick's budget. Deleting it
1161
+ * all synchronously would block the gateway event loop for minutes (host
1162
+ * adversarial review 2026-07-13). Leave the remainder for later ticks —
1163
+ * safe, since usage-event retention is a hygiene job, not a safety
1164
+ * invariant (the purge-safety recall lookback only needs the last 30 days,
1165
+ * well inside this 90-day window either way).
1166
+ */
1167
+ function pruneExpiredUsageEvents(store, now, budgetMs) {
1168
+ const cutoff = now - MEMORY_EVENTS_USAGE_RETENTION_MS;
1169
+ const statement = store.sqlite.prepare(`DELETE FROM nodix_memory_events
1170
+ WHERE id IN (
1171
+ SELECT id FROM nodix_memory_events
1172
+ WHERE event_type IN ('recall', 'inject') AND timestamp_ms < ?
1173
+ LIMIT ${RETENTION_DELETE_BATCH}
1174
+ )`);
1175
+ const deadline = Date.now() + budgetMs;
1176
+ let pruned = 0;
1177
+ do {
1178
+ const changes = statement.run(cutoff).changes ?? 0;
1179
+ pruned += changes;
1180
+ if (changes === 0) break;
1181
+ } while (Date.now() < deadline);
1182
+ return pruned;
1183
+ }
1184
+ function mergeFtsSegments(store) {
1185
+ if (!store.hasFtsSupport) return;
1186
+ const statement = store.sqlite.prepare("INSERT INTO nodix_memory_chunks_fts(nodix_memory_chunks_fts, rank) VALUES('merge', 500)");
1187
+ for (let i = 0; i < FTS_MERGE_MAX_ITERATIONS; i++) if ((statement.run().changes ?? 0) === 0) break;
1188
+ }
1189
+ /** Full integrity check and optional repair; reads and writes stay available. */
1190
+ function sweepIntegrity(deps, report) {
1191
+ const started = Date.now();
1192
+ try {
1193
+ const sweep = deps.integrityCheck ?? runIntegrityCheck;
1194
+ deps.store.sqlite.runRecoveryOperation((raw) => raw.transaction(() => {
1195
+ if (deps.store.hasFtsSupport) raw.prepare("SELECT rowid FROM nodix_memory_chunks_fts LIMIT 1").get();
1196
+ sweep(raw);
1197
+ }).deferred());
1198
+ } catch (error) {
1199
+ recordIntegrityFailure(deps, error);
1200
+ report.integrityRecovery = tryRecoverDerivedFts(deps) ? "recovered" : "retained";
1201
+ } finally {
1202
+ report.integrityMs = Date.now() - started;
1203
+ }
1204
+ }
1205
+ /** Runs the requested maintenance jobs independently. */
1206
+ function runMaintenancePass(deps, due = ALL_MAINTENANCE_JOBS, backupIntervalMs = BACKUP_INTERVAL_MS) {
1207
+ const report = {
1208
+ aborted: false,
1209
+ integrityRecovery: "none",
1210
+ integrityMs: 0,
1211
+ outboxFlushed: 0,
1212
+ quarantinePruned: 0,
1213
+ usageEventsPruned: 0,
1214
+ backupPath: void 0
1215
+ };
1216
+ if (deps.store.closed) {
1217
+ report.aborted = true;
1218
+ log.info("stopping maintenance for closed or replaced runtime", { dbPath: deps.dbPath }, {
1219
+ event_name: "sno_station_mem.maintenance.stopping.maintenance.for.closed.or.replaced.runtime",
1220
+ file: "packages/memory/src/store/maintenance.ts",
1221
+ function: "runMaintenancePass",
1222
+ site_id: "maintenance.runMaintenancePass.747b7cbef0"
1223
+ });
1224
+ return report;
1225
+ }
1226
+ deps.store.db.retrySetup();
1227
+ deps.store.hasFtsSupport = deps.store.sqlite.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'nodix_memory_chunks_fts' LIMIT 1").get() !== void 0;
1228
+ const now = Date.now();
1229
+ if (due.has("integrity")) sweepIntegrity(deps, report);
1230
+ if (deps.usageOutbox && due.has("usage-outbox")) try {
1231
+ report.outboxFlushed = deps.usageOutbox.drainPending(OUTBOX_DRAIN_BUDGET_MS).inserted;
1232
+ report.quarantinePruned = deps.usageOutbox.pruneQuarantined(now - OUTBOX_QUARANTINE_RETENTION_MS);
1233
+ } catch (error) {
1234
+ log.warn("outbox maintenance failed", { error }, {
1235
+ event_name: "sno_station_mem.maintenance.outbox.maintenance.failed",
1236
+ file: "packages/memory/src/store/maintenance.ts",
1237
+ function: "runMaintenancePass",
1238
+ site_id: "maintenance.runMaintenancePass.c72575daaa"
1239
+ });
1240
+ }
1241
+ if (due.has("usage-retention")) try {
1242
+ report.usageEventsPruned = pruneExpiredUsageEvents(deps.store, now, deps.retentionPruneBudgetMs ?? RETENTION_PRUNE_BUDGET_MS);
1243
+ } catch (error) {
1244
+ log.warn("usage-event retention failed", { error }, {
1245
+ event_name: "sno_station_mem.maintenance.usage.event.retention.failed",
1246
+ file: "packages/memory/src/store/maintenance.ts",
1247
+ function: "runMaintenancePass",
1248
+ site_id: "maintenance.runMaintenancePass.ef7a1e225c"
1249
+ });
1250
+ }
1251
+ if (due.has("fts-merge")) try {
1252
+ mergeFtsSegments(deps.store);
1253
+ } catch (error) {
1254
+ log.warn("fts merge failed", { error }, {
1255
+ event_name: "sno_station_mem.maintenance.fts.merge.failed",
1256
+ file: "packages/memory/src/store/maintenance.ts",
1257
+ function: "runMaintenancePass",
1258
+ site_id: "maintenance.runMaintenancePass.90f01cc543"
1259
+ });
1260
+ }
1261
+ if (due.has("planner-statistics")) try {
1262
+ deps.store.sqlite.exec("PRAGMA analysis_limit=400");
1263
+ deps.store.sqlite.exec("PRAGMA optimize");
1264
+ } catch (error) {
1265
+ log.warn("pragma optimize failed", { error }, {
1266
+ event_name: "sno_station_mem.maintenance.pragma.optimize.failed",
1267
+ file: "packages/memory/src/store/maintenance.ts",
1268
+ function: "runMaintenancePass",
1269
+ site_id: "maintenance.runMaintenancePass.3a7c9e73cf"
1270
+ });
1271
+ }
1272
+ if (due.has("backup")) try {
1273
+ if (isBackupDue(deps.backupDir, now, backupIntervalMs)) report.backupPath = runBackup(deps.dbPath, deps.backupDir);
1274
+ } catch (error) {
1275
+ log.warn("periodic backup failed", { error }, {
1276
+ event_name: "sno_station_mem.maintenance.periodic.backup.failed",
1277
+ file: "packages/memory/src/store/maintenance.ts",
1278
+ function: "runMaintenancePass",
1279
+ site_id: "maintenance.runMaintenancePass.11beb09895"
1280
+ });
1281
+ }
1282
+ log.info("maintenance pass complete", {
1283
+ integrityMs: report.integrityMs,
1284
+ outboxFlushed: report.outboxFlushed,
1285
+ quarantinePruned: report.quarantinePruned,
1286
+ usageEventsPruned: report.usageEventsPruned
1287
+ }, {
1288
+ event_name: "sno_station_mem.maintenance.maintenance.pass.complete",
1289
+ file: "packages/memory/src/store/maintenance.ts",
1290
+ function: "runMaintenancePass",
1291
+ site_id: "maintenance.runMaintenancePass.6a1e15dc18"
1292
+ });
1293
+ return report;
1294
+ }
1295
+ /**
1296
+ * Gateway maintenance schedule: first tick MAINTENANCE_FIRST_TICK_DELAY_MS after boot, then every
1297
+ * tickMs. Each tick runs the jobs whose own interval has elapsed since they last ran; the first
1298
+ * tick runs all of them. Stops when its store closes.
1299
+ */
1300
+ function startMaintenanceTimer(deps, tickMs = MAINTENANCE_TICK_MS, firstTickDelayMs = MAINTENANCE_FIRST_TICK_DELAY_MS, intervals = MAINTENANCE_INTERVALS) {
1301
+ let interval = null;
1302
+ let firstTick = null;
1303
+ let inFlight = false;
1304
+ let stopped = false;
1305
+ const lastRun = /* @__PURE__ */ new Map();
1306
+ const slackMs = tickMs / 2;
1307
+ const dueJobs = (now) => {
1308
+ const due = /* @__PURE__ */ new Set();
1309
+ for (const job of ALL_MAINTENANCE_JOBS) {
1310
+ const last = lastRun.get(job);
1311
+ if (job === "backup" || last === void 0 || now - last >= intervals[job] - slackMs) due.add(job);
1312
+ }
1313
+ return due;
1314
+ };
1315
+ const stop = () => {
1316
+ stopped = true;
1317
+ if (firstTick) {
1318
+ clearTimeout(firstTick);
1319
+ firstTick = null;
1320
+ }
1321
+ if (interval) {
1322
+ clearInterval(interval);
1323
+ interval = null;
1324
+ }
1325
+ };
1326
+ const tick = () => {
1327
+ if (inFlight) {
1328
+ log.warn("skipping maintenance tick: previous pass still in flight", void 0, {
1329
+ event_name: "sno_station_mem.maintenance.skipping.maintenance.tick.previous.pass.still.in.flight",
1330
+ file: "packages/memory/src/store/maintenance.ts",
1331
+ function: "tick",
1332
+ site_id: "maintenance.tick.e111b66415"
1333
+ });
1334
+ return;
1335
+ }
1336
+ if (deps.store.closed) {
1337
+ log.info("stopping maintenance for closed or replaced runtime", { dbPath: deps.dbPath }, {
1338
+ event_name: "sno_station_mem.maintenance.stopping.maintenance.for.closed.or.replaced.runtime",
1339
+ file: "packages/memory/src/store/maintenance.ts",
1340
+ function: "tick",
1341
+ site_id: "maintenance.tick.6bd3e0102c"
1342
+ });
1343
+ stop();
1344
+ return;
1345
+ }
1346
+ inFlight = true;
1347
+ const now = Date.now();
1348
+ const due = dueJobs(now);
1349
+ for (const job of due) lastRun.set(job, now);
1350
+ (async () => {
1351
+ try {
1352
+ if (runMaintenancePass(deps, due, intervals.backup - slackMs).aborted) {
1353
+ stop();
1354
+ return;
1355
+ }
1356
+ if (!due.has("rem-trigger")) return;
1357
+ await evaluateRemAutomaticTriggers({
1358
+ database: deps.store.sqlite,
1359
+ stateDir: deps.stateDir,
1360
+ auditStateDir: getSnoStationMemStateDir(),
1361
+ requestedOperations: deps.remSettings.requestedOperations,
1362
+ tickEnabled: deps.remSettings.tickEnabled,
1363
+ modelCalls: deps.modelCalls,
1364
+ mode: deps.mode,
1365
+ hasConnectedHost: deps.hasConnectedHost,
1366
+ now: deps.remClock,
1367
+ volumeThreshold: deps.remVolumeThreshold
1368
+ });
1369
+ } catch (error) {
1370
+ log.warn("maintenance pass threw", { error }, {
1371
+ event_name: "sno_station_mem.maintenance.maintenance.pass.threw",
1372
+ file: "packages/memory/src/store/maintenance.ts",
1373
+ function: "<anonymous callback>",
1374
+ site_id: "maintenance.<anonymous callback>.f90713a2e3"
1375
+ });
1376
+ } finally {
1377
+ inFlight = false;
1378
+ }
1379
+ })();
1380
+ };
1381
+ firstTick = setTimeout(() => {
1382
+ firstTick = null;
1383
+ tick();
1384
+ if (stopped) return;
1385
+ interval = setInterval(tick, tickMs);
1386
+ interval.unref?.();
1387
+ }, firstTickDelayMs);
1388
+ firstTick.unref?.();
1389
+ log.debug("maintenance timer started", {
1390
+ tickMs,
1391
+ intervals
1392
+ }, {
1393
+ event_name: "sno_station_mem.maintenance.maintenance.timer.started",
1394
+ file: "packages/memory/src/store/maintenance.ts",
1395
+ function: "startMaintenanceTimer",
1396
+ site_id: "maintenance.startMaintenanceTimer.06c1e98584"
1397
+ });
1398
+ return { stop };
1399
+ }
1400
+ //#endregion
1401
+ export { MAINTENANCE_FIRST_TICK_DELAY_MS, MAINTENANCE_INTERVALS, MEMORY_EVENTS_USAGE_RETENTION_MS, OUTBOX_QUARANTINE_RETENTION_MS, runMaintenancePass, startMaintenanceTimer, uniformMaintenanceIntervals };