@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,2551 @@
1
+ import { REM_ADDITIONAL_TRANSITION_PATTERN_SHA256, REM_AMBIGUOUS_PATTERN_SHA256, REM_CURRENT_PROGRESSIVE_PATTERN_SHA256, REM_HISTORICAL_FACT_PATTERN_SHA256, REM_PURE_NEGATION_PATTERN_SHA256, REM_TRANSITION_GRAMMAR_SHA256, classifyRemRow, runRestateClassifierEntry, runVerdictClassifierEntry } from "./classifier.js";
2
+ import { REM_BUILT_OPERATION_TYPES, REM_JOB_STATES, REM_OPERATION_TYPES, REM_ROW_STATES, assertJobIdentity, parseRemOperationType } from "./types.js";
3
+ import { z } from "zod";
4
+ import { createLogger } from "@snoai/utils/logger";
5
+ import path, { dirname, extname, resolve } from "node:path";
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { existsSync, lstatSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
8
+ //#region src/engine/rem/adapter-a-conflict-adjudicator.ts
9
+ /** @file adapter-a-conflict-adjudicator.ts
10
+ * @purpose Renders and parses a fail-safe conflict verdict for an ordered memory pair.
11
+ * @boundary Accepts package-level memory views only; callers adapt their storage records.
12
+ */
13
+ function adapterAViewFromRecord(record) {
14
+ if (record.kind !== "profile" && record.kind !== "episodic" && record.kind !== "state") throw new Error(`Adapter A does not accept ${record.kind} memories`);
15
+ return {
16
+ text: record.text,
17
+ ...record.subject !== void 0 ? { subject: record.subject } : {},
18
+ kind: record.kind,
19
+ validFrom: isoUtcSeconds(record.validFrom),
20
+ assertedAt: isoUtcSeconds(record.assertedAt),
21
+ contentHash: record.contentHash
22
+ };
23
+ }
24
+ function prospectiveProfileView(text, at) {
25
+ return {
26
+ text: text.trim(),
27
+ kind: "profile",
28
+ validFrom: isoUtcSeconds(at),
29
+ assertedAt: isoUtcSeconds(at),
30
+ contentHash: createHash("sha256").update(text.trim()).digest("hex")
31
+ };
32
+ }
33
+ function orderAdapterAPair(existing, candidate) {
34
+ if (compareViews(existing, candidate) <= 0) return {
35
+ older: existing,
36
+ newer: candidate,
37
+ candidateRole: "newer"
38
+ };
39
+ return {
40
+ older: candidate,
41
+ newer: existing,
42
+ candidateRole: "older"
43
+ };
44
+ }
45
+ function renderAdapterAPrompt(older, newer) {
46
+ return [
47
+ "Judge the relationship of the newer memory to the older memory.",
48
+ "",
49
+ "Older:",
50
+ `kind: ${older.kind}`,
51
+ `validFrom: ${older.validFrom}`,
52
+ `assertedAt: ${older.assertedAt}`,
53
+ `text: ${older.text}`,
54
+ "",
55
+ "Newer:",
56
+ `kind: ${newer.kind}`,
57
+ `validFrom: ${newer.validFrom}`,
58
+ `assertedAt: ${newer.assertedAt}`,
59
+ `text: ${newer.text}`,
60
+ "",
61
+ "Verdict:"
62
+ ].join("\n");
63
+ }
64
+ function renderAdapterAChatPrompt(pair) {
65
+ return [
66
+ "Adjudicate whether the newer memory replaces the older memory.",
67
+ "Return either one JSON object, {\"verdict\":\"replacement|keep|uncertain\"}, or exactly one verdict token.",
68
+ "replacement: the newer memory directly changes or corrects the same fact, state, or preference, so the older memory is no longer true.",
69
+ "keep: the older memory remains valid, is historical, concerns a different fact, coexists with the newer memory, or is only refined without being erased.",
70
+ "uncertain: the relationship is unclear or the evidence is insufficient. Prefer uncertain over guessing.",
71
+ "Apply the same semantics regardless of memory kind.",
72
+ `Older memory:\n${JSON.stringify(pair.older)}`,
73
+ `Newer memory:\n${JSON.stringify(pair.newer)}`
74
+ ].join("\n\n");
75
+ }
76
+ function parseAdapterAChatVerdict(raw) {
77
+ const trimmed = raw.trim();
78
+ if (trimmed === "replacement" || trimmed === "keep" || trimmed === "uncertain") return trimmed;
79
+ const json = extractJsonFromResponse(trimmed);
80
+ if (!json) return "uncertain";
81
+ try {
82
+ const parsed = JSON.parse(json);
83
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "uncertain";
84
+ const verdict = parsed["verdict"];
85
+ return verdict === "replacement" || verdict === "keep" || verdict === "uncertain" ? verdict : "uncertain";
86
+ } catch {
87
+ return "uncertain";
88
+ }
89
+ }
90
+ /**
91
+ * Whether a reply is a verdict at all: a bare verdict token, or a JSON object carrying one, read
92
+ * the way `parseAdapterAChatVerdict` reads it (a fenced object counts).
93
+ */
94
+ function isAdapterAVerdictReply(value) {
95
+ const trimmed = value.trim();
96
+ if (trimmed === "replacement" || trimmed === "keep" || trimmed === "uncertain") return true;
97
+ const json = extractJsonFromResponse(trimmed);
98
+ if (json === null) return false;
99
+ try {
100
+ const parsed = JSON.parse(json);
101
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
102
+ const verdict = parsed["verdict"];
103
+ return verdict === "replacement" || verdict === "keep" || verdict === "uncertain";
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+ function compareViews(a, b) {
109
+ const validFrom = Date.parse(a.validFrom) - Date.parse(b.validFrom);
110
+ if (validFrom !== 0) return validFrom;
111
+ return Date.parse(a.assertedAt) - Date.parse(b.assertedAt);
112
+ }
113
+ function isoUtcSeconds(value) {
114
+ if (!Number.isFinite(value)) throw new Error("Adapter A timestamp must be finite");
115
+ return new Date(value).toISOString().replace(/\.\d{3}Z$/, "Z");
116
+ }
117
+ function extractJsonFromResponse(text) {
118
+ const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
119
+ if (fenceMatch) return (fenceMatch[1] ?? "").trim();
120
+ const firstBrace = text.indexOf("{");
121
+ if (firstBrace === -1) return null;
122
+ let depth = 0;
123
+ let lastBrace = -1;
124
+ let inString = false;
125
+ let escaped = false;
126
+ for (let i = firstBrace; i < text.length; i++) {
127
+ const ch = text[i];
128
+ if (ch === void 0) continue;
129
+ if (escaped) {
130
+ escaped = false;
131
+ continue;
132
+ }
133
+ if (ch === "\\") {
134
+ escaped = inString;
135
+ continue;
136
+ }
137
+ if (ch === "\"") {
138
+ inString = !inString;
139
+ continue;
140
+ }
141
+ if (inString) continue;
142
+ if (ch === "{") depth++;
143
+ else if (ch === "}") {
144
+ depth--;
145
+ if (depth === 0) {
146
+ lastBrace = i;
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ if (lastBrace === -1) return null;
152
+ return text.substring(firstBrace, lastBrace + 1);
153
+ }
154
+ //#endregion
155
+ //#region src/engine/rem/operational-config.ts
156
+ const sha256Schema = z.string().regex(/^[0-9a-f]{64}$/u);
157
+ const positiveSafeInteger = z.number().int().safe().positive("value must be positive and non-zero");
158
+ const nullablePercentage = z.number().min(0).max(1).nullable();
159
+ const remOperationalConfigurationSchema = z.object({
160
+ profileId: z.string().min(1),
161
+ operations: z.object({
162
+ "rem-update": z.boolean(),
163
+ "rem-replace": z.boolean(),
164
+ "rem-distill": z.boolean(),
165
+ "rem-retire": z.boolean()
166
+ }).strict(),
167
+ budgets: z.object({ maxPairs: positiveSafeInteger }).strict(),
168
+ retrieval: z.object({
169
+ neighborLimit: positiveSafeInteger,
170
+ similarityThreshold: z.number().min(0).max(1)
171
+ }).strict(),
172
+ coverage: z.object({ accuracyFloor: nullablePercentage }).strict(),
173
+ retries: z.object({ liveContentionRetries: z.number().int().safe().nonnegative() }).strict(),
174
+ facetPolicy: z.object({
175
+ aggregationGrammar: z.string().min(1),
176
+ historyGrammar: z.string().min(1)
177
+ }).strict(),
178
+ calibration: z.object({
179
+ minimumPublishableScoreEffect: z.number().nonnegative().nullable(),
180
+ minimumTargetCount: positiveSafeInteger.nullable(),
181
+ minimumTargetPercent: nullablePercentage
182
+ }).strict(),
183
+ enableGateDigests: z.object({
184
+ "p5-production-config": sha256Schema,
185
+ "p6-monthly-non-regression": sha256Schema,
186
+ "p7-detector-gate-verdict": sha256Schema,
187
+ "population-routing": sha256Schema,
188
+ "rem-update": sha256Schema,
189
+ "rem-replace": sha256Schema,
190
+ "p5-immutable-profile": sha256Schema.optional()
191
+ }).strict()
192
+ }).strict();
193
+ function parseRemOperationalConfiguration(input) {
194
+ return remOperationalConfigurationSchema.parse(input);
195
+ }
196
+ function canonicalizeRemConfiguration(input) {
197
+ const { enableGateDigests: _excluded, ...operational } = parseRemOperationalConfiguration(input);
198
+ return canonicalJson(operational);
199
+ }
200
+ function deriveRemConfigurationSha256(input) {
201
+ return createHash("sha256").update(canonicalizeRemConfiguration(input), "utf8").digest("hex");
202
+ }
203
+ function validateRemEnableGateDigestKeys(input) {
204
+ if (parseRemOperationalConfiguration(input.configuration).enableGateDigests["p5-immutable-profile"] !== void 0 !== (input.p5ReadbackSource === "immutable-profile")) return {
205
+ decision: "refuse",
206
+ reasonCode: "enableGateDigests.p5-immutable-profile"
207
+ };
208
+ return {
209
+ decision: "allow",
210
+ reasonCode: null
211
+ };
212
+ }
213
+ function requireRemCoverageAccuracyFloor(input) {
214
+ return parseRemOperationalConfiguration(input.configuration).coverage.accuracyFloor === null ? {
215
+ decision: "refuse",
216
+ reasonCode: "coverage.accuracyFloor"
217
+ } : {
218
+ decision: "allow",
219
+ reasonCode: null
220
+ };
221
+ }
222
+ function requireRemOwnerDecisions(input) {
223
+ const configuration = parseRemOperationalConfiguration(input.configuration);
224
+ if (configuration.coverage.accuracyFloor === null) return {
225
+ decision: "refuse",
226
+ reasonCode: "coverage.accuracyFloor"
227
+ };
228
+ for (const key of [
229
+ "minimumPublishableScoreEffect",
230
+ "minimumTargetCount",
231
+ "minimumTargetPercent"
232
+ ]) if (configuration.calibration[key] === null) return {
233
+ decision: "refuse",
234
+ reasonCode: `calibration.${key}`
235
+ };
236
+ return {
237
+ decision: "allow",
238
+ reasonCode: null
239
+ };
240
+ }
241
+ function declareRemCalibrationThreshold(input) {
242
+ const configuration = parseRemOperationalConfiguration(input.configuration);
243
+ for (const key of [
244
+ "minimumPublishableScoreEffect",
245
+ "minimumTargetCount",
246
+ "minimumTargetPercent"
247
+ ]) if (configuration.calibration[key] === null) return {
248
+ decision: "refuse",
249
+ reasonCode: `calibration.${key}`
250
+ };
251
+ if (input.derivationStatement?.trim().length === 0 || input.derivationStatement === void 0) return {
252
+ decision: "refuse",
253
+ reasonCode: "calibration.derivationStatement"
254
+ };
255
+ return {
256
+ decision: "allow",
257
+ reasonCode: null
258
+ };
259
+ }
260
+ function validateRemGrammarChange(input) {
261
+ const before = parseRemOperationalConfiguration(input.before);
262
+ const after = parseRemOperationalConfiguration(input.after);
263
+ if (deriveRemConfigurationSha256(before) !== input.beforeConfigurationSha256 || deriveRemConfigurationSha256(after) !== input.afterConfigurationSha256 || !sha256Schema.safeParse(input.beforeCorpusSha256).success) return {
264
+ decision: "refuse",
265
+ reasonCode: "configuration_digest_mismatch"
266
+ };
267
+ if (!(before.facetPolicy.aggregationGrammar !== after.facetPolicy.aggregationGrammar || before.facetPolicy.historyGrammar !== after.facetPolicy.historyGrammar)) return {
268
+ decision: "allow",
269
+ reasonCode: null
270
+ };
271
+ const artifact = input.abArtifact;
272
+ if (artifact === void 0 || artifact.beforeConfigurationSha256 !== input.beforeConfigurationSha256 || artifact.afterConfigurationSha256 !== input.afterConfigurationSha256 || artifact.corpusSha256 !== input.beforeCorpusSha256 || artifact.metricDefinitions.length === 0 || artifact.result !== "pass") return {
273
+ decision: "refuse",
274
+ reasonCode: "grammar_ab_missing_or_failed"
275
+ };
276
+ return {
277
+ decision: "allow",
278
+ reasonCode: null
279
+ };
280
+ }
281
+ function canonicalJson(value) {
282
+ if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
283
+ if (typeof value === "number") {
284
+ if (!Number.isFinite(value)) throw new Error("canonical JSON does not accept non-finite numbers");
285
+ return JSON.stringify(value);
286
+ }
287
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
288
+ if (typeof value !== "object") throw new Error("canonical JSON accepts JSON values only");
289
+ const record = value;
290
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
291
+ }
292
+ //#endregion
293
+ //#region src/engine/rem/census.ts
294
+ function sha256(value) {
295
+ return createHash("sha256").update(value).digest("hex");
296
+ }
297
+ function validateRemProspectiveIdentity(input) {
298
+ if (input.writeIdentitySha256.includes(":")) return {
299
+ decision: "refuse",
300
+ reasonCode: "scan_generation_identity"
301
+ };
302
+ if (!isSha256(input.writeIdentitySha256) || !isSha256(input.candidateSetSha256)) return {
303
+ decision: "refuse",
304
+ reasonCode: "identity_invalid"
305
+ };
306
+ return {
307
+ decision: "allow",
308
+ reasonCode: null
309
+ };
310
+ }
311
+ function deriveRemWriteIdentity(input) {
312
+ return sha256(JSON.stringify([
313
+ input.rowId,
314
+ input.text,
315
+ input.contentHash,
316
+ input.timestamp
317
+ ]));
318
+ }
319
+ function deriveRemReplayArtifactIdentity(input) {
320
+ const corpusSha256 = sha256(readFileSync(input.corpusPath));
321
+ const embedderSha256 = sha256(readFileSync(input.embedderPath));
322
+ const toolSha256 = sha256(readFileSync(input.toolPath));
323
+ const configurationSha256 = sha256(canonicalizeRemConfiguration(input.configuration));
324
+ return {
325
+ corpusSha256,
326
+ embedderSha256,
327
+ toolSha256,
328
+ configurationSha256,
329
+ replayIdentitySha256: sha256(JSON.stringify([
330
+ corpusSha256,
331
+ embedderSha256,
332
+ toolSha256,
333
+ configurationSha256
334
+ ]))
335
+ };
336
+ }
337
+ function validateRemReplayIdentityTuple(input) {
338
+ return Object.values(input).every(isSha256) ? {
339
+ decision: "allow",
340
+ reasonCode: null
341
+ } : {
342
+ decision: "refuse",
343
+ reasonCode: "replay_identity_invalid"
344
+ };
345
+ }
346
+ function classifyRemCensusLabel(input) {
347
+ const observations = input.observations;
348
+ if (input.label === "reachability") {
349
+ if (observations["similarityMet"] !== true) return "unknown";
350
+ return observations["neighborEmitted"] === true ? "reachable" : "unreachable";
351
+ }
352
+ if (input.label === "contradiction") {
353
+ if (observations["sameFactKey"] !== true) return "undetermined";
354
+ return observations["valuesConflict"] === true ? "yes" : "no";
355
+ }
356
+ if (input.label === "coverageEligibility") {
357
+ if (observations["retrievabilityComputed"] !== true) return "undetermined";
358
+ return observations["referenceSetNonEmpty"] === true && observations["retiringAtomsMatched"] === true ? "eligible" : "ineligible";
359
+ }
360
+ throw new Error(`unknown REM census label: ${input.label}`);
361
+ }
362
+ function classifyAllRemCensusLabels(input) {
363
+ return {
364
+ reachability: classifyRemCensusLabel({
365
+ label: "reachability",
366
+ observations: input.reachability
367
+ }),
368
+ contradiction: classifyRemCensusLabel({
369
+ label: "contradiction",
370
+ observations: input.contradiction
371
+ }),
372
+ coverageEligibility: classifyRemCensusLabel({
373
+ label: "coverageEligibility",
374
+ observations: input.coverageEligibility
375
+ })
376
+ };
377
+ }
378
+ function validateRemCensusOracleProvenance(input) {
379
+ return input.authoredBy === "product-classifier" ? {
380
+ decision: "refuse",
381
+ reasonCode: "self_authored_oracle"
382
+ } : {
383
+ decision: "allow",
384
+ reasonCode: null
385
+ };
386
+ }
387
+ function validateRemCensusLabelTuple(input) {
388
+ return [
389
+ "reachable",
390
+ "unreachable",
391
+ "unknown"
392
+ ].includes(input.reachability) && [
393
+ "yes",
394
+ "no",
395
+ "undetermined"
396
+ ].includes(input.contradiction) && [
397
+ "eligible",
398
+ "ineligible",
399
+ "undetermined"
400
+ ].includes(input.coverageEligibility) ? {
401
+ decision: "allow",
402
+ reasonCode: null
403
+ } : {
404
+ decision: "refuse",
405
+ reasonCode: "label_enum_mismatch"
406
+ };
407
+ }
408
+ function readRetainedRemCensusArtifact(input) {
409
+ if (!existsSync(input.artifactPath)) return {
410
+ decision: "refuse",
411
+ reasonCode: "artifact_missing"
412
+ };
413
+ const bytes = readFileSync(input.artifactPath);
414
+ try {
415
+ JSON.parse(bytes.toString("utf8"));
416
+ } catch {
417
+ return {
418
+ decision: "refuse",
419
+ reasonCode: "artifact_invalid"
420
+ };
421
+ }
422
+ return {
423
+ decision: "allow",
424
+ reasonCode: null,
425
+ artifactSha256: sha256(bytes)
426
+ };
427
+ }
428
+ function produceRemCensusArtifact(input) {
429
+ if (input.producer !== "capture" && input.producer !== "replay") throw new Error("REM census producer must be capture or replay");
430
+ writeFileSync(input.outputPath, `${JSON.stringify({ route: input.producer })}\n`);
431
+ }
432
+ function validateRemCensusRouteCoverage(input) {
433
+ const routes = new Set(input.map(({ route }) => route));
434
+ return routes.has("capture") && routes.has("replay") ? {
435
+ decision: "allow",
436
+ reasonCode: null
437
+ } : {
438
+ decision: "refuse",
439
+ reasonCode: "route_coverage_incomplete"
440
+ };
441
+ }
442
+ function journalRemGenerationTransition(input) {
443
+ if (input.outgoingGenerationId === input.incomingGenerationId) return {
444
+ decision: "refuse",
445
+ reasonCode: "generation_identity_reused"
446
+ };
447
+ input.database.prepare(`INSERT INTO nodix_rem_generation_transitions(
448
+ outgoing_generation_id, incoming_generation_id, recorded_at
449
+ ) VALUES (?, ?, ?)`).run(input.outgoingGenerationId, input.incomingGenerationId, (/* @__PURE__ */ new Date()).toISOString());
450
+ return {
451
+ decision: "allow",
452
+ reasonCode: null
453
+ };
454
+ }
455
+ function summarizeRemBatchEvents(input) {
456
+ const distribution = {};
457
+ let calls = 0;
458
+ let tokens = 0;
459
+ let closeCount = 0;
460
+ let refusalCount = 0;
461
+ for (const event of input.events) {
462
+ calls += event.calls ?? 0;
463
+ tokens += event.tokens ?? 0;
464
+ closeCount += event.close ?? 0;
465
+ if (event.refusal !== void 0 && event.refusal !== null) refusalCount += 1;
466
+ if (event.verdict !== void 0) distribution[event.verdict] = (distribution[event.verdict] ?? 0) + 1;
467
+ }
468
+ input.database.prepare(`INSERT INTO nodix_rem_batch_summaries(
469
+ batch_id, generation_id, calls, tokens, pairCount, closeCount,
470
+ refusalCount, verdict_distribution_json
471
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.batchId, input.generationId ?? null, calls, tokens, input.events.length, closeCount, refusalCount, JSON.stringify(distribution));
472
+ }
473
+ function recordRemObservedVerdict(input) {
474
+ const isVerdict = (/* @__PURE__ */ new Set([
475
+ "replacement",
476
+ "keep",
477
+ "merge",
478
+ "unrelated"
479
+ ])).has(input.observed);
480
+ input.database.prepare(`INSERT INTO nodix_rem_verdict_observations(
481
+ pair_id, audit_kind, persisted_verdict, raw_value
482
+ ) VALUES (?, ?, ?, ?)`).run(input.pairId, isVerdict ? "VERDICT" : "SAMPLE", isVerdict ? input.observed : null, input.observed);
483
+ }
484
+ function recordRemCandidateCapCounts(input) {
485
+ input.database.prepare(`INSERT INTO nodix_rem_batch_summaries(
486
+ batch_id, generation_id, pre_truncation_count, emitted_count
487
+ ) VALUES (?, ?, ?, ?)`).run(`candidate-cap:${input.generationId}`, input.generationId, input.total, Math.min(input.total, input.cap));
488
+ }
489
+ function reportRemSimilarityCalibration(input) {
490
+ const bytes = readFileSync(input.retainedArtifactPath);
491
+ JSON.parse(bytes.toString("utf8"));
492
+ const emitted = new Set(input.emittedPairIds);
493
+ const targets = new Set(input.targetPairIds);
494
+ const emittedTargetCount = [...targets].filter((pairId) => emitted.has(pairId)).length;
495
+ return {
496
+ emittedFraction: targets.size === 0 ? 0 : emittedTargetCount / targets.size,
497
+ targetRanks: input.targetPairIds.map((pairId) => input.rankedPairIds.indexOf(pairId) + 1),
498
+ adjudicationReachability: input.targetPairIds.map((pairId) => emitted.has(pairId)),
499
+ modelCallBudget: input.modelCallBudget,
500
+ consumedArtifactSha256: sha256(bytes)
501
+ };
502
+ }
503
+ function validateRemCalibrationCrossCheck(input) {
504
+ return input.independent === null || input.independent === void 0 ? {
505
+ decision: "refuse",
506
+ reasonCode: "independent_count_missing"
507
+ } : {
508
+ decision: "allow",
509
+ reasonCode: null
510
+ };
511
+ }
512
+ function isSha256(value) {
513
+ return /^[0-9a-f]{64}$/u.test(value);
514
+ }
515
+ //#endregion
516
+ //#region src/engine/rem/clause-splitter.ts
517
+ /**
518
+ * Produces the fixed, lossless clause list used by REM judgments. The text itself
519
+ * remains the authority: clause values are exact source slices and separators are retained.
520
+ */
521
+ function splitExactClauses(text) {
522
+ if (text.trim().length === 0) throw new Error("REM clause splitter requires non-empty text");
523
+ const clauses = [];
524
+ let cursor = 0;
525
+ for (const boundary of text.matchAll(/\n+|(?<=[.!?;])\s+|(?<=,)\s+(?=\p{Ll})/gu)) {
526
+ const end = boundary.index;
527
+ clauses.push({
528
+ value: text.slice(cursor, end),
529
+ start: cursor,
530
+ end,
531
+ separatorAfter: boundary[0]
532
+ });
533
+ cursor = end + boundary[0].length;
534
+ }
535
+ clauses.push({
536
+ value: text.slice(cursor),
537
+ start: cursor,
538
+ end: text.length,
539
+ separatorAfter: ""
540
+ });
541
+ if (clauses.some((clause) => clause.value.trim().length === 0)) throw new Error("REM clause splitter cannot delimit an empty clause");
542
+ if (clauses.map((clause) => `${clause.value}${clause.separatorAfter}`).join("") !== text) throw new Error("REM clause splitter must preserve source bytes");
543
+ return clauses;
544
+ }
545
+ //#endregion
546
+ //#region src/engine/rem/update-locale-resources.ts
547
+ const REM_UPDATE_LOCALES = [
548
+ "en",
549
+ "de",
550
+ "es",
551
+ "fr",
552
+ "zh",
553
+ "zh-Hant",
554
+ "ja",
555
+ "ko",
556
+ "ru"
557
+ ];
558
+ const REM_UPDATE_LOCALE_RESOURCES = {
559
+ en: {
560
+ valuePrefix: "Current preference: ",
561
+ listPrefix: "Current todos: ",
562
+ listSeparator: ", ",
563
+ spanSeparator: " ",
564
+ anchorMode: "capitalized-sequence"
565
+ },
566
+ de: {
567
+ valuePrefix: "Aktuelle Präferenz: ",
568
+ listPrefix: "Aktuelle Aufgaben: ",
569
+ listSeparator: ", ",
570
+ spanSeparator: " ",
571
+ anchorMode: "capitalized-sequence"
572
+ },
573
+ es: {
574
+ valuePrefix: "Preferencia actual: ",
575
+ listPrefix: "Tareas actuales: ",
576
+ listSeparator: ", ",
577
+ spanSeparator: " ",
578
+ anchorMode: "capitalized-sequence"
579
+ },
580
+ fr: {
581
+ valuePrefix: "Préférence actuelle : ",
582
+ listPrefix: "Tâches actuelles : ",
583
+ listSeparator: ", ",
584
+ spanSeparator: " ",
585
+ anchorMode: "capitalized-sequence"
586
+ },
587
+ zh: {
588
+ valuePrefix: "当前偏好:",
589
+ listPrefix: "当前任务:",
590
+ listSeparator: "、",
591
+ spanSeparator: "",
592
+ anchorMode: "explicit-script"
593
+ },
594
+ "zh-Hant": {
595
+ valuePrefix: "目前偏好:",
596
+ listPrefix: "目前任務:",
597
+ listSeparator: "、",
598
+ spanSeparator: "",
599
+ anchorMode: "explicit-script"
600
+ },
601
+ ja: {
602
+ valuePrefix: "現在の好み:",
603
+ listPrefix: "現在のタスク:",
604
+ listSeparator: "、",
605
+ spanSeparator: "",
606
+ anchorMode: "explicit-script"
607
+ },
608
+ ko: {
609
+ valuePrefix: "현재 선호: ",
610
+ listPrefix: "현재 작업: ",
611
+ listSeparator: ", ",
612
+ spanSeparator: " ",
613
+ anchorMode: "explicit-script"
614
+ },
615
+ ru: {
616
+ valuePrefix: "Текущее предпочтение: ",
617
+ listPrefix: "Текущие задачи: ",
618
+ listSeparator: ", ",
619
+ spanSeparator: " ",
620
+ anchorMode: "capitalized-sequence"
621
+ }
622
+ };
623
+ function getRemUpdateLocaleResource(locale) {
624
+ return REM_UPDATE_LOCALE_RESOURCES[locale];
625
+ }
626
+ //#endregion
627
+ //#region src/engine/rem/update-rewrite.ts
628
+ function composeRemNegatedCurrent(input) {
629
+ const generatedRowId = `rem-negated-${createHash("sha256").update(`${input.topic}\u0000${input.retractionText}`).digest("hex").slice(0, 24)}`;
630
+ return {
631
+ shape: "negated-current",
632
+ topic: input.topic,
633
+ assertions: [{
634
+ polarity: "negative",
635
+ provenance: input.retractionText
636
+ }],
637
+ generatedRowId,
638
+ consumedRowIds: [input.priorRowId]
639
+ };
640
+ }
641
+ function enumerateRemUpdateMembers(source) {
642
+ return extractListMemberSpans(source).map((span) => source.slice(span.start, span.end));
643
+ }
644
+ function buildRemUpdateReferenceSet(input) {
645
+ if (input.tier === "list") return buildMemberUnits(input.source, input.enumeratedMembers ?? []);
646
+ const clauses = splitExactClauses(input.source).map((clause, index) => toCoverageReference(input.source, `clause:${index}`, "clause", clause));
647
+ const anchors = extractAnchors(input.source, input.locale).map((anchor, index) => toCoverageReference(input.source, `anchor:${index}`, "anchor", anchor));
648
+ return [...clauses, ...anchors];
649
+ }
650
+ function buildMemberUnits(source, members) {
651
+ return members.map((value, index) => {
652
+ const start = source.indexOf(value);
653
+ return toCoverageReference(source, `member:${index}`, "structured-member", {
654
+ value,
655
+ start,
656
+ end: start < 0 ? -1 : start + value.length
657
+ });
658
+ });
659
+ }
660
+ function toCoverageReference(source, id, kind, span) {
661
+ const text = source.slice(span.start, span.end);
662
+ return {
663
+ id,
664
+ atomId: `atom:${id}`,
665
+ kind,
666
+ value: span.value,
667
+ text,
668
+ sourceTextSha256: createHash("sha256").update(source).digest("hex"),
669
+ start: span.start,
670
+ end: span.end,
671
+ startByte: Buffer.byteLength(source.slice(0, span.start)),
672
+ endByte: Buffer.byteLength(source.slice(0, span.end))
673
+ };
674
+ }
675
+ function extractListMemberSpans(source) {
676
+ return [...source.matchAll(/^[ \t]*[-*•][ \t]+(.+)$/gmu)].flatMap((match) => {
677
+ const member = match[1];
678
+ if (member === void 0 || match.index === void 0) return [];
679
+ const start = match.index + match[0].indexOf(member);
680
+ return [{
681
+ start,
682
+ end: start + member.length
683
+ }];
684
+ });
685
+ }
686
+ function extractAnchors(source, locale) {
687
+ const anchors = [...matches(source, /["“‘][^"”’]+["”’]/gu), ...matches(source, /\p{Sc}?\d[\d.,:/-]*/gu)];
688
+ if (getRemUpdateLocaleResource(locale).anchorMode === "capitalized-sequence") anchors.push(...matches(source, /\b\p{Lu}[\p{L}\p{M}]*(?:\s+\p{Lu}[\p{L}\p{M}]*)+\b/gu));
689
+ return uniqueSpans(anchors);
690
+ }
691
+ function matches(source, pattern) {
692
+ return [...source.matchAll(pattern)].flatMap((match) => {
693
+ if (match.index === void 0 || match[0].length === 0) return [];
694
+ return [{
695
+ value: match[0],
696
+ start: match.index,
697
+ end: match.index + match[0].length
698
+ }];
699
+ });
700
+ }
701
+ function uniqueSpans(spans) {
702
+ const seen = /* @__PURE__ */ new Set();
703
+ return spans.filter((span) => {
704
+ const key = `${span.start}:${span.end}`;
705
+ if (seen.has(key)) return false;
706
+ seen.add(key);
707
+ return true;
708
+ }).sort((left, right) => left.start - right.start || left.end - right.end);
709
+ }
710
+ //#endregion
711
+ //#region src/engine/rem/coverage-contract.ts
712
+ function buildRemCoverageAtoms(input) {
713
+ if (input.source.trim().length === 0) return {
714
+ outcome: "refuse",
715
+ reason: "empty_reference_set"
716
+ };
717
+ if (input.format === "flattened-clauses") return {
718
+ outcome: "refuse",
719
+ reason: "unsegmentable_reference_set"
720
+ };
721
+ if (input.format === "prose") {
722
+ const atoms = buildRemUpdateReferenceSet({
723
+ source: input.source,
724
+ tier: "prose",
725
+ locale: "en"
726
+ });
727
+ return atoms.length === 0 ? {
728
+ outcome: "refuse",
729
+ reason: "empty_reference_set"
730
+ } : {
731
+ outcome: "accept",
732
+ atoms: atoms.map(selectCoverageFields)
733
+ };
734
+ }
735
+ return buildStructuredAtoms(input.source);
736
+ }
737
+ async function validateRemClauseCoverageArtifacts(input) {
738
+ const arms = {
739
+ goldSet: input.expectedGoldSetSha256 === input.measuredGoldSetSha256 ? "pass" : "fail",
740
+ confidence: input.confidenceSatisfied ? "pass" : "fail",
741
+ chronology: input.declarationSequence < input.firstScoredSequence ? "pass" : "fail",
742
+ measurement: input.measurementPresent ? "pass" : "fail",
743
+ artifactDigest: input.artifactDigestMatches ? "pass" : "fail",
744
+ accuracyFloor: input.accuracyFloor === null ? "owner-unset" : input.measuredAccuracy !== null && input.measuredAccuracy >= input.accuracyFloor ? "pass" : "fail"
745
+ };
746
+ if (input.accuracyFloor === null) return {
747
+ decision: "refuse",
748
+ reason: "coverage.accuracyFloor",
749
+ arms
750
+ };
751
+ if (input.measuredAccuracy === null) return {
752
+ decision: "refuse",
753
+ reason: "coverage.measurementMissing",
754
+ arms
755
+ };
756
+ for (const [arm, status] of Object.entries(arms)) if (status === "fail") return {
757
+ decision: "refuse",
758
+ reason: `coverage.${arm}`,
759
+ arms
760
+ };
761
+ return {
762
+ decision: "allow",
763
+ reason: null,
764
+ arms
765
+ };
766
+ }
767
+ function buildStructuredAtoms(source) {
768
+ let decoded;
769
+ try {
770
+ decoded = JSON.parse(source);
771
+ } catch {
772
+ return {
773
+ outcome: "refuse",
774
+ reason: "unsegmentable_reference_set"
775
+ };
776
+ }
777
+ if (decoded === null || typeof decoded !== "object") return {
778
+ outcome: "refuse",
779
+ reason: "unsegmentable_reference_set"
780
+ };
781
+ const descriptors = [];
782
+ collectStructuredLeaves(decoded, "", void 0, descriptors);
783
+ let cursor = 0;
784
+ const digest = createHash("sha256").update(source).digest("hex");
785
+ const atoms = descriptors.flatMap((descriptor) => {
786
+ let start = source.indexOf(descriptor.needle, cursor);
787
+ if (start < 0) start = source.indexOf(descriptor.needle);
788
+ if (start < 0) return [];
789
+ const end = start + descriptor.needle.length;
790
+ cursor = end;
791
+ return [{
792
+ atomId: `atom:${descriptor.id}`,
793
+ kind: descriptor.kind,
794
+ sourceTextSha256: digest,
795
+ startByte: Buffer.byteLength(source.slice(0, start)),
796
+ endByte: Buffer.byteLength(source.slice(0, end)),
797
+ text: source.slice(start, end)
798
+ }];
799
+ });
800
+ return atoms.length === descriptors.length && atoms.length > 0 ? {
801
+ outcome: "accept",
802
+ atoms
803
+ } : {
804
+ outcome: "refuse",
805
+ reason: "unsegmentable_reference_set"
806
+ };
807
+ }
808
+ function collectStructuredLeaves(value, pointer, objectKey, output) {
809
+ if (Array.isArray(value)) {
810
+ value.forEach((member, index) => {
811
+ const memberPointer = `${pointer}/${index}`;
812
+ if (member !== null && typeof member === "object") collectStructuredLeaves(member, memberPointer, void 0, output);
813
+ else output.push({
814
+ id: memberPointer,
815
+ kind: "structured-member",
816
+ needle: JSON.stringify(member)
817
+ });
818
+ });
819
+ return;
820
+ }
821
+ if (value !== null && typeof value === "object") {
822
+ for (const [key, field] of Object.entries(value)) collectStructuredLeaves(field, `${pointer}/${escapeJsonPointer(key)}`, key, output);
823
+ return;
824
+ }
825
+ if (objectKey !== void 0) output.push({
826
+ id: pointer,
827
+ kind: "structured-field",
828
+ needle: `${JSON.stringify(objectKey)}:${JSON.stringify(value)}`
829
+ });
830
+ }
831
+ function selectCoverageFields(unit) {
832
+ return {
833
+ atomId: unit.atomId,
834
+ kind: unit.kind,
835
+ sourceTextSha256: unit.sourceTextSha256,
836
+ startByte: unit.startByte,
837
+ endByte: unit.endByte,
838
+ text: unit.text
839
+ };
840
+ }
841
+ function escapeJsonPointer(value) {
842
+ return value.replace(/~/gu, "~0").replace(/\//gu, "~1");
843
+ }
844
+ //#endregion
845
+ //#region src/engine/rem/enable-gate.ts
846
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
847
+ const artifactSchema = z.object({
848
+ schema_version: z.number().int(),
849
+ job_type: z.enum(REM_BUILT_OPERATION_TYPES),
850
+ implementation_version: z.string().min(1),
851
+ corpus_sha256: z.string().regex(SHA256_PATTERN),
852
+ baseline_sha256: z.string().regex(SHA256_PATTERN),
853
+ result: z.enum(["pass", "fail"]),
854
+ evaluated_at: z.string().datetime(),
855
+ expires_at: z.string().datetime()
856
+ }).strict();
857
+ var RemEnableGateError = class extends Error {
858
+ code;
859
+ constructor(code) {
860
+ super(code);
861
+ this.code = code;
862
+ this.name = "RemEnableGateError";
863
+ }
864
+ };
865
+ const REM_ENABLE_GATE_BINDINGS = {
866
+ implementationVersion: "edge-rem-wave-20260806",
867
+ corpusSha256: "67d1acbe4451cccc0199a9b670b2b8df17b79ba7c3d6d431b4e0e303d73869e2",
868
+ baselineSha256: "87413c08883fddc81d479fe6dcb9d7a537ee156d2fb73716ab0603860caa64c1"
869
+ };
870
+ function loadRemEnableGate(input) {
871
+ const artifactPath = path.join(input.stateDir, "rem-gates", `${input.jobType}.json`);
872
+ let stat;
873
+ try {
874
+ stat = lstatSync(artifactPath);
875
+ } catch {
876
+ throw new RemEnableGateError("artifact_missing");
877
+ }
878
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new RemEnableGateError("artifact_not_regular");
879
+ if ((stat.mode & 18) !== 0) throw new RemEnableGateError("artifact_permissions");
880
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new RemEnableGateError("artifact_owner");
881
+ const bytes = readFileSync(artifactPath);
882
+ if (createHash("sha256").update(bytes).digest("hex") !== input.artifactSha256) throw new RemEnableGateError("artifact_digest_mismatch");
883
+ const artifact = parseArtifact(bytes);
884
+ assertArtifactBindings(artifact, input);
885
+ return artifact;
886
+ }
887
+ function parseArtifact(bytes) {
888
+ let decoded;
889
+ try {
890
+ decoded = JSON.parse(bytes.toString("utf8"));
891
+ } catch {
892
+ throw new RemEnableGateError("artifact_schema");
893
+ }
894
+ const parsed = artifactSchema.safeParse(decoded);
895
+ if (!parsed.success) throw new RemEnableGateError("artifact_schema");
896
+ return parsed.data;
897
+ }
898
+ function assertArtifactBindings(artifact, input) {
899
+ if (artifact.schema_version !== 1) throw new RemEnableGateError("schema_version_mismatch");
900
+ if (artifact.job_type !== input.jobType) throw new RemEnableGateError("job_type_mismatch");
901
+ if (artifact.implementation_version !== input.implementationVersion) throw new RemEnableGateError("implementation_version_mismatch");
902
+ if (artifact.corpus_sha256 !== input.corpusSha256) throw new RemEnableGateError("corpus_mismatch");
903
+ if (artifact.baseline_sha256 !== input.baselineSha256) throw new RemEnableGateError("baseline_mismatch");
904
+ if (artifact.result !== "pass") throw new RemEnableGateError("result_not_pass");
905
+ const now = Date.parse(input.now);
906
+ const evaluatedAt = Date.parse(artifact.evaluated_at);
907
+ const expiresAt = Date.parse(artifact.expires_at);
908
+ if (!Number.isFinite(now) || evaluatedAt > now) throw new RemEnableGateError("artifact_not_yet_valid");
909
+ if (expiresAt <= now) throw new RemEnableGateError("artifact_expired");
910
+ }
911
+ //#endregion
912
+ //#region src/engine/rem/entry-governance.ts
913
+ async function recordNonRefusePairDecision(input) {
914
+ if (input.decision === "allow" || input.throughRefusePair) return;
915
+ if (input.reason === null || input.reason.trim().length === 0) throw new Error("A refused pair decision requires a reason");
916
+ const existing = input.database.prepare(`SELECT job_id, job_type, stage, outcome, reason
917
+ FROM nodix_rem_journal WHERE attempt_id = ?`).get(input.attemptIdentity);
918
+ if (existing !== void 0) {
919
+ if (existing.job_id === input.waveId && existing.job_type === input.operation && existing.stage === input.source && existing.outcome === "refused" && existing.reason === input.reason) return;
920
+ throw new Error("attempt_decision_conflict");
921
+ }
922
+ input.database.prepare(`INSERT INTO nodix_rem_journal(
923
+ job_id, job_type, stage, attempt_id, outcome,
924
+ pairs_scanned, verdicts, actions_applied, reason
925
+ ) VALUES (?, ?, ?, ?, 'refused', 0, 0, 0, ?)`).run(input.waveId, input.operation, input.source, input.attemptIdentity, input.reason);
926
+ }
927
+ function validateRemSubstantiveWaveEffects(input) {
928
+ const journalRows = input.database.prepare(`SELECT count(*) AS count FROM nodix_rem_journal
929
+ WHERE job_id = ? AND outcome NOT IN ('refused', 'disabled', 'no-action')`).get(input.waveId);
930
+ const effects = input.database.prepare("SELECT count(*) AS count FROM nodix_rem_write_attempts WHERE job_id = ? AND outcome = 'succeeded'").get(input.waveId);
931
+ const groupCloses = input.database.prepare(`SELECT count(*) AS count
932
+ FROM nodix_rem_journal AS journal
933
+ JOIN nodix_memories AS memory
934
+ ON memory.id = CASE WHEN json_valid(journal.detail)
935
+ THEN json_extract(journal.detail, '$.targetRowId') END
936
+ WHERE journal.job_id = ?
937
+ AND journal.outcome = 'done'
938
+ AND journal.stage LIKE 'update-retirement-target:%'
939
+ AND json_extract(memory.metadata, '$.superseded_by') = CASE
940
+ WHEN json_valid(journal.detail)
941
+ THEN json_extract(journal.detail, '$.nominatedRowId') END`).get(input.waveId);
942
+ if ((journalRows?.count ?? 0) > 0 && (effects?.count ?? 0) + (groupCloses?.count ?? 0) === 0) return {
943
+ decision: "refuse",
944
+ reasonCode: "journal_only"
945
+ };
946
+ return {
947
+ decision: "allow",
948
+ reasonCode: null
949
+ };
950
+ }
951
+ function defineRemSafetyGuard(_metadata, guard) {
952
+ return guard;
953
+ }
954
+ function validateRemGuardCensus(input) {
955
+ const manifest = readJsonArray(input.manifestPath);
956
+ const required = readJsonArray(input.requiredGuardsPath);
957
+ const sourceFiles = input.sourceRoots.flatMap(listTypeScriptFiles);
958
+ const sources = new Map(sourceFiles.map((file) => [path.resolve(file), readFileSync(file, "utf8")]));
959
+ const discovered = /* @__PURE__ */ new Set();
960
+ for (const source of sources.values()) for (const match of source.matchAll(/defineRemSafetyGuard\s*\(\s*\{\s*guardId:\s*["']([^"']+)["']/gu)) {
961
+ const guardId = match[1];
962
+ if (guardId !== void 0) discovered.add(guardId);
963
+ }
964
+ const manifestIds = new Set(manifest.map((row) => row.guardId));
965
+ if (manifestIds.size === 0 && discovered.size === 0) return {
966
+ decision: "refuse",
967
+ reasonCode: "guard_inventory_empty"
968
+ };
969
+ if (!sameSet(manifestIds, discovered)) return {
970
+ decision: "refuse",
971
+ reasonCode: [...sources.values()].some((source) => /(?:function\s+\w+\s*\([^)]*\)\s*:\s*RemRefusalCode|throw\s+new\s+\w*RemGateError)/u.test(source)) ? "unmarked_guard" : "manifest_source_inequality"
972
+ };
973
+ if (required.length === 0 || manifest.some((row) => !required.some((item) => item.guardId === row.guardId)) || required.some((item) => !manifestIds.has(item.guardId))) return {
974
+ decision: "refuse",
975
+ reasonCode: "required_protection_missing"
976
+ };
977
+ const reachable = collectReachableFiles(input.productionRoots, sources);
978
+ for (const row of manifest) {
979
+ const moduleFile = [...sources.keys()].find((file) => file.endsWith(row.module));
980
+ if (moduleFile === void 0 || !reachable.has(moduleFile)) return {
981
+ decision: "refuse",
982
+ reasonCode: "guard_unreachable"
983
+ };
984
+ }
985
+ return {
986
+ decision: "allow",
987
+ reasonCode: null
988
+ };
989
+ }
990
+ function readJsonArray(file) {
991
+ const value = JSON.parse(readFileSync(file, "utf8"));
992
+ if (!Array.isArray(value)) throw new Error(`${file} must contain a JSON array`);
993
+ return value;
994
+ }
995
+ function listTypeScriptFiles(root) {
996
+ return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
997
+ const entryPath = path.join(root, entry.name);
998
+ if (entry.isDirectory()) return listTypeScriptFiles(entryPath);
999
+ return entry.isFile() && /\.tsx?$/u.test(entry.name) ? [entryPath] : [];
1000
+ });
1001
+ }
1002
+ function collectReachableFiles(roots, sources) {
1003
+ const reachable = /* @__PURE__ */ new Set();
1004
+ const pending = roots.map((root) => path.resolve(root));
1005
+ while (pending.length > 0) {
1006
+ const file = pending.pop();
1007
+ if (file === void 0 || reachable.has(file)) continue;
1008
+ const source = sources.get(file);
1009
+ if (source === void 0) continue;
1010
+ reachable.add(file);
1011
+ for (const match of source.matchAll(/(?:from\s+|import\s*\()["'](\.[^"']+)["']/gu)) {
1012
+ const specifier = match[1];
1013
+ if (specifier === void 0) continue;
1014
+ for (const candidate of resolveSourceCandidates(file, specifier)) if (sources.has(candidate)) pending.push(candidate);
1015
+ }
1016
+ }
1017
+ return reachable;
1018
+ }
1019
+ function resolveSourceCandidates(importer, specifier) {
1020
+ const base = path.resolve(path.dirname(importer), specifier);
1021
+ return [
1022
+ base,
1023
+ `${base}.ts`,
1024
+ `${base}.tsx`,
1025
+ path.join(base, "index.ts")
1026
+ ].map((file) => path.resolve(file));
1027
+ }
1028
+ function sameSet(left, right) {
1029
+ return left.size === right.size && [...left].every((value) => right.has(value));
1030
+ }
1031
+ //#endregion
1032
+ //#region src/engine/rem/row-hash.ts
1033
+ /**
1034
+ * The one definition of a REM whole-row hash.
1035
+ *
1036
+ * A recovery record stores `expected_post_hash` before the write; the verified-write guard
1037
+ * recomputes it after the write and refuses the mutation when the two disagree. Those two
1038
+ * computations lived in different packages and drifted: this package hashed `SELECT *` while
1039
+ * `rem-sqlite-adapter` hashed a projected row, so adding the `timezone` column to the projection
1040
+ * made every text-version write refuse AFTER its transaction had already committed. One function,
1041
+ * imported by both sides, is what stops that happening again.
1042
+ *
1043
+ * `REM_ROW_HASH_FIELDS` IS the hash — reordering it, adding to it, or removing from it changes
1044
+ * every value this function produces. That is why it is versioned rather than edited in place.
1045
+ */
1046
+ const REM_ROW_HASH_FIELDS = [
1047
+ "id",
1048
+ "text",
1049
+ "category",
1050
+ "project_id",
1051
+ "importance",
1052
+ "timestamp",
1053
+ "timezone",
1054
+ "metadata",
1055
+ "content_hash",
1056
+ "fact_id",
1057
+ "derived_from",
1058
+ "consolidation_epoch_id",
1059
+ "confidence_source",
1060
+ "lane",
1061
+ "raw_candidate_json",
1062
+ "disposition_reason",
1063
+ "dispositioned_at_ms"
1064
+ ];
1065
+ /**
1066
+ * Stamped on every recovery record this build writes. Version 1 means "written before the field
1067
+ * list was pinned", and a version-1 hash cannot be reproduced — the two producers of that era
1068
+ * covered different columns. Readers must branch on the stored version rather than assume.
1069
+ */
1070
+ const REM_ROW_HASH_VERSION = 2;
1071
+ /** Version 1 records predate the pinned field list and carry a hash nothing can recompute. */
1072
+ const REM_ROW_HASH_VERSION_LEGACY = 1;
1073
+ function hashRemMemoryRow(row) {
1074
+ const projected = {};
1075
+ for (const field of REM_ROW_HASH_FIELDS) projected[field] = row[field] ?? null;
1076
+ return createHash("sha256").update(JSON.stringify(projected)).digest("hex");
1077
+ }
1078
+ //#endregion
1079
+ //#region src/engine/rem/facet-persistence.ts
1080
+ async function writeRemTwoFacetTransaction(input) {
1081
+ const timestamp = input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1082
+ const updatedAtMs = Date.parse(timestamp);
1083
+ if (!Number.isFinite(updatedAtMs)) throw new Error("REM facet timestamp must be valid");
1084
+ if (input.current.trim().length === 0 || input.history.trim().length === 0) throw new Error("REM facet text must be non-empty");
1085
+ if (input.currentChunks.length === 0) throw new Error("REM current facet requires prepared chunks");
1086
+ assertJobIdentity(input.jobId, input.jobType);
1087
+ return input.database.transaction(() => {
1088
+ const row = input.database.prepare("SELECT * FROM nodix_memories WHERE id = ?").get(input.memoryId);
1089
+ if (row === void 0) throw new Error("REM facet target row is missing");
1090
+ const nextHash = input.contentHash ?? createHash("sha256").update(input.current, "utf8").digest("hex");
1091
+ const recoveryHandle = randomUUID();
1092
+ const priorFacets = input.database.prepare("SELECT facet, text, updated_at_ms FROM nodix_rem_memory_facets WHERE memory_id = ? ORDER BY facet").all(input.memoryId);
1093
+ const priorChunkFacets = input.database.prepare("SELECT chunk_id, facet, chunk_index FROM nodix_memory_chunks WHERE memory_id = ? ORDER BY chunk_id").all(input.memoryId);
1094
+ if (input.metadata === void 0) input.database.prepare("UPDATE nodix_memories SET text = ?, content_hash = ? WHERE id = ?").run(input.current, nextHash, input.memoryId);
1095
+ else input.database.prepare("UPDATE nodix_memories SET text = ?, metadata = ?, content_hash = ? WHERE id = ?").run(input.current, input.metadata, nextHash, input.memoryId);
1096
+ if (process.env["SNO_STATION_MEM_REM_TEST_FAILPOINT"] === "after_primary_write") throw new Error("SNO_STATION_MEM_REM_TEST_FAILPOINT:after_primary_write");
1097
+ const postRow = input.database.prepare("SELECT * FROM nodix_memories WHERE id = ?").get(input.memoryId);
1098
+ if (postRow === void 0) throw new Error("REM facet target row disappeared after update");
1099
+ const expectedPostHash = hashRemMemoryRow(postRow);
1100
+ input.database.prepare(`INSERT INTO nodix_rem_recovery_history(
1101
+ recovery_handle, row_id, operation_kind, prior_row_image, prior_content_hash,
1102
+ expected_post_hash, row_hash_version, reason, mutation_ts
1103
+ ) VALUES (?, ?, 'text-version', ?, ?, ?, ?, ?, ?)`).run(recoveryHandle, input.memoryId, JSON.stringify(row), row.content_hash, expectedPostHash, 2, input.reason ?? "REM two-facet update", timestamp);
1104
+ if (input.attemptId !== void 0) input.database.prepare(`UPDATE nodix_rem_write_attempts SET expected_post_content_sha256 = ?
1105
+ WHERE attempt_id = ? AND outcome = 'pending'`).run(nextHash, input.attemptId);
1106
+ const historyIndex = input.database.prepare("SELECT COALESCE(MAX(chunk_index), -1) AS value FROM nodix_memory_chunks WHERE memory_id = ? AND facet = 'history'").get(input.memoryId);
1107
+ input.database.prepare("UPDATE nodix_memory_chunks SET facet = 'history', chunk_index = chunk_index + ? WHERE memory_id = ? AND facet = 'current'").run(historyIndex.value + 1, input.memoryId);
1108
+ const insertChunk = input.database.prepare(`INSERT INTO nodix_memory_chunks(
1109
+ chunk_id, memory_id, chunk_index, chunk_text, dense_payload, summary,
1110
+ entities, tags, source, start_offset, end_offset, token_count, content_type,
1111
+ chunking_version, embedder_provider, embedder_model, embedder_dim,
1112
+ created_at, updated_at, facet
1113
+ ) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'current')`);
1114
+ const insertVector = input.database.prepare("INSERT INTO nodix_memory_chunk_vectors(id, project_id, embedding) VALUES (?, ?, vec_f32(?))");
1115
+ for (const chunk of input.currentChunks) {
1116
+ insertChunk.run(chunk.chunkId, input.memoryId, chunk.chunkIndex, chunk.chunkText, chunk.densePayload, chunk.summary ?? null, chunk.startOffset, chunk.endOffset, chunk.tokenCount, chunk.contentType, chunk.chunkingVersion, chunk.embedderProvider, chunk.embedderModel, chunk.embedderDim, updatedAtMs, updatedAtMs);
1117
+ insertVector.run(chunk.chunkId, row.project_id, chunk.vectorBytes);
1118
+ }
1119
+ input.database.prepare("DELETE FROM nodix_rem_memory_facets WHERE memory_id = ?").run(input.memoryId);
1120
+ const insertFacet = input.database.prepare("INSERT INTO nodix_rem_memory_facets(memory_id, facet, text, updated_at_ms) VALUES (?, ?, ?, ?)");
1121
+ insertFacet.run(input.memoryId, "current", input.current, updatedAtMs);
1122
+ insertFacet.run(input.memoryId, "history", input.history, updatedAtMs);
1123
+ const expectedFacets = input.database.prepare("SELECT facet, text, updated_at_ms FROM nodix_rem_memory_facets WHERE memory_id = ? ORDER BY facet").all(input.memoryId);
1124
+ const expectedChunkFacets = input.database.prepare("SELECT chunk_id, facet, chunk_index FROM nodix_memory_chunks WHERE memory_id = ? ORDER BY chunk_id").all(input.memoryId);
1125
+ input.database.prepare(`INSERT INTO nodix_rem_facet_recovery(
1126
+ recovery_handle, prior_facets_json, prior_chunk_facets_json,
1127
+ expected_facets_json, expected_chunk_facets_json
1128
+ ) VALUES (?, ?, ?, ?, ?)`).run(recoveryHandle, JSON.stringify(priorFacets), JSON.stringify(priorChunkFacets), JSON.stringify(expectedFacets), JSON.stringify(expectedChunkFacets));
1129
+ const queueId = input.jobId;
1130
+ input.database.prepare(`INSERT INTO nodix_rem_journal(
1131
+ job_id, job_type, stage, outcome, row_id, pairs_scanned, verdicts, actions_applied
1132
+ ) VALUES (?, ?, 'two-facet-write', 'done', ?, 0, 0, 1)`).run(queueId, input.jobType, input.memoryId);
1133
+ return {
1134
+ contentHash: nextHash,
1135
+ recoveryHandle
1136
+ };
1137
+ }).immediate();
1138
+ }
1139
+ //#endregion
1140
+ //#region src/engine/rem/repository.ts
1141
+ const bootIdPath = "/proc/sys/kernel/random/boot_id";
1142
+ function createRemRepository(database) {
1143
+ return {
1144
+ recordClassification: (input) => recordClassification(database, input),
1145
+ claimRow: (input) => claimRow(database, input),
1146
+ recoverRowClaim: (input) => recoverRowClaim(database, input),
1147
+ completeRowClaim: (input) => completeRowClaim(database, input),
1148
+ releaseRowClaim: (input) => releaseRowClaim(database, input),
1149
+ createVerdictGeneration: (input) => createVerdictGeneration(database, input),
1150
+ findOpenVerdictGeneration: (input) => findOpenVerdictGeneration(database, input),
1151
+ listVerdictPairs: (generationId) => listVerdictPairs(database, generationId),
1152
+ readClosedVerdictPairIds: (pairIds) => readClosedVerdictPairIds(database, pairIds),
1153
+ claimNextPair: (input) => claimNextPair(database, input),
1154
+ releasePairClaim: (input) => releasePairClaim(database, input),
1155
+ completePair: (input) => completePair(database, input),
1156
+ refusePair: (input) => refusePair(database, input),
1157
+ reserveLlmBudget: (input) => reserveLlmBudget(database, input),
1158
+ readGeneration: (generationId) => readGeneration(database, generationId),
1159
+ readInvocation: (input) => readInvocation(database, input),
1160
+ appendJournal: (jobId, jobType, entry) => appendJournal(database, jobId, jobType, entry),
1161
+ listJournal: (jobId, jobType) => listJournal(database, jobId, jobType),
1162
+ recordVerdictCheckpoint: (input) => recordVerdictCheckpoint(database, input),
1163
+ resumeVerdictPair: (input) => resumeVerdictPair(database, input),
1164
+ readVerdictPair: (generationId, pairId) => readVerdictPair(database, generationId, pairId)
1165
+ };
1166
+ }
1167
+ function recordClassification(database, input) {
1168
+ database.prepare(`INSERT INTO nodix_rem_relation_ledger(
1169
+ row_id, content_hash, state, owner, claim_ts, classified_at
1170
+ ) VALUES (?, ?, ?, 'none', NULL, ?)
1171
+ ON CONFLICT(row_id) DO UPDATE SET
1172
+ content_hash = excluded.content_hash,
1173
+ state = excluded.state,
1174
+ classified_at = excluded.classified_at
1175
+ WHERE nodix_rem_relation_ledger.owner = 'none'`).run(input.rowId, input.contentHash, input.state, input.classifiedAt);
1176
+ }
1177
+ function claimRow(database, input) {
1178
+ const holderProcessStart = requireProcessStart(input.holderPid);
1179
+ const holderBootId = requireBootId();
1180
+ return database.transaction(() => {
1181
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_relation_ledger
1182
+ SET owner = ?, claim_ts = ?
1183
+ WHERE row_id = ? AND owner = 'none' AND content_hash = ?
1184
+ AND EXISTS (
1185
+ SELECT 1 FROM nodix_memories
1186
+ WHERE id = nodix_rem_relation_ledger.row_id
1187
+ AND content_hash = nodix_rem_relation_ledger.content_hash
1188
+ )`).run(input.owner, input.claimTs, input.rowId, input.contentHash)).changes === 1) {
1189
+ database.prepare(`INSERT INTO nodix_rem_row_claims(
1190
+ row_id, content_hash, owner, claim_token, holder_pid,
1191
+ holder_process_start, holder_boot_id, claimed_at
1192
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.rowId, input.contentHash, input.owner, input.claimToken, input.holderPid, holderProcessStart, holderBootId, input.claimTs);
1193
+ return { claimed: true };
1194
+ }
1195
+ const ledger = database.prepare("SELECT content_hash, owner FROM nodix_rem_relation_ledger WHERE row_id = ?").get(input.rowId);
1196
+ const memory = database.prepare("SELECT content_hash FROM nodix_memories WHERE id = ?").get(input.rowId);
1197
+ if (!ledger || !memory) return {
1198
+ claimed: false,
1199
+ reason: "missing"
1200
+ };
1201
+ if (ledger.content_hash !== input.contentHash || memory.content_hash !== input.contentHash) return {
1202
+ claimed: false,
1203
+ reason: "content_changed"
1204
+ };
1205
+ const claim = database.prepare("SELECT claim_token FROM nodix_rem_row_claims WHERE row_id = ?").get(input.rowId);
1206
+ if (!claim) throw new Error("row claim has inconsistent durable state");
1207
+ return {
1208
+ claimed: false,
1209
+ reason: "already_claimed",
1210
+ claimToken: claim.claim_token
1211
+ };
1212
+ }).immediate();
1213
+ }
1214
+ function recoverRowClaim(database, input) {
1215
+ const holderProcessStart = requireProcessStart(input.holderPid);
1216
+ const holderBootId = requireBootId();
1217
+ const refusal = inspectRowClaimOwnership(database, input);
1218
+ if (refusal) return {
1219
+ recovered: false,
1220
+ reason: refusal
1221
+ };
1222
+ const prior = getRowClaim(database, input.rowId);
1223
+ if (!(prior.holder_pid === input.holderPid && prior.holder_process_start === holderProcessStart && prior.holder_boot_id === holderBootId) && isExactProcessAlive(prior.holder_pid, prior.holder_process_start, prior.holder_boot_id, holderBootId)) return {
1224
+ recovered: false,
1225
+ reason: "holder_alive"
1226
+ };
1227
+ return database.transaction(() => {
1228
+ const currentRefusal = inspectRowClaimOwnership(database, input);
1229
+ if (currentRefusal) return {
1230
+ recovered: false,
1231
+ reason: currentRefusal
1232
+ };
1233
+ const memory = database.prepare("SELECT content_hash FROM nodix_memories WHERE id = ?").get(input.rowId);
1234
+ if (!memory) return {
1235
+ recovered: false,
1236
+ reason: "missing"
1237
+ };
1238
+ if (memory.content_hash !== input.contentHash) {
1239
+ if (asRunResult(database.prepare(`DELETE FROM nodix_rem_row_claims
1240
+ WHERE row_id = ? AND content_hash = ? AND owner = ? AND state = 'active'
1241
+ AND claim_token = ? AND holder_pid = ? AND holder_process_start = ?
1242
+ AND holder_boot_id IS ?`).run(input.rowId, input.contentHash, input.owner, input.expectedClaimToken, prior.holder_pid, prior.holder_process_start, prior.holder_boot_id)).changes !== 1) return {
1243
+ recovered: false,
1244
+ reason: "claim_changed"
1245
+ };
1246
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_relation_ledger SET owner = 'none', claim_ts = NULL
1247
+ WHERE row_id = ? AND content_hash = ? AND owner = ?`).run(input.rowId, input.contentHash, input.owner)).changes !== 1) throw new Error("row claim has inconsistent durable state");
1248
+ return {
1249
+ recovered: false,
1250
+ reason: "content_changed_released"
1251
+ };
1252
+ }
1253
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_row_claims SET
1254
+ claim_token = ?, holder_pid = ?, holder_process_start = ?,
1255
+ holder_boot_id = ?, claimed_at = ?
1256
+ WHERE row_id = ? AND content_hash = ? AND owner = ? AND state = 'active'
1257
+ AND claim_token = ? AND holder_pid = ? AND holder_process_start = ?
1258
+ AND holder_boot_id IS ?`).run(input.claimToken, input.holderPid, holderProcessStart, holderBootId, input.claimTs, input.rowId, input.contentHash, input.owner, input.expectedClaimToken, prior.holder_pid, prior.holder_process_start, prior.holder_boot_id)).changes !== 1) return {
1259
+ recovered: false,
1260
+ reason: "claim_changed"
1261
+ };
1262
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_relation_ledger SET claim_ts = ?
1263
+ WHERE row_id = ? AND content_hash = ? AND owner = ?
1264
+ AND EXISTS (
1265
+ SELECT 1 FROM nodix_memories
1266
+ WHERE id = nodix_rem_relation_ledger.row_id
1267
+ AND content_hash = nodix_rem_relation_ledger.content_hash
1268
+ )`).run(input.claimTs, input.rowId, input.contentHash, input.owner)).changes !== 1) throw new Error("row claim has inconsistent durable state");
1269
+ return { recovered: true };
1270
+ }).immediate();
1271
+ }
1272
+ function completeRowClaim(database, input) {
1273
+ assertJobIdentity(input.jobId, input.jobType);
1274
+ return database.transaction(() => {
1275
+ const prior = getRowClaim(database, input.rowId);
1276
+ if (prior.state === "completed") {
1277
+ const ledger = database.prepare("SELECT content_hash, owner FROM nodix_rem_relation_ledger WHERE row_id = ?").get(input.rowId);
1278
+ const memory = database.prepare("SELECT content_hash FROM nodix_memories WHERE id = ?").get(input.rowId);
1279
+ if (prior.claim_token === input.claimToken && prior.content_hash === input.contentHash && prior.owner === input.owner && ledger?.content_hash === input.contentHash && ledger.owner === input.owner && memory?.content_hash === input.currentContentHash) return { completed: true };
1280
+ }
1281
+ const refusal = inspectRowClaim(database, {
1282
+ ...input,
1283
+ expectedClaimToken: input.claimToken,
1284
+ expectedMemoryContentHash: input.currentContentHash
1285
+ });
1286
+ if (refusal) {
1287
+ if (refusal === "content_changed") appendJournal(database, input.jobId, input.jobType, {
1288
+ stage: `row-claim:${input.rowId}`,
1289
+ outcome: "refused",
1290
+ pairsScanned: 0,
1291
+ verdicts: 0,
1292
+ actionsApplied: 0,
1293
+ reason: refusal,
1294
+ rowId: input.rowId
1295
+ });
1296
+ return {
1297
+ completed: false,
1298
+ reason: refusal
1299
+ };
1300
+ }
1301
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_row_claims SET state = 'completed', completed_at = ?
1302
+ WHERE row_id = ? AND content_hash = ? AND owner = ?
1303
+ AND claim_token = ? AND state = 'active'`).run(input.completedAt, input.rowId, input.contentHash, input.owner, input.claimToken)).changes !== 1) return {
1304
+ completed: false,
1305
+ reason: "claim_changed"
1306
+ };
1307
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_relation_ledger SET claim_ts = NULL
1308
+ WHERE row_id = ? AND content_hash = ? AND owner = ?`).run(input.rowId, input.contentHash, input.owner)).changes !== 1) throw new Error("row claim has inconsistent durable state");
1309
+ return { completed: true };
1310
+ }).immediate();
1311
+ }
1312
+ function releaseRowClaim(database, input) {
1313
+ return database.transaction(() => {
1314
+ const refusal = inspectRowClaimOwnership(database, {
1315
+ ...input,
1316
+ expectedClaimToken: input.claimToken
1317
+ });
1318
+ if (refusal) return {
1319
+ released: false,
1320
+ reason: refusal
1321
+ };
1322
+ if (asRunResult(database.prepare(`DELETE FROM nodix_rem_row_claims
1323
+ WHERE row_id = ? AND content_hash = ? AND owner = ?
1324
+ AND claim_token = ? AND state = 'active'`).run(input.rowId, input.contentHash, input.owner, input.claimToken)).changes !== 1) return {
1325
+ released: false,
1326
+ reason: "claim_changed"
1327
+ };
1328
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_relation_ledger SET owner = 'none', claim_ts = NULL
1329
+ WHERE row_id = ? AND content_hash = ? AND owner = ?`).run(input.rowId, input.contentHash, input.owner)).changes !== 1) throw new Error("row claim has inconsistent durable state");
1330
+ return { released: true };
1331
+ }).immediate();
1332
+ }
1333
+ function createVerdictGeneration(database, input) {
1334
+ database.transaction(() => {
1335
+ database.prepare(`INSERT INTO nodix_rem_scan_generations(
1336
+ generation_id, corpus_snapshot_hash, pairing_config_hash, max_llm_calls, max_tokens
1337
+ ) VALUES (?, ?, ?, ?, ?)`).run(input.generationId, input.corpusSnapshotHash, input.pairingConfigHash, input.maxLlmCalls, input.maxTokens);
1338
+ const insertPair = database.prepare(`INSERT INTO nodix_rem_scan_pairs(
1339
+ generation_id, pair_id, left_row_id, right_row_id, sort_key,
1340
+ claim_state, attempt_count, progress_state, refusal_reason, inherited_from_generation_id
1341
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
1342
+ const inheritedRefusal = input.inheritedRefusalsFromGenerationId ? database.prepare(`SELECT attempt_count, refusal_reason FROM nodix_rem_scan_pairs
1343
+ WHERE generation_id = ? AND pair_id = ? AND progress_state = 'refused'
1344
+ AND refusal_reason IN ('content_changed', 'target_changed')`) : void 0;
1345
+ for (const pair of input.pairs) {
1346
+ const inherited = inheritedRefusal?.get(input.inheritedRefusalsFromGenerationId, pair.pairId);
1347
+ insertPair.run(input.generationId, pair.pairId, pair.leftRowId, pair.rightRowId, pair.sortKey, "unvisited", inherited?.attempt_count ?? 0, inherited ? "refused" : "pending", inherited?.refusal_reason ?? null, inherited ? input.inheritedRefusalsFromGenerationId : null);
1348
+ }
1349
+ }).immediate();
1350
+ }
1351
+ function findOpenVerdictGeneration(database, input) {
1352
+ const row = database.prepare(`SELECT generation_id, corpus_snapshot_hash
1353
+ FROM nodix_rem_scan_generations AS generation
1354
+ WHERE corpus_snapshot_hash = ? AND pairing_config_hash = ?
1355
+ AND EXISTS (
1356
+ SELECT 1 FROM nodix_rem_scan_pairs AS pair
1357
+ WHERE pair.generation_id = generation.generation_id
1358
+ AND pair.claim_state != 'done'
1359
+ )
1360
+ ORDER BY rowid
1361
+ LIMIT 1`).get(input.corpusSnapshotHash, input.pairingConfigHash);
1362
+ return row === void 0 ? void 0 : {
1363
+ generationId: row.generation_id,
1364
+ corpusSnapshotHash: row.corpus_snapshot_hash
1365
+ };
1366
+ }
1367
+ function listVerdictPairs(database, generationId) {
1368
+ return database.prepare(`SELECT pair_id, left_row_id, right_row_id, sort_key, claim_state, invocation_id
1369
+ FROM nodix_rem_scan_pairs
1370
+ WHERE generation_id = ?
1371
+ ORDER BY sort_key, pair_id`).all(generationId).map((row) => ({
1372
+ pairId: row.pair_id,
1373
+ leftRowId: row.left_row_id,
1374
+ rightRowId: row.right_row_id,
1375
+ sortKey: row.sort_key,
1376
+ claimState: row.claim_state,
1377
+ invocationId: row.invocation_id
1378
+ }));
1379
+ }
1380
+ function readClosedVerdictPairIds(database, pairIds) {
1381
+ const rows = database.prepare(`SELECT DISTINCT pair_id FROM nodix_rem_scan_pairs
1382
+ WHERE progress_state = 'closed' AND pair_id IN (SELECT value FROM json_each(?))`).all(JSON.stringify(pairIds));
1383
+ return new Set(rows.map((row) => row.pair_id));
1384
+ }
1385
+ function claimNextPair(database, input) {
1386
+ const holderProcessStart = requireProcessStart(input.holderPid);
1387
+ const holderBootId = requireBootId();
1388
+ return database.transaction(() => {
1389
+ ensureInvocation(database, input.generationId, input.invocationId, input.claimedAt);
1390
+ const pair = database.prepare(`SELECT pair_id, left_row_id, right_row_id, sort_key, checkpoint, verdict,
1391
+ actions_applied, claim_state, invocation_id, budget_reserved, budget_invocation_id,
1392
+ reserved_tokens, attempt_count, progress_state, refusal_reason, inherited_from_generation_id
1393
+ FROM nodix_rem_scan_pairs
1394
+ WHERE generation_id = ? AND claim_state = 'unvisited'
1395
+ ORDER BY CASE progress_state WHEN 'refused' THEN 0 ELSE 1 END, sort_key, pair_id
1396
+ LIMIT 1`).get(input.generationId);
1397
+ if (!pair) return void 0;
1398
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET
1399
+ claim_state = 'claimed', invocation_id = ?, claimed_at = ?,
1400
+ attempt_count = attempt_count + 1, progress_state = 'pending', refusal_reason = NULL
1401
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'unvisited'`).run(input.invocationId, input.claimedAt, input.generationId, pair.pair_id)).changes !== 1) return void 0;
1402
+ database.prepare(`INSERT INTO nodix_rem_pair_claims(
1403
+ generation_id, pair_id, claim_token, holder_pid, holder_process_start,
1404
+ holder_boot_id, claimed_at
1405
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(input.generationId, pair.pair_id, input.invocationId, input.holderPid, holderProcessStart, holderBootId, input.claimedAt);
1406
+ return {
1407
+ pairId: pair.pair_id,
1408
+ leftRowId: pair.left_row_id,
1409
+ rightRowId: pair.right_row_id
1410
+ };
1411
+ }).immediate();
1412
+ }
1413
+ function completePair(database, input) {
1414
+ database.transaction(() => {
1415
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET claim_state = 'done', progress_state = 'closed'
1416
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'claimed'
1417
+ AND invocation_id = ?`).run(input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair is not owned by invocation");
1418
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_pair_claims SET state = 'completed', completed_at = CURRENT_TIMESTAMP
1419
+ WHERE generation_id = ? AND pair_id = ? AND claim_token = ? AND state = 'active'`).run(input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair claim has inconsistent durable state");
1420
+ }).immediate();
1421
+ }
1422
+ function releasePairClaim(database, input) {
1423
+ database.transaction(() => {
1424
+ if (asRunResult(database.prepare(`DELETE FROM nodix_rem_pair_claims WHERE generation_id = ? AND pair_id = ?
1425
+ AND claim_token = ? AND state = 'active'`).run(input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair claim is not owned by invocation");
1426
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET claim_state = 'unvisited', invocation_id = NULL,
1427
+ claimed_at = NULL, checkpoint = NULL, verdict = NULL,
1428
+ budget_reserved = 0, budget_invocation_id = NULL, reserved_tokens = 0,
1429
+ attempt_count = MAX(0, attempt_count - 1)
1430
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'claimed' AND invocation_id = ?`).run(input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair claim has inconsistent durable state");
1431
+ database.prepare("DELETE FROM nodix_rem_pair_stage_budgets WHERE generation_id = ? AND pair_id = ?").run(input.generationId, input.pairId);
1432
+ }).immediate();
1433
+ }
1434
+ function refusePair(database, input) {
1435
+ if (input.reason.trim().length === 0) throw new Error("pair refusal requires a reason");
1436
+ assertJobIdentity(input.jobId, input.jobType);
1437
+ return database.transaction(() => {
1438
+ const pair = getPairRow(database, input.generationId, input.pairId);
1439
+ if (pair.claim_state !== "claimed" || pair.invocation_id !== input.invocationId) throw new Error("pair is not owned by invocation");
1440
+ const state = pair.attempt_count >= 3 ? "exhausted" : "refused";
1441
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET claim_state = 'done', progress_state = ?, refusal_reason = ?
1442
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'claimed'
1443
+ AND invocation_id = ?`).run(state, input.reason, input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair is not owned by invocation");
1444
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_pair_claims SET state = 'completed', completed_at = CURRENT_TIMESTAMP
1445
+ WHERE generation_id = ? AND pair_id = ? AND claim_token = ? AND state = 'active'`).run(input.generationId, input.pairId, input.invocationId)).changes !== 1) throw new Error("pair claim has inconsistent durable state");
1446
+ appendJournal(database, input.jobId, input.jobType, {
1447
+ stage: `replace-pair:${input.pairId}`,
1448
+ outcome: "refused",
1449
+ pairsScanned: 1,
1450
+ verdicts: 1,
1451
+ actionsApplied: 0,
1452
+ reason: input.reason,
1453
+ pairId: input.pairId
1454
+ });
1455
+ return {
1456
+ state,
1457
+ attemptCount: pair.attempt_count
1458
+ };
1459
+ }).immediate();
1460
+ }
1461
+ function reserveLlmBudget(database, input) {
1462
+ return parseBudgetReservation(database.transaction(() => {
1463
+ const pair = getPairRow(database, input.generationId, input.pairId);
1464
+ if (pair.claim_state !== "claimed" || pair.invocation_id !== input.invocationId) throw new Error("budget pair is not owned by invocation");
1465
+ const holder = getPairClaim(database, input.generationId, input.pairId);
1466
+ if (holder.state !== "active" || holder.claim_token !== input.invocationId) throw new Error("budget pair has inconsistent durable claim");
1467
+ const existingStage = database.prepare(`SELECT reserved_tokens, idempotency_key FROM nodix_rem_pair_stage_budgets
1468
+ WHERE generation_id = ? AND pair_id = ? AND stage = ?`).get(input.generationId, input.pairId, input.stage);
1469
+ if (existingStage !== void 0) return {
1470
+ status: "reserved",
1471
+ tokens: existingStage.reserved_tokens,
1472
+ idempotencyKey: existingStage.idempotency_key
1473
+ };
1474
+ if (!Number.isSafeInteger(input.outputTokenCap) || input.outputTokenCap < 0) throw new Error("output token cap must be a non-negative safe integer");
1475
+ let tokens;
1476
+ if (input.providerUsage) tokens = input.providerUsage.inputTokens + input.providerUsage.outputTokens;
1477
+ else if (input.countTokens) tokens = input.countTokens(input.prompt) + input.outputTokenCap;
1478
+ else {
1479
+ persistPendingStage(database, input.generationId, input.stage);
1480
+ return {
1481
+ status: "pending",
1482
+ reason: "tokenizer_unavailable"
1483
+ };
1484
+ }
1485
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_invocations
1486
+ SET llm_calls_used = llm_calls_used + 1, tokens_used = tokens_used + ?
1487
+ WHERE generation_id = ? AND invocation_id = ?`).run(tokens, input.generationId, input.invocationId)).changes === 1) {
1488
+ database.prepare(`UPDATE nodix_rem_scan_generations
1489
+ SET llm_calls_used = llm_calls_used + 1, tokens_used = tokens_used + ?
1490
+ WHERE generation_id = ?`).run(tokens, input.generationId);
1491
+ const idempotencyKey = `${holder.claim_token}:${input.stage}`;
1492
+ database.prepare(`INSERT INTO nodix_rem_pair_stage_budgets(
1493
+ generation_id, pair_id, stage, invocation_id, reserved_tokens, idempotency_key
1494
+ ) VALUES (?, ?, ?, ?, ?, ?)`).run(input.generationId, input.pairId, input.stage, input.invocationId, tokens, idempotencyKey);
1495
+ return {
1496
+ status: "reserved",
1497
+ tokens,
1498
+ idempotencyKey
1499
+ };
1500
+ }
1501
+ persistPendingStage(database, input.generationId, input.stage);
1502
+ return {
1503
+ status: "pending",
1504
+ reason: "invocation_row_missing"
1505
+ };
1506
+ }).immediate());
1507
+ }
1508
+ function readGeneration(database, generationId) {
1509
+ const row = getGenerationRow(database, generationId);
1510
+ return {
1511
+ generationId: row.generation_id,
1512
+ pendingStages: parsePendingStages(row.pending_stages_json),
1513
+ llmCallsUsed: row.llm_calls_used,
1514
+ tokensUsed: row.tokens_used
1515
+ };
1516
+ }
1517
+ function readInvocation(database, input) {
1518
+ const row = getInvocationRow(database, input.generationId, input.invocationId);
1519
+ return {
1520
+ llmCallsUsed: row.llm_calls_used,
1521
+ tokensUsed: row.tokens_used
1522
+ };
1523
+ }
1524
+ function appendJournal(database, jobId, jobType, entry) {
1525
+ database.prepare(`INSERT INTO nodix_rem_journal(
1526
+ job_id, job_type, stage, outcome, pairs_scanned, verdicts, actions_applied, reason,
1527
+ detail, row_id, pair_id
1528
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(jobId, jobType, entry.stage, entry.outcome, entry.pairsScanned, entry.verdicts, entry.actionsApplied, entry.reason ?? null, entry.detail ?? null, entry.rowId ?? null, entry.pairId ?? null);
1529
+ }
1530
+ function listJournal(database, jobId, jobType) {
1531
+ const operationFilter = jobType === void 0 ? "" : " AND job_type = ?";
1532
+ const parameters = jobType === void 0 ? [jobId] : [jobId, jobType];
1533
+ return database.prepare(`SELECT stage, outcome, pairs_scanned, verdicts, actions_applied, reason, detail,
1534
+ row_id, pair_id
1535
+ FROM nodix_rem_journal WHERE job_id = ?${operationFilter} ORDER BY sequence`).all(...parameters).map((row) => ({
1536
+ stage: row.stage,
1537
+ outcome: row.outcome,
1538
+ pairsScanned: row.pairs_scanned,
1539
+ verdicts: row.verdicts,
1540
+ actionsApplied: row.actions_applied,
1541
+ ...row.reason === null ? {} : { reason: row.reason },
1542
+ ...row.detail === null ? {} : { detail: row.detail },
1543
+ ...row.row_id === null ? {} : { rowId: row.row_id },
1544
+ ...row.pair_id === null ? {} : { pairId: row.pair_id }
1545
+ }));
1546
+ }
1547
+ function recordVerdictCheckpoint(database, input) {
1548
+ if (input.verdict !== void 0 && input.verdict !== "keep" && input.verdict !== "replacement" && input.verdict !== "uncertain") throw new Error("verdict must be keep, replacement, or uncertain");
1549
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET
1550
+ claimed_at = ?,
1551
+ checkpoint = ?,
1552
+ verdict = COALESCE(?, verdict),
1553
+ actions_applied = CASE
1554
+ WHEN ? = 'action_applied' THEN 1
1555
+ ELSE actions_applied
1556
+ END
1557
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'claimed'
1558
+ AND invocation_id = ?
1559
+ AND CASE checkpoint
1560
+ WHEN 'action_applied' THEN 3
1561
+ WHEN 'verdict_recorded' THEN 2
1562
+ WHEN 'before_llm' THEN 1
1563
+ ELSE 0
1564
+ END <= CASE ?
1565
+ WHEN 'action_applied' THEN 3
1566
+ WHEN 'verdict_recorded' THEN 2
1567
+ WHEN 'before_llm' THEN 1
1568
+ ELSE 0
1569
+ END`).run(input.recordedAt, input.checkpoint, input.verdict ?? null, input.checkpoint, input.generationId, input.pairId, input.invocationId, input.checkpoint)).changes !== 1) throw new Error("verdict pair is not owned by invocation");
1570
+ }
1571
+ function resumeVerdictPair(database, input) {
1572
+ const holderProcessStart = requireProcessStart(input.holderPid);
1573
+ const holderBootId = requireBootId();
1574
+ const pair = getPairRow(database, input.generationId, input.pairId);
1575
+ const holder = getPairClaim(database, input.generationId, input.pairId);
1576
+ if (pair.claim_state !== "claimed" || pair.invocation_id !== input.expectedInvocationId || holder.state !== "active" || holder.claim_token !== input.expectedInvocationId) throw new Error("verdict pair is not owned by invocation");
1577
+ if (!(holder.holder_pid === input.holderPid && holder.holder_process_start === holderProcessStart && holder.holder_boot_id === holderBootId) && isExactProcessAlive(holder.holder_pid, holder.holder_process_start, holder.holder_boot_id, holderBootId)) return {
1578
+ next: "refused",
1579
+ reason: "holder_alive"
1580
+ };
1581
+ const claimToken = input.invocationId;
1582
+ return database.transaction(() => {
1583
+ ensureInvocation(database, input.generationId, claimToken, input.resumedAt);
1584
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_scan_pairs SET invocation_id = ?, claimed_at = ?
1585
+ WHERE generation_id = ? AND pair_id = ? AND claim_state = 'claimed'
1586
+ AND invocation_id = ?`).run(claimToken, input.resumedAt, input.generationId, input.pairId, input.expectedInvocationId)).changes !== 1) throw new Error("verdict pair is not owned by invocation");
1587
+ if (asRunResult(database.prepare(`UPDATE nodix_rem_pair_claims SET
1588
+ claim_token = ?, holder_pid = ?, holder_process_start = ?,
1589
+ holder_boot_id = ?, claimed_at = ?
1590
+ WHERE generation_id = ? AND pair_id = ? AND state = 'active'
1591
+ AND claim_token = ? AND holder_pid = ? AND holder_process_start = ?
1592
+ AND holder_boot_id IS ?`).run(claimToken, input.holderPid, holderProcessStart, holderBootId, input.resumedAt, input.generationId, input.pairId, input.expectedInvocationId, holder.holder_pid, holder.holder_process_start, holder.holder_boot_id)).changes !== 1) throw new Error("verdict pair has inconsistent durable claim");
1593
+ const resumed = getPairRow(database, input.generationId, input.pairId);
1594
+ if (resumed.checkpoint === "action_applied") return {
1595
+ next: "complete",
1596
+ claimToken
1597
+ };
1598
+ if (resumed.checkpoint === "verdict_recorded") {
1599
+ if (!resumed.verdict) throw new Error("recorded verdict is missing");
1600
+ return {
1601
+ next: "apply_action",
1602
+ verdict: resumed.verdict,
1603
+ claimToken
1604
+ };
1605
+ }
1606
+ return {
1607
+ next: "call_llm",
1608
+ claimToken
1609
+ };
1610
+ }).immediate();
1611
+ }
1612
+ function readVerdictPair(database, generationId, pairId) {
1613
+ const pair = getPairRow(database, generationId, pairId);
1614
+ return {
1615
+ checkpoint: pair.checkpoint,
1616
+ actionsApplied: pair.actions_applied
1617
+ };
1618
+ }
1619
+ function getGenerationRow(database, generationId) {
1620
+ const row = database.prepare("SELECT * FROM nodix_rem_scan_generations WHERE generation_id = ?").get(generationId);
1621
+ if (!row) throw new Error("scan generation not found");
1622
+ return row;
1623
+ }
1624
+ function ensureInvocation(database, generationId, invocationId, createdAt) {
1625
+ database.prepare(`INSERT INTO nodix_rem_scan_invocations(generation_id, invocation_id, created_at)
1626
+ VALUES (?, ?, ?) ON CONFLICT(generation_id, invocation_id) DO NOTHING`).run(generationId, invocationId, createdAt);
1627
+ }
1628
+ function getInvocationRow(database, generationId, invocationId) {
1629
+ const row = database.prepare(`SELECT llm_calls_used, tokens_used FROM nodix_rem_scan_invocations
1630
+ WHERE generation_id = ? AND invocation_id = ?`).get(generationId, invocationId);
1631
+ if (!row) throw new Error("scan invocation not found");
1632
+ return row;
1633
+ }
1634
+ function getPairRow(database, generationId, pairId) {
1635
+ const row = database.prepare(`SELECT pair_id, left_row_id, right_row_id, sort_key, checkpoint, verdict,
1636
+ actions_applied, claim_state, invocation_id, budget_reserved, budget_invocation_id,
1637
+ reserved_tokens, attempt_count, progress_state, refusal_reason, inherited_from_generation_id
1638
+ FROM nodix_rem_scan_pairs WHERE generation_id = ? AND pair_id = ?`).get(generationId, pairId);
1639
+ if (!row) throw new Error("verdict pair not found");
1640
+ return row;
1641
+ }
1642
+ function getPairClaim(database, generationId, pairId) {
1643
+ const row = database.prepare(`SELECT claim_token, holder_pid, holder_process_start, holder_boot_id, state
1644
+ FROM nodix_rem_pair_claims WHERE generation_id = ? AND pair_id = ?`).get(generationId, pairId);
1645
+ if (!row) throw new Error("verdict pair has inconsistent durable claim");
1646
+ return row;
1647
+ }
1648
+ function getRowClaim(database, rowId) {
1649
+ const row = database.prepare(`SELECT content_hash, owner, claim_token, holder_pid, holder_process_start,
1650
+ holder_boot_id, state
1651
+ FROM nodix_rem_row_claims WHERE row_id = ?`).get(rowId);
1652
+ if (!row) throw new Error("row claim has inconsistent durable state");
1653
+ return row;
1654
+ }
1655
+ function inspectRowClaim(database, input) {
1656
+ const ownershipRefusal = inspectRowClaimOwnership(database, input);
1657
+ if (ownershipRefusal) return ownershipRefusal;
1658
+ const memory = database.prepare("SELECT content_hash FROM nodix_memories WHERE id = ?").get(input.rowId);
1659
+ if (!memory) return "missing";
1660
+ if (memory.content_hash !== (input.expectedMemoryContentHash ?? input.contentHash)) return "content_changed";
1661
+ }
1662
+ function inspectRowClaimOwnership(database, input) {
1663
+ const ledger = database.prepare("SELECT content_hash, owner FROM nodix_rem_relation_ledger WHERE row_id = ?").get(input.rowId);
1664
+ if (!ledger) return "missing";
1665
+ if (ledger.content_hash !== input.contentHash) return "inconsistent_state";
1666
+ const claim = database.prepare(`SELECT content_hash, owner, claim_token, holder_pid, holder_process_start,
1667
+ holder_boot_id, state
1668
+ FROM nodix_rem_row_claims WHERE row_id = ?`).get(input.rowId);
1669
+ if (!claim) return ledger.owner === "none" ? "claim_changed" : "inconsistent_state";
1670
+ if (claim.claim_token !== input.expectedClaimToken || claim.content_hash !== input.contentHash || claim.owner !== input.owner || claim.state !== "active") return "claim_changed";
1671
+ if (ledger.owner !== input.owner) return "inconsistent_state";
1672
+ }
1673
+ function requireProcessStart(pid) {
1674
+ if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error("holder pid must be positive");
1675
+ const marker = readProcessStart(pid);
1676
+ if (!marker) throw new Error("holder process is not running");
1677
+ return marker;
1678
+ }
1679
+ function isExactProcessAlive(pid, expectedStart, expectedBootId, currentBootId) {
1680
+ if (expectedBootId !== currentBootId) return false;
1681
+ const processStat = readProcessStat(pid);
1682
+ return processStat !== void 0 && processStat.start === expectedStart && processStat.state !== "Z" && processStat.state !== "X" && processStat.state !== "x";
1683
+ }
1684
+ function readProcessStart(pid) {
1685
+ return readProcessStat(pid)?.start;
1686
+ }
1687
+ function readProcessStat(pid) {
1688
+ try {
1689
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
1690
+ const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
1691
+ const state = fields[0];
1692
+ const start = fields[19];
1693
+ if (!state) throw new Error(`process ${pid} stat is missing its state`);
1694
+ if (!start) throw new Error(`process ${pid} stat is missing its start marker`);
1695
+ return {
1696
+ state,
1697
+ start
1698
+ };
1699
+ } catch (error) {
1700
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ESRCH")) return;
1701
+ throw error;
1702
+ }
1703
+ }
1704
+ function requireBootId() {
1705
+ const bootId = readFileSync(bootIdPath, "utf8").trim();
1706
+ if (!bootId) throw new Error("kernel boot id is empty");
1707
+ return bootId;
1708
+ }
1709
+ function persistPendingStage(database, generationId, stage) {
1710
+ const row = getGenerationRow(database, generationId);
1711
+ const pending = new Set(parsePendingStages(row.pending_stages_json));
1712
+ pending.add(stage);
1713
+ database.prepare("UPDATE nodix_rem_scan_generations SET pending_stages_json = ? WHERE generation_id = ?").run(JSON.stringify(Array.from(pending)), generationId);
1714
+ }
1715
+ function parsePendingStages(value) {
1716
+ const parsed = JSON.parse(value);
1717
+ if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string")) throw new Error("invalid pending stage record");
1718
+ return parsed;
1719
+ }
1720
+ function asRunResult(value) {
1721
+ if (typeof value !== "object" || value === null || !("changes" in value) || typeof value.changes !== "number") throw new Error("database run result is missing changes");
1722
+ return { changes: value.changes };
1723
+ }
1724
+ function parseBudgetReservation(value) {
1725
+ if (typeof value !== "object" || value === null || !("status" in value)) throw new Error("budget transaction returned an invalid result");
1726
+ if (value.status === "reserved" && "tokens" in value && typeof value.tokens === "number" && "idempotencyKey" in value && typeof value.idempotencyKey === "string") return {
1727
+ status: "reserved",
1728
+ tokens: value.tokens,
1729
+ idempotencyKey: value.idempotencyKey
1730
+ };
1731
+ if (value.status === "pending" && "reason" in value && (value.reason === "invocation_row_missing" || value.reason === "tokenizer_unavailable")) return {
1732
+ status: "pending",
1733
+ reason: value.reason
1734
+ };
1735
+ throw new Error("budget transaction returned an invalid result");
1736
+ }
1737
+ //#endregion
1738
+ //#region src/engine/rem/replace-clause-verdict.ts
1739
+ function buildReplaceEligibleClauses(older, newer) {
1740
+ return [...splitExactClauses(older).map((clause) => ({
1741
+ origin: "older",
1742
+ ...clause
1743
+ })), ...splitExactClauses(newer).map((clause) => ({
1744
+ origin: "newer",
1745
+ ...clause
1746
+ }))];
1747
+ }
1748
+ function renderReplaceClauseVerdictPrompt(clauses) {
1749
+ return [
1750
+ "Judge whether the newer memory replaces the older memory.",
1751
+ "replacement: the newer memory supersedes the older memory, so one or more older clauses should retire.",
1752
+ "keep: the two memories should remain independently active because they are compatible, describe different facts, or the newer memory does not establish a replacement.",
1753
+ "uncertain: you cannot determine whether the newer memory replaces the older memory from the supplied content; take no action.",
1754
+ "Return JSON only: {\"verdict\":\"replacement|keep|uncertain\",\"retiring_clause_indices\":[0]}.",
1755
+ "Use only the zero-based indices in the supplied eligible clauses.",
1756
+ "A retiring clause must originate from the older memory. Do not produce text or explanations.",
1757
+ `Eligible clauses: ${JSON.stringify(clauses)}`
1758
+ ].join("\n\n");
1759
+ }
1760
+ function parseReplaceClauseVerdict(response, clauses) {
1761
+ if (!isRecord(response)) return invalidVerdict();
1762
+ if (!hasOnlyKeys(response, ["verdict", "retiring_clause_indices"])) return invalidVerdict();
1763
+ const verdict = response["verdict"];
1764
+ const indices = response["retiring_clause_indices"];
1765
+ if (verdict !== "replacement" && verdict !== "keep" && verdict !== "uncertain" || !Array.isArray(indices) || !indices.every((index) => Number.isInteger(index) && index >= 0 && index < clauses.length) || new Set(indices).size !== indices.length || indices.some((index) => clauses[index]?.origin !== "older")) return invalidVerdict();
1766
+ return {
1767
+ parseState: "valid",
1768
+ verdict,
1769
+ retiringClauseIndices: indices
1770
+ };
1771
+ }
1772
+ function arbitrateReplaceVerdicts(pairVerdict, clauseVerdict) {
1773
+ if (pairVerdict !== "replacement") return { outcome: "no-action" };
1774
+ if (clauseVerdict.parseState === "invalid") return {
1775
+ outcome: "refused",
1776
+ reason: "clause_parse_failed"
1777
+ };
1778
+ if (clauseVerdict.retiringClauseIndices.length === 0) return {
1779
+ outcome: "refused",
1780
+ reason: "empty_retiring_set"
1781
+ };
1782
+ return {
1783
+ outcome: "proceed",
1784
+ retiringClauseIndices: clauseVerdict.retiringClauseIndices
1785
+ };
1786
+ }
1787
+ function invalidVerdict() {
1788
+ return {
1789
+ parseState: "invalid",
1790
+ verdict: "uncertain",
1791
+ retiringClauseIndices: []
1792
+ };
1793
+ }
1794
+ function isRecord(value) {
1795
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1796
+ }
1797
+ function hasOnlyKeys(value, expected) {
1798
+ return Object.keys(value).length === expected.length && expected.every((key) => key in value);
1799
+ }
1800
+ //#endregion
1801
+ //#region src/engine/rem/replace-coverage.ts
1802
+ const replaceCoverageSchema = z.object({ atoms: z.array(z.object({
1803
+ clause_index: z.number().int().nonnegative(),
1804
+ class: z.enum([
1805
+ "current-fact",
1806
+ "retired-fact",
1807
+ "event-fact"
1808
+ ]),
1809
+ status: z.enum([
1810
+ "covered",
1811
+ "uncovered",
1812
+ "undetermined"
1813
+ ])
1814
+ }).strict()) }).strict();
1815
+ function renderReplaceCoveragePrompt(input) {
1816
+ const olderClauses = buildReplaceEligibleClauses(input.older, input.newer).filter((clause) => clause.origin === "older");
1817
+ return [
1818
+ "Classify whether every older-memory clause remains covered after replacement.",
1819
+ "Return JSON only: {\"atoms\":[{\"clause_index\":0,\"class\":\"current-fact|retired-fact|event-fact\",\"status\":\"covered|uncovered|undetermined\"}]}.",
1820
+ "Return exactly one atom for each older clause. Prefer undetermined over guessing.",
1821
+ "Covered means the newer memory carries the older clause's information. A newer value for the same fact counts as covered: the older value was replaced, not lost.",
1822
+ "Uncovered means the older clause also holds a separate fact that the newer memory neither updates nor restates, so closing the older row would lose it.",
1823
+ "Class describes what kind of assertion the older clause makes, independently of Retiring older-clause indices. Those indices do not determine class or status and do not excuse information loss; evaluate every older clause.",
1824
+ `Older clauses: ${JSON.stringify(olderClauses)}`,
1825
+ `Newer memory: ${JSON.stringify(input.newer)}`,
1826
+ `Retiring older-clause indices: ${JSON.stringify(input.retiringClauseIndices)}`
1827
+ ].join("\n\n");
1828
+ }
1829
+ function parseReplaceCoverageAtoms(value) {
1830
+ const parsed = replaceCoverageSchema.safeParse(value);
1831
+ if (!parsed.success) return void 0;
1832
+ return parsed.data.atoms.map((atom) => ({
1833
+ clauseIndex: atom.clause_index,
1834
+ class: atom.class,
1835
+ status: atom.status
1836
+ }));
1837
+ }
1838
+ /** Validates model-classified coverage against the code-owned older-clause reference set. */
1839
+ /**
1840
+ * Every refusal raised after the observation list exists carries what was seen up to that point.
1841
+ * Owner ruling 2026-08-13: the count is of conditions detected. Refusing on one condition while
1842
+ * silently dropping another the same pair exhibited is how a deletion stays unmeasurable.
1843
+ */
1844
+ function refuseWith(reason, atoms, observations, detail) {
1845
+ return {
1846
+ decision: "refuse",
1847
+ reason,
1848
+ atoms,
1849
+ ...detail === void 0 ? {} : { detail },
1850
+ ...observations.length === 0 ? {} : { observations: [...observations] }
1851
+ };
1852
+ }
1853
+ function decideReplaceCoverage(input) {
1854
+ const atoms = input.atoms.map((atom) => ({ ...atom }));
1855
+ if (input.older.trim().length === 0) return {
1856
+ decision: "refuse",
1857
+ reason: "empty_older_clause_set",
1858
+ atoms
1859
+ };
1860
+ const olderClauses = buildReplaceEligibleClauses(input.older, input.newer).filter((clause) => clause.origin === "older");
1861
+ if (olderClauses.length === 0) return {
1862
+ decision: "refuse",
1863
+ reason: "empty_older_clause_set",
1864
+ atoms
1865
+ };
1866
+ const observations = [];
1867
+ if (atoms.length !== olderClauses.length || new Set(atoms.map((atom) => atom.clauseIndex)).size !== atoms.length || atoms.some((atom) => atom.clauseIndex < 0 || atom.clauseIndex >= olderClauses.length)) observations.push({
1868
+ code: "incomplete_clause_accounting",
1869
+ detail: `atoms=${atoms.length} clauses=${olderClauses.length}`
1870
+ });
1871
+ const retiring = new Set(input.retiringClauseIndices);
1872
+ for (const atom of atoms) {
1873
+ if (atom.status !== "covered") return refuseWith(`atom_${atom.status}`, atoms, observations);
1874
+ if (atom.class === "retired-fact" && !retiring.has(atom.clauseIndex)) observations.push({
1875
+ code: "retiring_side_mismatch",
1876
+ detail: `clause_index=${atom.clauseIndex}`
1877
+ });
1878
+ if (atom.class === "retired-fact" && input.rawCandidateEvidence !== void 0) {
1879
+ const clause = olderClauses[atom.clauseIndex];
1880
+ if (clause !== void 0 && !input.rawCandidateEvidence.includes(clause.value)) {
1881
+ atom.status = "undetermined";
1882
+ return refuseWith("retired_fact_not_in_evidence", atoms, observations);
1883
+ }
1884
+ }
1885
+ }
1886
+ const certified = new Set(atoms.filter((atom) => atom.status === "covered").map((atom) => atom.clauseIndex));
1887
+ const uncertifiedClauses = olderClauses.map((clause, index) => certified.has(index) || retiring.has(index) ? void 0 : clause.value).filter((value) => value !== void 0);
1888
+ if (atoms.some((atom) => atom.class === "event-fact")) {
1889
+ if (input.carrier === void 0) return refuseWith("event_retrievability_port_missing", atoms, observations);
1890
+ return decideEventFacts(input.carrier, atoms, observations, uncertifiedClauses);
1891
+ }
1892
+ return {
1893
+ decision: "allow",
1894
+ atoms,
1895
+ ...observations.length === 0 ? {} : { observations },
1896
+ ...uncertifiedClauses.length === 0 ? {} : { uncertifiedClauses }
1897
+ };
1898
+ }
1899
+ async function decideEventFacts(carrier, atoms, observations, uncertifiedClauses) {
1900
+ const state = await carrier.carrierState();
1901
+ if (!state.retained) return refuseWith("event_not_retrievable", atoms, observations, state.fault);
1902
+ return {
1903
+ decision: "allow",
1904
+ atoms,
1905
+ ...observations.length === 0 ? {} : { observations: [...observations] },
1906
+ ...uncertifiedClauses.length === 0 ? {} : { uncertifiedClauses: [...uncertifiedClauses] }
1907
+ };
1908
+ }
1909
+ //#endregion
1910
+ //#region src/engine/rem/rem-update-judgment.ts
1911
+ const judgmentSchema = z.object({
1912
+ proposed_current: z.string(),
1913
+ retired_values: z.array(z.string())
1914
+ }).strict();
1915
+ const relationJudgmentSchema = z.object({
1916
+ supersedes: z.boolean(),
1917
+ retires_anything: z.boolean(),
1918
+ supersedes_everything: z.boolean()
1919
+ }).strict();
1920
+ const retirementTargetSchema = z.object({ target_row_ids: z.array(z.string().min(1)) }).strict();
1921
+ const verificationSchema = z.object({
1922
+ faithful: z.boolean(),
1923
+ retired_absent: z.boolean(),
1924
+ all_facts_accounted: z.boolean()
1925
+ }).strict();
1926
+ function renderRemUpdateJudgmentPrompt(input) {
1927
+ return [
1928
+ input.judgmentSkill,
1929
+ "Task: REM source rewrite.",
1930
+ "Return exactly one JSON object and no prose: {\"proposed_current\":\"\",\"retired_values\":[]}.",
1931
+ "Use exactly those two fields and no additional fields.",
1932
+ "proposed_current must be a string.",
1933
+ "retired_values must be an array of strings.",
1934
+ `Source row: ${JSON.stringify(input.source)}`,
1935
+ ...input.enumeratedMembers === void 0 ? [] : [`Code-enumerated members: ${JSON.stringify(input.enumeratedMembers)}`],
1936
+ ...input.supersedingText === void 0 ? [] : [`Newer record that supersedes part of this row: ${JSON.stringify(input.supersedingText)}`]
1937
+ ].join("\n\n");
1938
+ }
1939
+ function renderRemUpdateRelationJudgmentPrompt(input) {
1940
+ return [
1941
+ input.judgmentSkill,
1942
+ "Task: REM relation judgment.",
1943
+ "Return exactly one JSON object and no prose: {\"supersedes\":false,\"retires_anything\":false,\"supersedes_everything\":false}.",
1944
+ "Use exactly those three fields and no additional fields.",
1945
+ "supersedes must be a boolean.",
1946
+ "retires_anything must be a boolean.",
1947
+ "supersedes_everything must be a boolean.",
1948
+ `Record under judgment: ${JSON.stringify(input.rowText)}`,
1949
+ `Older records: ${JSON.stringify(input.predecessorTexts)}`,
1950
+ `Other related records: ${JSON.stringify(input.successorTexts)}`
1951
+ ].join("\n\n");
1952
+ }
1953
+ function renderRemRetirementTargetPrompt(input) {
1954
+ return [
1955
+ input.judgmentSkill,
1956
+ "Task: REM retirement target judgment.",
1957
+ "Return exactly one JSON object and no prose: {\"target_row_ids\":[]}.",
1958
+ "Use exactly that field and no additional fields.",
1959
+ "target_row_ids must contain only ids from Candidate rows and must not contain duplicates.",
1960
+ `Nominated row: ${JSON.stringify(input.nominatedRow)}`,
1961
+ `Candidate rows: ${JSON.stringify(input.candidateRows)}`
1962
+ ].join("\n\n");
1963
+ }
1964
+ function renderRemUpdateVerificationPrompt(input) {
1965
+ return [
1966
+ input.judgmentSkill,
1967
+ "Task: REM rewrite verification.",
1968
+ "Return exactly one JSON object and no prose: {\"faithful\":false,\"retired_absent\":false,\"all_facts_accounted\":false}.",
1969
+ "Use exactly those three fields and no additional fields.",
1970
+ ...input.retractionText === void 0 ? [] : ["Judge whether the proposed rewrite follows from the relation between the original row and the retraction row."],
1971
+ `Original row: ${JSON.stringify(input.source)}`,
1972
+ ...input.supersedingText === void 0 ? [] : [`Newer record: ${JSON.stringify(input.supersedingText)}`],
1973
+ ...input.retractionText === void 0 ? [] : [`Retraction row: ${JSON.stringify(input.retractionText)}`],
1974
+ `Proposed rewrite: ${JSON.stringify(input.proposedCurrent)}`,
1975
+ `Retired values: ${JSON.stringify(input.retiredValues)}`
1976
+ ].join("\n\n");
1977
+ }
1978
+ function renderRemClauseCarryPrompt(input) {
1979
+ return [
1980
+ input.judgmentSkill,
1981
+ "Task: REM clause carry judgment.",
1982
+ "Return exactly one JSON object and no prose: {\"already_current\":[true,false]}.",
1983
+ "Use exactly that field and no additional fields.",
1984
+ "already_current must contain exactly one boolean per numbered clause, in the same order.",
1985
+ `Survivor text: ${JSON.stringify(input.survivorText)}`,
1986
+ ...input.clauses.map((clause, index) => `Clause ${index + 1}: ${JSON.stringify(clause)}`)
1987
+ ].join("\n\n");
1988
+ }
1989
+ function decideRemUpdateRelationFromReply(reply) {
1990
+ const json = extractJsonFromResponse(reply);
1991
+ if (json === null) return {
1992
+ outcome: "refuse",
1993
+ reason: "model_response_invalid"
1994
+ };
1995
+ let value;
1996
+ try {
1997
+ value = JSON.parse(json);
1998
+ } catch {
1999
+ return {
2000
+ outcome: "refuse",
2001
+ reason: "model_response_invalid"
2002
+ };
2003
+ }
2004
+ const parsed = relationJudgmentSchema.safeParse(value);
2005
+ if (!parsed.success) return {
2006
+ outcome: "refuse",
2007
+ reason: "model_response_invalid"
2008
+ };
2009
+ if (!parsed.data.supersedes || !parsed.data.retires_anything) return {
2010
+ outcome: "refuse",
2011
+ reason: "no_retired_fact"
2012
+ };
2013
+ return {
2014
+ outcome: "allow",
2015
+ supersedesEverything: parsed.data.supersedes_everything
2016
+ };
2017
+ }
2018
+ function decideRemRetirementTargetFromReply(reply, offeredRowIds) {
2019
+ const json = extractJsonFromResponse(reply);
2020
+ if (json === null) return {
2021
+ outcome: "refuse",
2022
+ reason: "model_response_invalid"
2023
+ };
2024
+ let value;
2025
+ try {
2026
+ value = JSON.parse(json);
2027
+ } catch {
2028
+ return {
2029
+ outcome: "refuse",
2030
+ reason: "model_response_invalid"
2031
+ };
2032
+ }
2033
+ const parsed = retirementTargetSchema.safeParse(value);
2034
+ if (!parsed.success) return {
2035
+ outcome: "refuse",
2036
+ reason: "model_response_invalid"
2037
+ };
2038
+ const targetRowIds = new Set(parsed.data.target_row_ids);
2039
+ if (targetRowIds.size !== parsed.data.target_row_ids.length) return {
2040
+ outcome: "refuse",
2041
+ reason: "model_response_invalid"
2042
+ };
2043
+ if ([...targetRowIds].some((rowId) => !offeredRowIds.has(rowId))) return {
2044
+ outcome: "refuse",
2045
+ reason: "row_id_not_offered"
2046
+ };
2047
+ return {
2048
+ outcome: "allow",
2049
+ targetRowIds: [...targetRowIds]
2050
+ };
2051
+ }
2052
+ function decideRemUpdateFromReply(reply) {
2053
+ const parsed = parseJudgment(reply);
2054
+ if (parsed === void 0) return {
2055
+ outcome: "refuse",
2056
+ reason: "model_response_invalid"
2057
+ };
2058
+ if (parsed.retiredValues.length === 0) return {
2059
+ outcome: "refuse",
2060
+ reason: "no_retired_fact"
2061
+ };
2062
+ if (parsed.proposedCurrent.trim().length === 0) return {
2063
+ outcome: "refuse",
2064
+ reason: "no_surviving_remainder"
2065
+ };
2066
+ return {
2067
+ outcome: "verify",
2068
+ ...parsed
2069
+ };
2070
+ }
2071
+ function decideRemUpdateVerification(reply) {
2072
+ const json = extractJsonFromResponse(reply);
2073
+ if (json === null) return {
2074
+ outcome: "refuse",
2075
+ reason: "model_response_invalid"
2076
+ };
2077
+ let value;
2078
+ try {
2079
+ value = JSON.parse(json);
2080
+ } catch {
2081
+ return {
2082
+ outcome: "refuse",
2083
+ reason: "model_response_invalid"
2084
+ };
2085
+ }
2086
+ const parsed = verificationSchema.safeParse(value);
2087
+ if (!parsed.success) return {
2088
+ outcome: "refuse",
2089
+ reason: "model_response_invalid"
2090
+ };
2091
+ if (!parsed.data.faithful) return {
2092
+ outcome: "refuse",
2093
+ reason: "proposal_not_faithful"
2094
+ };
2095
+ if (!parsed.data.retired_absent) return {
2096
+ outcome: "refuse",
2097
+ reason: "retired_value_survives"
2098
+ };
2099
+ if (!parsed.data.all_facts_accounted) return {
2100
+ outcome: "refuse",
2101
+ reason: "surviving_fact_lost"
2102
+ };
2103
+ return { outcome: "apply" };
2104
+ }
2105
+ function decideRemClauseCarry(reply, clauseCount) {
2106
+ if (!Number.isSafeInteger(clauseCount) || clauseCount < 0) return {
2107
+ outcome: "refuse",
2108
+ reason: "model_response_invalid"
2109
+ };
2110
+ const json = extractJsonFromResponse(reply);
2111
+ if (json === null) return {
2112
+ outcome: "refuse",
2113
+ reason: "model_response_invalid"
2114
+ };
2115
+ let value;
2116
+ try {
2117
+ value = JSON.parse(json);
2118
+ } catch {
2119
+ return {
2120
+ outcome: "refuse",
2121
+ reason: "model_response_invalid"
2122
+ };
2123
+ }
2124
+ const parsed = z.object({ already_current: z.array(z.boolean()).length(clauseCount) }).strict().safeParse(value);
2125
+ if (!parsed.success) return {
2126
+ outcome: "refuse",
2127
+ reason: "model_response_invalid"
2128
+ };
2129
+ return {
2130
+ outcome: "decided",
2131
+ alreadyCurrent: parsed.data.already_current
2132
+ };
2133
+ }
2134
+ function parseJudgment(reply) {
2135
+ const json = extractJsonFromResponse(reply);
2136
+ if (json === null) return void 0;
2137
+ let value;
2138
+ try {
2139
+ value = JSON.parse(json);
2140
+ } catch {
2141
+ return;
2142
+ }
2143
+ const parsed = judgmentSchema.safeParse(value);
2144
+ if (!parsed.success) return void 0;
2145
+ return {
2146
+ proposedCurrent: parsed.data.proposed_current,
2147
+ retiredValues: parsed.data.retired_values
2148
+ };
2149
+ }
2150
+ //#endregion
2151
+ //#region src/engine/rem/runner.ts
2152
+ async function runRemStages(input) {
2153
+ const results = {};
2154
+ for (const stage of input.stages) {
2155
+ let entry;
2156
+ try {
2157
+ const observed = await stage.run();
2158
+ results[stage.name] = "done";
2159
+ entry = {
2160
+ ...journal(stage.name, "done"),
2161
+ ...observed
2162
+ };
2163
+ } catch (error) {
2164
+ const reason = error instanceof Error ? error.message : String(error);
2165
+ input.onStageError?.({
2166
+ stage: stage.name,
2167
+ error
2168
+ });
2169
+ results[stage.name] = "failed";
2170
+ entry = {
2171
+ ...journal(stage.name, "failed"),
2172
+ reason
2173
+ };
2174
+ }
2175
+ try {
2176
+ await input.repository.appendJournal(input.jobId, input.jobType, entry);
2177
+ } catch (error) {
2178
+ createLogger("sno-station-mem:rem-runner").error("rem.stage.journal.failed", {
2179
+ error,
2180
+ stage: stage.name
2181
+ }, {
2182
+ event_name: "rem.stage.journal.failed",
2183
+ file: "packages/memory/src/engine/rem/runner.ts",
2184
+ function: "runRemStages",
2185
+ site_id: "rem.stage.journal.failed"
2186
+ });
2187
+ }
2188
+ }
2189
+ return results;
2190
+ }
2191
+ function journal(stage, outcome) {
2192
+ return {
2193
+ stage,
2194
+ outcome,
2195
+ pairsScanned: 0,
2196
+ verdicts: 0,
2197
+ actionsApplied: 0
2198
+ };
2199
+ }
2200
+ //#endregion
2201
+ //#region src/engine/rem/schema.ts
2202
+ const REM_SCHEMA_SQL = `
2203
+ CREATE TABLE IF NOT EXISTS nodix_rem_relation_ledger (
2204
+ row_id TEXT PRIMARY KEY,
2205
+ content_hash TEXT NOT NULL,
2206
+ state TEXT NOT NULL CHECK (state IN ('transition', 'stale-current', 'pure-negation', 'ambiguous')),
2207
+ owner TEXT NOT NULL DEFAULT 'none' CHECK (owner IN ('restate', 'verdict', 'none')),
2208
+ claim_ts TEXT,
2209
+ classified_at TEXT NOT NULL
2210
+ );
2211
+
2212
+ CREATE TABLE IF NOT EXISTS nodix_rem_row_claims (
2213
+ row_id TEXT PRIMARY KEY,
2214
+ content_hash TEXT NOT NULL,
2215
+ owner TEXT NOT NULL CHECK (owner IN ('restate', 'verdict')),
2216
+ claim_token TEXT NOT NULL,
2217
+ holder_pid INTEGER NOT NULL CHECK (holder_pid > 0),
2218
+ holder_process_start TEXT NOT NULL,
2219
+ holder_boot_id TEXT NOT NULL,
2220
+ state TEXT NOT NULL DEFAULT 'active' CHECK (state IN ('active', 'completed')),
2221
+ claimed_at TEXT NOT NULL,
2222
+ completed_at TEXT,
2223
+ FOREIGN KEY (row_id) REFERENCES nodix_rem_relation_ledger(row_id)
2224
+ );
2225
+
2226
+ CREATE TABLE IF NOT EXISTS nodix_rem_scan_generations (
2227
+ generation_id TEXT PRIMARY KEY,
2228
+ corpus_snapshot_hash TEXT NOT NULL,
2229
+ pairing_config_hash TEXT NOT NULL,
2230
+ max_llm_calls INTEGER NOT NULL CHECK (max_llm_calls >= 0),
2231
+ max_tokens INTEGER NOT NULL CHECK (max_tokens >= 0),
2232
+ llm_calls_used INTEGER NOT NULL DEFAULT 0,
2233
+ tokens_used INTEGER NOT NULL DEFAULT 0,
2234
+ pending_stages_json TEXT NOT NULL DEFAULT '[]'
2235
+ );
2236
+
2237
+ CREATE TABLE IF NOT EXISTS nodix_rem_scan_pairs (
2238
+ generation_id TEXT NOT NULL,
2239
+ pair_id TEXT NOT NULL,
2240
+ left_row_id TEXT NOT NULL,
2241
+ right_row_id TEXT NOT NULL,
2242
+ sort_key TEXT NOT NULL,
2243
+ claim_state TEXT NOT NULL DEFAULT 'unvisited'
2244
+ CHECK (claim_state IN ('unvisited', 'claimed', 'done')),
2245
+ invocation_id TEXT,
2246
+ claimed_at TEXT,
2247
+ checkpoint TEXT,
2248
+ verdict TEXT,
2249
+ actions_applied INTEGER NOT NULL DEFAULT 0 CHECK (actions_applied IN (0, 1)),
2250
+ budget_reserved INTEGER NOT NULL DEFAULT 0 CHECK (budget_reserved IN (0, 1)),
2251
+ budget_invocation_id TEXT,
2252
+ reserved_tokens INTEGER NOT NULL DEFAULT 0 CHECK (reserved_tokens >= 0),
2253
+ attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
2254
+ progress_state TEXT NOT NULL DEFAULT 'pending'
2255
+ CHECK (progress_state IN ('pending', 'refused', 'closed', 'exhausted')),
2256
+ refusal_reason TEXT,
2257
+ inherited_from_generation_id TEXT,
2258
+ PRIMARY KEY (generation_id, pair_id),
2259
+ FOREIGN KEY (generation_id) REFERENCES nodix_rem_scan_generations(generation_id)
2260
+ );
2261
+ CREATE INDEX IF NOT EXISTS rem_scan_pairs_cursor
2262
+ ON nodix_rem_scan_pairs(generation_id, claim_state, sort_key, pair_id);
2263
+
2264
+ CREATE TABLE IF NOT EXISTS nodix_rem_scan_invocations (
2265
+ generation_id TEXT NOT NULL,
2266
+ invocation_id TEXT NOT NULL,
2267
+ llm_calls_used INTEGER NOT NULL DEFAULT 0,
2268
+ tokens_used INTEGER NOT NULL DEFAULT 0,
2269
+ created_at TEXT NOT NULL,
2270
+ PRIMARY KEY (generation_id, invocation_id),
2271
+ FOREIGN KEY (generation_id) REFERENCES nodix_rem_scan_generations(generation_id)
2272
+ );
2273
+
2274
+ CREATE TABLE IF NOT EXISTS nodix_rem_pair_stage_budgets (
2275
+ generation_id TEXT NOT NULL,
2276
+ pair_id TEXT NOT NULL,
2277
+ stage TEXT NOT NULL,
2278
+ invocation_id TEXT NOT NULL,
2279
+ reserved_tokens INTEGER NOT NULL CHECK (reserved_tokens >= 0),
2280
+ idempotency_key TEXT NOT NULL,
2281
+ PRIMARY KEY (generation_id, pair_id, stage),
2282
+ FOREIGN KEY (generation_id, pair_id) REFERENCES nodix_rem_scan_pairs(generation_id, pair_id),
2283
+ FOREIGN KEY (generation_id, invocation_id) REFERENCES nodix_rem_scan_invocations(generation_id, invocation_id)
2284
+ );
2285
+
2286
+ CREATE TABLE IF NOT EXISTS nodix_rem_pair_claims (
2287
+ generation_id TEXT NOT NULL,
2288
+ pair_id TEXT NOT NULL,
2289
+ claim_token TEXT NOT NULL,
2290
+ holder_pid INTEGER NOT NULL CHECK (holder_pid > 0),
2291
+ holder_process_start TEXT NOT NULL,
2292
+ holder_boot_id TEXT NOT NULL,
2293
+ state TEXT NOT NULL DEFAULT 'active' CHECK (state IN ('active', 'completed')),
2294
+ claimed_at TEXT NOT NULL,
2295
+ completed_at TEXT,
2296
+ PRIMARY KEY (generation_id, pair_id),
2297
+ FOREIGN KEY (generation_id, pair_id) REFERENCES nodix_rem_scan_pairs(generation_id, pair_id)
2298
+ );
2299
+
2300
+ CREATE TABLE IF NOT EXISTS nodix_rem_journal (
2301
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
2302
+ job_id TEXT NOT NULL,
2303
+ job_type TEXT NOT NULL,
2304
+ stage TEXT NOT NULL,
2305
+ attempt_id TEXT,
2306
+ outcome TEXT NOT NULL CHECK (outcome IN ('done', 'failed', 'disabled', 'pending', 'refused', 'no-action')),
2307
+ row_id TEXT,
2308
+ pair_id TEXT,
2309
+ pairs_scanned INTEGER NOT NULL DEFAULT 0,
2310
+ verdicts INTEGER NOT NULL DEFAULT 0,
2311
+ actions_applied INTEGER NOT NULL DEFAULT 0,
2312
+ reason TEXT,
2313
+ detail TEXT,
2314
+ CHECK (outcome NOT IN ('failed', 'refused') OR length(trim(reason)) > 0)
2315
+ );
2316
+ CREATE INDEX IF NOT EXISTS rem_journal_job_sequence ON nodix_rem_journal(job_id, sequence);
2317
+
2318
+ CREATE TABLE IF NOT EXISTS nodix_rem_recovery_history (
2319
+ recovery_handle TEXT PRIMARY KEY,
2320
+ row_id TEXT NOT NULL,
2321
+ operation_kind TEXT NOT NULL CHECK (operation_kind IN ('lane', 'text-version', 'mark')),
2322
+ prior_row_image TEXT NOT NULL,
2323
+ prior_content_hash TEXT NOT NULL,
2324
+ expected_post_hash TEXT NOT NULL,
2325
+ row_hash_version INTEGER NOT NULL DEFAULT 1,
2326
+ reason TEXT NOT NULL CHECK (length(trim(reason)) > 0),
2327
+ mutation_ts TEXT NOT NULL,
2328
+ restored_at TEXT
2329
+ );
2330
+ CREATE INDEX IF NOT EXISTS rem_recovery_history_row ON nodix_rem_recovery_history(row_id);
2331
+
2332
+ CREATE TABLE IF NOT EXISTS nodix_rem_memory_facets (
2333
+ memory_id TEXT NOT NULL,
2334
+ facet TEXT NOT NULL CHECK (facet IN ('current', 'history')),
2335
+ text TEXT NOT NULL,
2336
+ updated_at_ms INTEGER NOT NULL,
2337
+ PRIMARY KEY (memory_id, facet),
2338
+ FOREIGN KEY (memory_id) REFERENCES nodix_memories(id) ON DELETE CASCADE
2339
+ );
2340
+ CREATE INDEX IF NOT EXISTS rem_memory_facets_by_facet
2341
+ ON nodix_rem_memory_facets(facet, memory_id);
2342
+
2343
+ CREATE TRIGGER IF NOT EXISTS rem_memory_facets_after_insert
2344
+ AFTER INSERT ON nodix_memories
2345
+ BEGIN
2346
+ INSERT INTO nodix_rem_memory_facets(memory_id, facet, text, updated_at_ms)
2347
+ VALUES (NEW.id, 'current', NEW.text, NEW.timestamp);
2348
+ END;
2349
+
2350
+ CREATE TRIGGER IF NOT EXISTS rem_memory_facets_after_text_update
2351
+ AFTER UPDATE OF text ON nodix_memories
2352
+ BEGIN
2353
+ INSERT INTO nodix_rem_memory_facets(memory_id, facet, text, updated_at_ms)
2354
+ VALUES (NEW.id, 'current', NEW.text, NEW.timestamp)
2355
+ ON CONFLICT(memory_id, facet) DO UPDATE SET
2356
+ text = excluded.text, updated_at_ms = excluded.updated_at_ms;
2357
+ END;
2358
+
2359
+ CREATE TABLE IF NOT EXISTS nodix_rem_facet_recovery (
2360
+ recovery_handle TEXT PRIMARY KEY,
2361
+ prior_facets_json TEXT NOT NULL,
2362
+ prior_chunk_facets_json TEXT NOT NULL,
2363
+ expected_facets_json TEXT NOT NULL,
2364
+ expected_chunk_facets_json TEXT NOT NULL,
2365
+ FOREIGN KEY (recovery_handle) REFERENCES nodix_rem_recovery_history(recovery_handle)
2366
+ );
2367
+
2368
+ CREATE TABLE IF NOT EXISTS nodix_rem_write_verdicts (
2369
+ evidence_id TEXT PRIMARY KEY,
2370
+ winner_row_id TEXT NOT NULL,
2371
+ loser_row_id TEXT NOT NULL,
2372
+ target_row_id TEXT NOT NULL,
2373
+ retired_fact_atoms_json TEXT NOT NULL,
2374
+ recorded_at TEXT NOT NULL
2375
+ );
2376
+
2377
+ CREATE TABLE IF NOT EXISTS nodix_rem_write_attempts (
2378
+ attempt_id TEXT PRIMARY KEY,
2379
+ job_id TEXT NOT NULL,
2380
+ stage TEXT NOT NULL,
2381
+ row_id TEXT NOT NULL,
2382
+ writer TEXT NOT NULL,
2383
+ attempt_ordinal INTEGER NOT NULL CHECK (attempt_ordinal > 0),
2384
+ outcome TEXT NOT NULL CHECK (outcome IN ('pending', 'succeeded', 'failed', 'refused', 'degraded')),
2385
+ reason_code TEXT,
2386
+ pre_write_content_sha256 TEXT NOT NULL,
2387
+ proposed_text_sha256 TEXT NOT NULL,
2388
+ evidence_id TEXT NOT NULL,
2389
+ configuration_sha256 TEXT NOT NULL,
2390
+ expected_post_content_sha256 TEXT,
2391
+ post_write_content_sha256 TEXT,
2392
+ opened_at TEXT NOT NULL,
2393
+ closed_at TEXT
2394
+ );
2395
+ CREATE INDEX IF NOT EXISTS rem_write_attempts_outcome ON nodix_rem_write_attempts(outcome, opened_at);
2396
+
2397
+ CREATE TABLE IF NOT EXISTS nodix_rem_census_rows (
2398
+ row_id TEXT PRIMARY KEY,
2399
+ write_identity_sha256 TEXT,
2400
+ candidate_set_sha256 TEXT,
2401
+ generation_id TEXT,
2402
+ corpus_sha256 TEXT,
2403
+ embedder_sha256 TEXT,
2404
+ configuration_sha256 TEXT
2405
+ );
2406
+
2407
+ CREATE TABLE IF NOT EXISTS nodix_rem_generation_transitions (
2408
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
2409
+ outgoing_generation_id TEXT NOT NULL,
2410
+ incoming_generation_id TEXT NOT NULL,
2411
+ recorded_at TEXT NOT NULL
2412
+ );
2413
+
2414
+ CREATE TABLE IF NOT EXISTS nodix_rem_batch_summaries (
2415
+ batch_id TEXT PRIMARY KEY,
2416
+ generation_id TEXT UNIQUE,
2417
+ calls INTEGER NOT NULL DEFAULT 0,
2418
+ tokens INTEGER NOT NULL DEFAULT 0,
2419
+ pairCount INTEGER NOT NULL DEFAULT 0,
2420
+ closeCount INTEGER NOT NULL DEFAULT 0,
2421
+ refusalCount INTEGER NOT NULL DEFAULT 0,
2422
+ verdict_distribution_json TEXT NOT NULL DEFAULT '{}',
2423
+ pre_truncation_count INTEGER,
2424
+ emitted_count INTEGER
2425
+ );
2426
+
2427
+ CREATE TABLE IF NOT EXISTS nodix_rem_verdict_observations (
2428
+ pair_id TEXT PRIMARY KEY,
2429
+ audit_kind TEXT NOT NULL CHECK (audit_kind IN ('VERDICT', 'SAMPLE')),
2430
+ persisted_verdict TEXT,
2431
+ raw_value TEXT NOT NULL
2432
+ );
2433
+ `;
2434
+ function installRemSchema(database) {
2435
+ database.exec(REM_SCHEMA_SQL);
2436
+ ensureAmbiguousRowState(database);
2437
+ ensureHolderBootIdColumn(database, "nodix_rem_row_claims");
2438
+ ensureHolderBootIdColumn(database, "nodix_rem_pair_claims");
2439
+ ensureJournalNullableColumn(database, "row_id");
2440
+ ensureJournalNullableColumn(database, "pair_id");
2441
+ ensureJournalNullableColumn(database, "attempt_id");
2442
+ ensureJournalNullableColumn(database, "detail");
2443
+ database.exec(`
2444
+ CREATE UNIQUE INDEX IF NOT EXISTS rem_journal_external_decision_attempt
2445
+ ON nodix_rem_journal(attempt_id)
2446
+ WHERE attempt_id IS NOT NULL AND stage IN ('arbitration', 'coverage');
2447
+ `);
2448
+ ensureRemChunkFacet(database);
2449
+ backfillRemCurrentFacets(database);
2450
+ }
2451
+ function ensureAmbiguousRowState(database) {
2452
+ if (database.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'nodix_rem_relation_ledger'").get()?.sql?.includes("'ambiguous'")) return;
2453
+ database.exec("PRAGMA foreign_keys = OFF");
2454
+ try {
2455
+ database.transaction(() => {
2456
+ database.exec(`
2457
+ CREATE TABLE nodix_rem_relation_ledger_next (
2458
+ row_id TEXT PRIMARY KEY,
2459
+ content_hash TEXT NOT NULL,
2460
+ state TEXT NOT NULL CHECK (state IN ('transition', 'stale-current', 'pure-negation', 'ambiguous')),
2461
+ owner TEXT NOT NULL DEFAULT 'none' CHECK (owner IN ('restate', 'verdict', 'none')),
2462
+ claim_ts TEXT,
2463
+ classified_at TEXT NOT NULL
2464
+ );
2465
+ INSERT INTO nodix_rem_relation_ledger_next(
2466
+ row_id, content_hash, state, owner, claim_ts, classified_at
2467
+ ) SELECT row_id, content_hash, state, owner, claim_ts, classified_at
2468
+ FROM nodix_rem_relation_ledger;
2469
+ DROP TABLE nodix_rem_relation_ledger;
2470
+ ALTER TABLE nodix_rem_relation_ledger_next RENAME TO nodix_rem_relation_ledger;
2471
+ `);
2472
+ }).immediate();
2473
+ } finally {
2474
+ database.exec("PRAGMA foreign_keys = ON");
2475
+ }
2476
+ }
2477
+ function ensureRemChunkFacet(database) {
2478
+ if (!tableExists(database, "nodix_memory_chunks")) return;
2479
+ if (!database.prepare("PRAGMA table_info(nodix_memory_chunks)").all().some(({ name }) => name === "facet")) database.exec("ALTER TABLE nodix_memory_chunks ADD COLUMN facet TEXT NOT NULL DEFAULT 'current' CHECK (facet IN ('current', 'history'))");
2480
+ database.exec(`
2481
+ DROP INDEX IF EXISTS nodix_idx_memory_chunks_memory;
2482
+ CREATE UNIQUE INDEX IF NOT EXISTS nodix_idx_memory_chunks_memory_facet
2483
+ ON nodix_memory_chunks(memory_id, facet, chunk_index);
2484
+ `);
2485
+ }
2486
+ function backfillRemCurrentFacets(database) {
2487
+ if (!tableExists(database, "nodix_memories")) return;
2488
+ database.exec(`
2489
+ INSERT OR IGNORE INTO nodix_rem_memory_facets(memory_id, facet, text, updated_at_ms)
2490
+ SELECT id, 'current', text, timestamp FROM nodix_memories;
2491
+ `);
2492
+ }
2493
+ function tableExists(database, tableName) {
2494
+ return database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName) !== void 0;
2495
+ }
2496
+ function ensureJournalNullableColumn(database, column) {
2497
+ if (database.prepare("PRAGMA table_info(nodix_rem_journal)").all().some((candidate) => candidate.name === column)) return;
2498
+ database.exec(`ALTER TABLE nodix_rem_journal ADD COLUMN ${column} TEXT`);
2499
+ }
2500
+ function ensureHolderBootIdColumn(database, tableName) {
2501
+ if (database.prepare(`PRAGMA table_info(${tableName})`).all().some((column) => column.name === "holder_boot_id")) return;
2502
+ database.exec(`ALTER TABLE ${tableName} ADD COLUMN holder_boot_id TEXT`);
2503
+ }
2504
+ //#endregion
2505
+ //#region src/engine/rem/writer-census.ts
2506
+ function discoverRemWritersFromCallGraph(input) {
2507
+ const executorSource = [...readImportClosure(input.entryPoints).values()].find((source) => /^(?:async\s+)?function applyWriterOperation\s*\(/mu.test(source));
2508
+ if (executorSource === void 0) throw new Error("REM production mutation executor is not reachable from the entry points");
2509
+ const start = executorSource.search(/^(?:async\s+)?function applyWriterOperation\s*\(/mu);
2510
+ const end = executorSource.indexOf("\n}\n\nfunction ", start);
2511
+ if (end < 0) throw new Error("REM production mutation executor boundary is incomplete");
2512
+ const writers = /* @__PURE__ */ new Set();
2513
+ for (const match of executorSource.slice(start, end + 2).matchAll(/case\s+"([A-Za-z]+)"\s*:/g)) {
2514
+ const writer = match[1];
2515
+ if (writer !== void 0) writers.add(writer);
2516
+ }
2517
+ return { writers: [...writers].map((writer) => ({ writer })) };
2518
+ }
2519
+ function readImportClosure(entryPoints) {
2520
+ const pending = entryPoints.map((entryPoint) => resolve(entryPoint));
2521
+ const sources = /* @__PURE__ */ new Map();
2522
+ while (pending.length > 0) {
2523
+ const path = pending.shift();
2524
+ if (path === void 0 || sources.has(path)) continue;
2525
+ if (!existsSync(path)) throw new Error(`REM call-graph entry does not exist: ${path}`);
2526
+ const source = readFileSync(path, "utf8");
2527
+ sources.set(path, source);
2528
+ for (const specifier of readModuleSpecifiers(source)) {
2529
+ const resolved = resolveSourceImport(path, specifier);
2530
+ if (resolved !== void 0 && !sources.has(resolved)) pending.push(resolved);
2531
+ }
2532
+ }
2533
+ return sources;
2534
+ }
2535
+ function readModuleSpecifiers(source) {
2536
+ const specifiers = [];
2537
+ for (const match of source.matchAll(/(?:from\s+|import\s*\()(["'])([^"']+)\1/g)) if (match[2] !== void 0) specifiers.push(match[2]);
2538
+ return specifiers;
2539
+ }
2540
+ function resolveSourceImport(importer, specifier) {
2541
+ let candidate;
2542
+ if (specifier.startsWith("./") || specifier.startsWith("../")) candidate = resolve(dirname(importer), specifier);
2543
+ else if (specifier.startsWith("@/")) {
2544
+ const markerIndex = importer.indexOf("/packages/memory/src/");
2545
+ if (markerIndex < 0) return void 0;
2546
+ candidate = resolve(importer.slice(0, markerIndex + 21), specifier.slice(2));
2547
+ } else return;
2548
+ return (extname(candidate).length > 0 ? [candidate, candidate.replace(/\.js$/, ".ts")] : [`${candidate}.ts`, resolve(candidate, "index.ts")]).find((path) => existsSync(path));
2549
+ }
2550
+ //#endregion
2551
+ export { REM_ADDITIONAL_TRANSITION_PATTERN_SHA256, REM_AMBIGUOUS_PATTERN_SHA256, REM_BUILT_OPERATION_TYPES, REM_CURRENT_PROGRESSIVE_PATTERN_SHA256, REM_ENABLE_GATE_BINDINGS, REM_HISTORICAL_FACT_PATTERN_SHA256, REM_JOB_STATES, REM_OPERATION_TYPES, REM_PURE_NEGATION_PATTERN_SHA256, REM_ROW_HASH_FIELDS, REM_ROW_HASH_VERSION, REM_ROW_HASH_VERSION_LEGACY, REM_ROW_STATES, REM_TRANSITION_GRAMMAR_SHA256, REM_UPDATE_LOCALES, RemEnableGateError, adapterAViewFromRecord, arbitrateReplaceVerdicts, assertJobIdentity, buildRemCoverageAtoms, buildRemUpdateReferenceSet, buildReplaceEligibleClauses, canonicalizeRemConfiguration, classifyAllRemCensusLabels, classifyRemCensusLabel, classifyRemRow, composeRemNegatedCurrent, createRemRepository, decideRemClauseCarry, decideRemRetirementTargetFromReply, decideRemUpdateFromReply, decideRemUpdateRelationFromReply, decideRemUpdateVerification, decideReplaceCoverage, declareRemCalibrationThreshold, defineRemSafetyGuard, deriveRemConfigurationSha256, deriveRemReplayArtifactIdentity, deriveRemWriteIdentity, discoverRemWritersFromCallGraph, enumerateRemUpdateMembers, extractJsonFromResponse, getRemUpdateLocaleResource, hashRemMemoryRow, installRemSchema, isAdapterAVerdictReply, journalRemGenerationTransition, loadRemEnableGate, orderAdapterAPair, parseAdapterAChatVerdict, parseRemOperationType, parseRemOperationalConfiguration, parseReplaceClauseVerdict, parseReplaceCoverageAtoms, produceRemCensusArtifact, prospectiveProfileView, readRetainedRemCensusArtifact, recordNonRefusePairDecision, recordRemCandidateCapCounts, recordRemObservedVerdict, renderAdapterAChatPrompt, renderAdapterAPrompt, renderRemClauseCarryPrompt, renderRemRetirementTargetPrompt, renderRemUpdateJudgmentPrompt, renderRemUpdateRelationJudgmentPrompt, renderRemUpdateVerificationPrompt, renderReplaceClauseVerdictPrompt, renderReplaceCoveragePrompt, reportRemSimilarityCalibration, requireRemCoverageAccuracyFloor, requireRemOwnerDecisions, runRemStages, runRestateClassifierEntry, runVerdictClassifierEntry, splitExactClauses, summarizeRemBatchEvents, validateRemCalibrationCrossCheck, validateRemCensusLabelTuple, validateRemCensusOracleProvenance, validateRemCensusRouteCoverage, validateRemClauseCoverageArtifacts, validateRemEnableGateDigestKeys, validateRemGrammarChange, validateRemGuardCensus, validateRemProspectiveIdentity, validateRemReplayIdentityTuple, validateRemSubstantiveWaveEffects, writeRemTwoFacetTransaction };