@remnic/core 9.3.726 → 9.3.728

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 (342) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +22 -22
  3. package/dist/access-cli.js +45 -45
  4. package/dist/access-http.d.ts +20 -5
  5. package/dist/access-http.js +27 -27
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +26 -26
  8. package/dist/access-operations-batch.js +23 -23
  9. package/dist/access-operations.d.ts +6 -6
  10. package/dist/access-operations.js +25 -25
  11. package/dist/access-schema.d.ts +4 -4
  12. package/dist/access-schema.js +3 -3
  13. package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
  14. package/dist/access-service.d.ts +5 -5
  15. package/dist/access-service.js +21 -21
  16. package/dist/access-surface-catalog.d.ts +5 -5
  17. package/dist/action-confidence.d.ts +1 -1
  18. package/dist/active-memory-bridge.d.ts +1 -1
  19. package/dist/active-recall.d.ts +1 -1
  20. package/dist/active-recall.js +3 -3
  21. package/dist/behavior-learner.d.ts +1 -1
  22. package/dist/behavior-signals.d.ts +1 -1
  23. package/dist/bootstrap.d.ts +4 -4
  24. package/dist/briefing.d.ts +1 -1
  25. package/dist/briefing.js +7 -7
  26. package/dist/buffer-surprise-report.d.ts +1 -1
  27. package/dist/buffer.d.ts +1 -1
  28. package/dist/calibration.d.ts +1 -1
  29. package/dist/capabilities.d.ts +44 -2
  30. package/dist/capabilities.js +5 -3
  31. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  32. package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
  33. package/dist/causal-behavior.d.ts +1 -1
  34. package/dist/causal-consolidation.d.ts +1 -1
  35. package/dist/causal-consolidation.js +8 -8
  36. package/dist/causal-trajectory-graph.d.ts +1 -1
  37. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  38. package/dist/{chunk-BIA7ELIB.js → chunk-36SEDFAU.js} +3 -3
  39. package/dist/{chunk-UQSVUEN2.js → chunk-37436GML.js} +2 -2
  40. package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
  41. package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
  42. package/dist/{chunk-RRHHQYJS.js → chunk-3TSTGI6F.js} +2 -2
  43. package/dist/{chunk-5OOA7SGX.js → chunk-4ESAR77N.js} +2 -2
  44. package/dist/{chunk-FO3HO52S.js → chunk-4H4F3BFL.js} +2 -2
  45. package/dist/{chunk-BEUCACTV.js → chunk-4NOI577P.js} +2 -2
  46. package/dist/{chunk-BBIE7W52.js → chunk-4NR2PDZ5.js} +2 -2
  47. package/dist/{chunk-WGTWGDR5.js → chunk-76NHR6ZL.js} +5 -5
  48. package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
  49. package/dist/{chunk-MQVKHFIU.js → chunk-7RLCCNHU.js} +8 -8
  50. package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
  51. package/dist/chunk-AAD2ZS6P.js.map +1 -0
  52. package/dist/{chunk-EZNDNMRH.js → chunk-AQLOP5F4.js} +2 -2
  53. package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
  54. package/dist/{chunk-NPL7VDM6.js → chunk-ASKXIAHD.js} +2 -2
  55. package/dist/{chunk-TQQH2FTX.js → chunk-BQCHEV7O.js} +106 -15
  56. package/dist/{chunk-TQQH2FTX.js.map → chunk-BQCHEV7O.js.map} +1 -1
  57. package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
  58. package/dist/chunk-C2PG7IEH.js.map +1 -0
  59. package/dist/{chunk-K4ZNARDO.js → chunk-CBWI22JN.js} +2 -2
  60. package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
  61. package/dist/{chunk-JPUT5NGH.js → chunk-DMQWV6SQ.js} +2 -2
  62. package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
  63. package/dist/{chunk-KJR66XOM.js → chunk-EY6MDMHK.js} +2 -2
  64. package/dist/{chunk-3TUT4PWB.js → chunk-FVZ2ISIX.js} +2 -2
  65. package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
  66. package/dist/chunk-G6NKALRN.js.map +1 -0
  67. package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
  68. package/dist/{chunk-PLG6JHQS.js → chunk-H5BWGCRZ.js} +43 -42
  69. package/dist/chunk-H5BWGCRZ.js.map +1 -0
  70. package/dist/{chunk-2NBP25BG.js → chunk-HGNOCPZX.js} +171 -79
  71. package/dist/chunk-HGNOCPZX.js.map +1 -0
  72. package/dist/{chunk-5NWYQUQC.js → chunk-HOHVIVDR.js} +2 -2
  73. package/dist/{chunk-DCKNMLEQ.js → chunk-I5REUWLH.js} +11 -5
  74. package/dist/chunk-I5REUWLH.js.map +1 -0
  75. package/dist/{chunk-6WAJQKBS.js → chunk-JYLFZWUX.js} +2 -2
  76. package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
  77. package/dist/{chunk-UKTDXTWX.js → chunk-M4AYVEWS.js} +7 -7
  78. package/dist/{chunk-YTZ3VGC7.js → chunk-M4V2JVSZ.js} +2 -2
  79. package/dist/{chunk-TA3Y6GEX.js → chunk-MLKWJOAJ.js} +2 -2
  80. package/dist/{chunk-DQXPJ6HD.js → chunk-O6YH5F47.js} +4 -4
  81. package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
  82. package/dist/chunk-PUR2CKX2.js.map +1 -0
  83. package/dist/{chunk-OAHOL3ZC.js → chunk-QNEUZIAH.js} +152 -62
  84. package/dist/chunk-QNEUZIAH.js.map +1 -0
  85. package/dist/{chunk-Q6D75LVH.js → chunk-UM5AWJNQ.js} +79 -17
  86. package/dist/chunk-UM5AWJNQ.js.map +1 -0
  87. package/dist/{chunk-53EEQYE5.js → chunk-UTHQPSRC.js} +8 -8
  88. package/dist/{chunk-BB57OZWC.js → chunk-UVPL565C.js} +2 -2
  89. package/dist/{chunk-6AFVIOVZ.js → chunk-V4GNSDFH.js} +2 -2
  90. package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
  91. package/dist/chunk-WAS3NV46.js.map +1 -0
  92. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  93. package/dist/{chunk-V7CMKMHM.js → chunk-Y4YD5TYW.js} +3 -3
  94. package/dist/{chunk-7NS7AWMZ.js → chunk-YGIQORJ3.js} +3 -3
  95. package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
  96. package/dist/chunk-YKBHOBJK.js.map +1 -0
  97. package/dist/{chunk-3MS3QVX6.js → chunk-YOLZS7NY.js} +2 -2
  98. package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
  99. package/dist/chunk-YTU7WKYN.js.map +1 -0
  100. package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
  101. package/dist/cli.d.ts +6 -6
  102. package/dist/cli.js +49 -49
  103. package/dist/compat/codex-cli-fallback.d.ts +1 -0
  104. package/dist/compat/codex-cli-fallback.js +22 -0
  105. package/dist/compat/codex-cli-fallback.js.map +1 -0
  106. package/dist/compat/codex-thread-key.d.ts +1 -0
  107. package/dist/compat/codex-thread-key.js +18 -0
  108. package/dist/compat/codex-thread-key.js.map +1 -0
  109. package/dist/compounding/engine.d.ts +1 -1
  110. package/dist/compounding/engine.js +7 -7
  111. package/dist/compounding/preference-consolidator.d.ts +1 -1
  112. package/dist/compression-optimizer.d.ts +1 -1
  113. package/dist/config.d.ts +1 -1
  114. package/dist/config.js +3 -3
  115. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  116. package/dist/connectors/codex-materialize-runner.js +7 -7
  117. package/dist/connectors/codex-materialize.d.ts +1 -1
  118. package/dist/connectors/index.d.ts +1 -1
  119. package/dist/connectors/index.js +7 -7
  120. package/dist/consolidation-provenance-check.d.ts +1 -1
  121. package/dist/consolidation-undo.d.ts +1 -1
  122. package/dist/contradiction/index.d.ts +1 -1
  123. package/dist/conversation-index/backend.d.ts +1 -1
  124. package/dist/conversation-index/chunker.d.ts +1 -1
  125. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  126. package/dist/conversation-index/indexer.d.ts +1 -1
  127. package/dist/conversation-index/search.d.ts +1 -1
  128. package/dist/day-summary.d.ts +1 -1
  129. package/dist/delinearize.d.ts +1 -1
  130. package/dist/direct-answer-wiring.d.ts +1 -1
  131. package/dist/direct-answer.d.ts +1 -1
  132. package/dist/embedding-fallback.d.ts +1 -1
  133. package/dist/embedding-fallback.js +2 -1
  134. package/dist/enrichment/index.d.ts +1 -1
  135. package/dist/entity-retrieval.d.ts +1 -1
  136. package/dist/entity-retrieval.js +7 -7
  137. package/dist/entity-schema.d.ts +1 -1
  138. package/dist/explicit-capture.d.ts +4 -4
  139. package/dist/extraction-faithfulness.d.ts +1 -1
  140. package/dist/extraction-faithfulness.js +2 -2
  141. package/dist/extraction-judge-telemetry.d.ts +1 -1
  142. package/dist/extraction-judge-training.d.ts +1 -1
  143. package/dist/extraction-judge.d.ts +1 -1
  144. package/dist/extraction.d.ts +1 -1
  145. package/dist/extraction.js +3 -2
  146. package/dist/faithfulness-config.d.ts +1 -0
  147. package/dist/faithfulness-config.js +1 -1
  148. package/dist/fallback-llm.d.ts +1 -1
  149. package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
  150. package/dist/first-start-migration-22LPFHD5.js.map +1 -0
  151. package/dist/graph-dashboard-diff.d.ts +1 -1
  152. package/dist/graph-dashboard-key.d.ts +1 -1
  153. package/dist/graph-dashboard-parser.d.ts +1 -1
  154. package/dist/graph-edge-reinforcement.d.ts +1 -1
  155. package/dist/graph-snapshot.d.ts +1 -1
  156. package/dist/graph.d.ts +1 -1
  157. package/dist/identity-continuity.d.ts +1 -1
  158. package/dist/importance.d.ts +1 -1
  159. package/dist/index.d.ts +9 -9
  160. package/dist/index.js +64 -64
  161. package/dist/intent.d.ts +1 -1
  162. package/dist/lcm/engine.d.ts +1 -1
  163. package/dist/lcm/engine.js +2 -2
  164. package/dist/lcm/index.d.ts +1 -1
  165. package/dist/lcm/index.js +7 -7
  166. package/dist/lcm/tools.d.ts +1 -1
  167. package/dist/lifecycle.d.ts +1 -1
  168. package/dist/live-connectors-runner.d.ts +1 -1
  169. package/dist/local-llm.d.ts +1 -1
  170. package/dist/local-model-endpoint.d.ts +9 -4
  171. package/dist/local-model-endpoint.js +1 -1
  172. package/dist/maintenance/memory-governance.d.ts +1 -1
  173. package/dist/maintenance/memory-governance.js +7 -7
  174. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  175. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  176. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  177. package/dist/memory-action-policy.d.ts +1 -1
  178. package/dist/memory-cache.d.ts +1 -1
  179. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  180. package/dist/memory-projection-store.d.ts +1 -1
  181. package/dist/memory-provenance.d.ts +1 -1
  182. package/dist/memory-worth-outcomes.d.ts +1 -1
  183. package/dist/models-json.d.ts +1 -1
  184. package/dist/namespaces/migrate.d.ts +2 -2
  185. package/dist/namespaces/migrate.js +14 -13
  186. package/dist/namespaces/principal.d.ts +1 -1
  187. package/dist/namespaces/search.d.ts +1 -1
  188. package/dist/namespaces/search.js +6 -5
  189. package/dist/namespaces/storage.d.ts +2 -2
  190. package/dist/namespaces/storage.js +7 -7
  191. package/dist/native-knowledge.d.ts +1 -1
  192. package/dist/operator-toolkit.d.ts +1 -1
  193. package/dist/operator-toolkit.js +20 -20
  194. package/dist/orchestration/maintenance.d.ts +2 -2
  195. package/dist/orchestration/maintenance.js +9 -9
  196. package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
  197. package/dist/orchestrator.d.ts +4 -4
  198. package/dist/orchestrator.js +33 -33
  199. package/dist/patterns-cli.d.ts +1 -1
  200. package/dist/policy-runtime.d.ts +1 -1
  201. package/dist/provenance.d.ts +1 -1
  202. package/dist/provenance.js +1 -1
  203. package/dist/qmd-recall-cache.d.ts +1 -1
  204. package/dist/qmd.d.ts +1 -1
  205. package/dist/recall-disclosure-escalation.d.ts +1 -1
  206. package/dist/recall-explain-renderer.d.ts +1 -1
  207. package/dist/recall-explain-renderer.js +3 -3
  208. package/dist/recall-planner-llm.d.ts +1 -1
  209. package/dist/recall-state.d.ts +1 -1
  210. package/dist/recall-tag-filter.d.ts +1 -1
  211. package/dist/recall-xray-cli.d.ts +1 -1
  212. package/dist/recall-xray-cli.js +4 -4
  213. package/dist/recall-xray-renderer.d.ts +1 -1
  214. package/dist/recall-xray-renderer.js +3 -3
  215. package/dist/recall-xray.d.ts +1 -1
  216. package/dist/recall-xray.js +2 -2
  217. package/dist/resolve-auth-token.d.ts +1 -1
  218. package/dist/resume-bundles.js +4 -4
  219. package/dist/retrieval-agents.d.ts +1 -1
  220. package/dist/retrieval-tiers.d.ts +1 -1
  221. package/dist/routing/engine.d.ts +1 -1
  222. package/dist/routing/store.d.ts +1 -1
  223. package/dist/search/embed-helper.d.ts +1 -1
  224. package/dist/search/embed-helper.js +2 -1
  225. package/dist/search/factory.d.ts +1 -1
  226. package/dist/search/factory.js +5 -4
  227. package/dist/search/index.d.ts +1 -1
  228. package/dist/search/index.js +9 -8
  229. package/dist/search/lancedb-backend.d.ts +1 -1
  230. package/dist/search/meilisearch-backend.d.ts +1 -1
  231. package/dist/search/noop-backend.d.ts +1 -1
  232. package/dist/search/orama-backend.d.ts +1 -1
  233. package/dist/search/port.d.ts +1 -1
  234. package/dist/search/remote-backend.d.ts +1 -1
  235. package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
  236. package/dist/semantic-consolidation.d.ts +2 -2
  237. package/dist/semantic-consolidation.js +8 -8
  238. package/dist/semantic-rule-promotion.js +7 -7
  239. package/dist/semantic-rule-verifier.d.ts +1 -1
  240. package/dist/semantic-rule-verifier.js +7 -7
  241. package/dist/session-observer-bands.d.ts +1 -1
  242. package/dist/session-observer-state.d.ts +1 -1
  243. package/dist/shared-context/manager.d.ts +1 -1
  244. package/dist/signal.d.ts +1 -1
  245. package/dist/storage.d.ts +1 -1
  246. package/dist/storage.js +6 -6
  247. package/dist/summarizer.d.ts +1 -1
  248. package/dist/summary-snapshot.d.ts +1 -1
  249. package/dist/temporal-supersession.d.ts +1 -1
  250. package/dist/temporal-validity.d.ts +1 -1
  251. package/dist/threading.d.ts +1 -1
  252. package/dist/tier-migration.d.ts +1 -1
  253. package/dist/tier-routing.d.ts +1 -1
  254. package/dist/topics.d.ts +1 -1
  255. package/dist/transcript.d.ts +1 -1
  256. package/dist/transfer/backup.js +2 -2
  257. package/dist/transfer/capsule-export.js +2 -2
  258. package/dist/transfer/capsule-import.js +2 -2
  259. package/dist/trust-score-stage.d.ts +1 -1
  260. package/dist/trust-score.d.ts +1 -1
  261. package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
  262. package/dist/types.d.ts +1 -1
  263. package/dist/types.js +1 -1
  264. package/dist/utility-runtime.d.ts +1 -1
  265. package/dist/verified-recall.js +7 -7
  266. package/package.json +26 -2
  267. package/src/access-http.ts +68 -1
  268. package/src/access-service.ts +2 -1
  269. package/src/admin/admin-surfaces.test.ts +289 -0
  270. package/src/admin/admin-surfaces.ts +138 -7
  271. package/src/bitemporal-dedup-backfill.test.ts +294 -0
  272. package/src/capabilities.test.ts +155 -0
  273. package/src/capabilities.ts +91 -0
  274. package/src/chat/chat-engine.test.ts +104 -1
  275. package/src/chat/chat-http.test.ts +119 -1
  276. package/src/chat/chat-http.ts +252 -97
  277. package/src/chat/chat-session.ts +166 -7
  278. package/src/cli.ts +3 -2
  279. package/src/compat/codex-cli-fallback.ts +23 -0
  280. package/src/compat/codex-subpath-aliases.test.ts +93 -0
  281. package/src/compat/codex-thread-key.ts +23 -0
  282. package/src/embedding-fallback.ts +2 -1
  283. package/src/extraction-faithfulness.test.ts +240 -0
  284. package/src/extraction-faithfulness.ts +58 -6
  285. package/src/extraction.ts +7 -3
  286. package/src/faithfulness-config.ts +16 -1
  287. package/src/local-model-endpoint.ts +24 -3
  288. package/src/maintenance/first-start-migration.ts +3 -1
  289. package/src/orchestrator.ts +238 -34
  290. package/src/provenance-extraction.test.ts +266 -0
  291. package/src/provenance.ts +66 -16
  292. package/src/search/embed-helper.ts +3 -2
  293. package/src/types.ts +8 -0
  294. package/dist/chunk-2NBP25BG.js.map +0 -1
  295. package/dist/chunk-6OIDDKAA.js.map +0 -1
  296. package/dist/chunk-7AG54AF3.js.map +0 -1
  297. package/dist/chunk-C4X4QGIO.js.map +0 -1
  298. package/dist/chunk-DCKNMLEQ.js.map +0 -1
  299. package/dist/chunk-EHISUJFN.js.map +0 -1
  300. package/dist/chunk-MDBZTH7N.js.map +0 -1
  301. package/dist/chunk-OAHOL3ZC.js.map +0 -1
  302. package/dist/chunk-PLG6JHQS.js.map +0 -1
  303. package/dist/chunk-Q6D75LVH.js.map +0 -1
  304. package/dist/chunk-XGTBNVIU.js.map +0 -1
  305. package/dist/chunk-ZA2S2VLL.js.map +0 -1
  306. package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
  307. /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  308. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  309. /package/dist/{chunk-BIA7ELIB.js.map → chunk-36SEDFAU.js.map} +0 -0
  310. /package/dist/{chunk-UQSVUEN2.js.map → chunk-37436GML.js.map} +0 -0
  311. /package/dist/{chunk-RRHHQYJS.js.map → chunk-3TSTGI6F.js.map} +0 -0
  312. /package/dist/{chunk-5OOA7SGX.js.map → chunk-4ESAR77N.js.map} +0 -0
  313. /package/dist/{chunk-FO3HO52S.js.map → chunk-4H4F3BFL.js.map} +0 -0
  314. /package/dist/{chunk-BEUCACTV.js.map → chunk-4NOI577P.js.map} +0 -0
  315. /package/dist/{chunk-BBIE7W52.js.map → chunk-4NR2PDZ5.js.map} +0 -0
  316. /package/dist/{chunk-WGTWGDR5.js.map → chunk-76NHR6ZL.js.map} +0 -0
  317. /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
  318. /package/dist/{chunk-MQVKHFIU.js.map → chunk-7RLCCNHU.js.map} +0 -0
  319. /package/dist/{chunk-EZNDNMRH.js.map → chunk-AQLOP5F4.js.map} +0 -0
  320. /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
  321. /package/dist/{chunk-NPL7VDM6.js.map → chunk-ASKXIAHD.js.map} +0 -0
  322. /package/dist/{chunk-K4ZNARDO.js.map → chunk-CBWI22JN.js.map} +0 -0
  323. /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
  324. /package/dist/{chunk-JPUT5NGH.js.map → chunk-DMQWV6SQ.js.map} +0 -0
  325. /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
  326. /package/dist/{chunk-KJR66XOM.js.map → chunk-EY6MDMHK.js.map} +0 -0
  327. /package/dist/{chunk-3TUT4PWB.js.map → chunk-FVZ2ISIX.js.map} +0 -0
  328. /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
  329. /package/dist/{chunk-5NWYQUQC.js.map → chunk-HOHVIVDR.js.map} +0 -0
  330. /package/dist/{chunk-6WAJQKBS.js.map → chunk-JYLFZWUX.js.map} +0 -0
  331. /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
  332. /package/dist/{chunk-UKTDXTWX.js.map → chunk-M4AYVEWS.js.map} +0 -0
  333. /package/dist/{chunk-YTZ3VGC7.js.map → chunk-M4V2JVSZ.js.map} +0 -0
  334. /package/dist/{chunk-TA3Y6GEX.js.map → chunk-MLKWJOAJ.js.map} +0 -0
  335. /package/dist/{chunk-DQXPJ6HD.js.map → chunk-O6YH5F47.js.map} +0 -0
  336. /package/dist/{chunk-53EEQYE5.js.map → chunk-UTHQPSRC.js.map} +0 -0
  337. /package/dist/{chunk-BB57OZWC.js.map → chunk-UVPL565C.js.map} +0 -0
  338. /package/dist/{chunk-6AFVIOVZ.js.map → chunk-V4GNSDFH.js.map} +0 -0
  339. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  340. /package/dist/{chunk-V7CMKMHM.js.map → chunk-Y4YD5TYW.js.map} +0 -0
  341. /package/dist/{chunk-7NS7AWMZ.js.map → chunk-YGIQORJ3.js.map} +0 -0
  342. /package/dist/{chunk-3MS3QVX6.js.map → chunk-YOLZS7NY.js.map} +0 -0
@@ -167,3 +167,297 @@ test("#1671: re-extraction with resolved invalidAt backfills an old promoted cop
167
167
  await cleanup();
168
168
  }
169
169
  });
170
+
171
+ // ─── #1707 widenings (promoted-cascade, valid_at, non-fact guard) ────────
172
+
173
+ // Thread 2 — valid_at propagation. A re-extracted duplicate whose event time
174
+ // yields only a start bound ("since 2024", "yesterday") must get the corrected
175
+ // per-fact validFrom onto the existing copy so as-of recall uses the corrected
176
+ // start. The orchestrator helper's rule: patch valid_at when the incoming
177
+ // validFrom is EXTRACTED and the copy's start bound is batch-anchored
178
+ // (assumed/legacy); never clobber a copy that already carries an extracted
179
+ // per-fact anchor. This test pins that rule against the storage patch the
180
+ // helper issues.
181
+ test("#1707 thread 2: corrected extracted validFrom overwrites a batch-anchored valid_at", async () => {
182
+ const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-");
183
+ try {
184
+ // Old promoted copy: batch-anchored valid_at, no per-fact event-time.
185
+ const content = "We have used Stripe for payments since 2024.";
186
+ const batchAnchor = "2026-06-01T00:00:00.000Z";
187
+ const id = await storage.writeMemory("fact", content, {
188
+ confidence: 0.9,
189
+ validAt: batchAnchor,
190
+ // No eventTimeSource → batch-anchored (legacy pre-#1670 copy).
191
+ });
192
+ const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
193
+ assert.ok(before);
194
+ assert.equal(before!.frontmatter.valid_at, batchAnchor);
195
+ assert.equal(before!.frontmatter.eventTimeSource, undefined);
196
+
197
+ // Simulate the helper's rule: incoming validFrom is extracted → patch
198
+ // because the copy is NOT extracted-anchored (fm.eventTimeSource !== "extracted").
199
+ const correctedValidFrom = "2024-01-01T00:00:00.000Z";
200
+ const incomingExtracted = true;
201
+ const copyExtracted = before!.frontmatter.eventTimeSource === "extracted";
202
+ const shouldPatchValidAt =
203
+ incomingExtracted && (copyExtracted === false || !before!.frontmatter.valid_at);
204
+ assert.equal(shouldPatchValidAt, true, "must correct the batch-anchored valid_at");
205
+
206
+ const ok = await storage.writeMemoryFrontmatter(before!, {
207
+ valid_at: correctedValidFrom,
208
+ eventTimeSource: "extracted",
209
+ observedAt: "2026-06-20T00:00:00.000Z",
210
+ });
211
+ assert.equal(ok, true);
212
+
213
+ const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
214
+ assert.ok(after);
215
+ assert.equal(after!.frontmatter.valid_at, correctedValidFrom);
216
+ assert.equal(after!.frontmatter.eventTimeSource, "extracted");
217
+ } finally {
218
+ await cleanup();
219
+ }
220
+ });
221
+
222
+ test("#1707 thread 2: identical extracted validFrom is a no-op (equality short-circuit)", async () => {
223
+ const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-equal-");
224
+ try {
225
+ // Copy already carries an extracted per-fact anchor.
226
+ const content = "The migration to MySQL completed in March 2025.";
227
+ const existingValidAt: string = "2025-03-01T00:00:00.000Z";
228
+ const id = await storage.writeMemory("fact", content, {
229
+ confidence: 0.9,
230
+ validAt: existingValidAt,
231
+ observedAt: "2025-06-01T00:00:00.000Z",
232
+ eventTimeSource: "extracted",
233
+ });
234
+ const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
235
+ assert.ok(before);
236
+
237
+ // Re-extraction resolves the SAME start bound (deterministic per #1670).
238
+ // The helper's equality short-circuit (fm.valid_at !== bounds.validFrom)
239
+ // skips the redundant write — the only no-clobber that holds without a
240
+ // fragile provenance heuristic (review codex PRRT_Ov7LKC).
241
+ const helperWouldPatch = existingValidAt !== existingValidAt; // equal → false
242
+ assert.equal(helperWouldPatch, false, "identical validFrom must not rewrite");
243
+
244
+ // No patch issued → valid_at unchanged.
245
+ const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
246
+ assert.ok(after);
247
+ assert.equal(after!.frontmatter.valid_at, existingValidAt);
248
+ } finally {
249
+ await cleanup();
250
+ }
251
+ });
252
+
253
+ test("#1707 thread 2: differing extracted validFrom overwrites (authoritative re-evaluation)", async () => {
254
+ const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-differ-");
255
+ try {
256
+ // Copy carries an older (batch-anchored) valid_at.
257
+ const content = "We have used Stripe for payments since 2024.";
258
+ const staleValidAt: string = "2026-06-01T00:00:00.000Z"; // batch anchor
259
+ const id = await storage.writeMemory("fact", content, {
260
+ confidence: 0.9,
261
+ validAt: staleValidAt,
262
+ });
263
+ const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
264
+ assert.ok(before);
265
+
266
+ // Re-extraction resolves a DIFFERING extracted start bound. Exact-content
267
+ // dedup + #1670 determinism means stable content re-resolves to the same
268
+ // value (equality no-op above); a divergence means the copy's start is
269
+ // stale (batch/assumed) and the extracted validFrom is authoritative.
270
+ const correctedValidFrom: string = "2024-01-01T00:00:00.000Z";
271
+ const helperWouldPatch = staleValidAt !== correctedValidFrom; // differ → true
272
+ assert.equal(helperWouldPatch, true, "differing extracted validFrom must overwrite");
273
+
274
+ const ok = await storage.writeMemoryFrontmatter(before!, {
275
+ valid_at: correctedValidFrom,
276
+ eventTimeSource: "extracted",
277
+ });
278
+ assert.equal(ok, true);
279
+
280
+ const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
281
+ assert.ok(after);
282
+ assert.equal(after!.frontmatter.valid_at, correctedValidFrom);
283
+ assert.equal(after!.frontmatter.eventTimeSource, "extracted");
284
+ } finally {
285
+ await cleanup();
286
+ }
287
+ });
288
+
289
+ // Review (codex PRRT_Ov68oA + PRRT_Ov7LKC): an END-only extraction ("through
290
+ // 2026") sets eventTimeSource = "extracted" (end resolved) but leaves valid_at
291
+ // batch/assumed-anchored. A later START-bound extraction must still correct
292
+ // valid_at. The helper now uses an equality short-circuit (no fragile
293
+ // provenance heuristic): the differing extracted validFrom overwrites the
294
+ // stale start regardless of the copy's eventTimeSource.
295
+ test("#1707 thread 2: end-only extracted provenance does NOT block a later start-bound correction", async () => {
296
+ const { storage, cleanup } = await makeStorage("bitemporal-1707-endonly-");
297
+ try {
298
+ // Copy state after an end-only backfill: eventTimeSource = "extracted"
299
+ // (the end "through 2026" resolved), but valid_at is still the ingestion
300
+ // anchor (assumed start).
301
+ const observedAt = "2026-06-01T00:00:00.000Z";
302
+ const content = "The service was maintained through 2026.";
303
+ const id = await storage.writeMemory("fact", content, {
304
+ confidence: 0.9,
305
+ validAt: observedAt, // valid_at === observedAt (assumed/batch start)
306
+ observedAt,
307
+ eventTimeSource: "extracted", // set by the end-only extraction
308
+ invalidAt: "2026-12-31T00:00:00.000Z", // the extracted end bound
309
+ });
310
+ const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
311
+ assert.ok(before);
312
+ assert.equal(before!.frontmatter.eventTimeSource, "extracted");
313
+ assert.equal(before!.frontmatter.valid_at, before!.frontmatter.observedAt);
314
+
315
+ // The helper's valid_at gate (mirrors orchestrator.ts):
316
+ // bounds.validFrom && eventTimeSource === "extracted" && valid_at !== validFrom
317
+ // The stale valid_at differs from the incoming real start → overwrite.
318
+
319
+ // A later re-extraction with a real extracted start must correct valid_at.
320
+ const realStart = "2024-03-01T00:00:00.000Z";
321
+ const ok = await storage.writeMemoryFrontmatter(before!, {
322
+ valid_at: realStart,
323
+ });
324
+ assert.equal(ok, true);
325
+
326
+ const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
327
+ assert.ok(after);
328
+ assert.equal(after!.frontmatter.valid_at, realStart);
329
+ } finally {
330
+ await cleanup();
331
+ }
332
+ });
333
+
334
+ // Gate guard (review cursor PRRT_OvHk / codex PRRT_OvHxVH): resolveFactEventTime
335
+ // sets a validFrom for EVERY fact (assumed = ingestion anchor). The helper's
336
+ // I/O gate must NOT scan on an assumed validFrom — only an EXTRACTED start bound
337
+ // changes recall. This pins the gate decision so ordinary duplicate facts
338
+ // without an extracted event time do not pay a readAllMemories scan on every
339
+ // dedup hit.
340
+ test("#1707 thread 2: assumed (batch-anchored) validFrom does NOT trigger a backfill scan", () => {
341
+ // Mirror the helper's I/O gate (orchestrator.ts backfillTemporalBoundsOnDedupHit):
342
+ // const hasExtractedStart = bounds.validFrom !== undefined && bounds.eventTimeSource === "extracted";
343
+ // if (!bounds.invalidAt && !hasExtractedStart) return;
344
+ const gateWouldScan = (bounds: {
345
+ invalidAt?: string;
346
+ validFrom?: string;
347
+ eventTimeSource?: "extracted" | "assumed";
348
+ }) =>
349
+ Boolean(bounds.invalidAt) ||
350
+ (bounds.validFrom !== undefined && bounds.eventTimeSource === "extracted");
351
+
352
+ // Assumed validFrom alone (the common no-event-time case) → no scan.
353
+ assert.equal(
354
+ gateWouldScan({ validFrom: "2026-06-01T00:00:00.000Z", eventTimeSource: "assumed" }),
355
+ false,
356
+ "assumed validFrom must not trigger a scan",
357
+ );
358
+ // Extracted validFrom → scan (corrected start bound is recall-relevant).
359
+ assert.equal(
360
+ gateWouldScan({ validFrom: "2024-01-01T00:00:00.000Z", eventTimeSource: "extracted" }),
361
+ true,
362
+ );
363
+ // End bound alone → scan (expires the fact).
364
+ assert.equal(gateWouldScan({ invalidAt: "2025-06-01T00:00:00.000Z" }), true);
365
+ // No bounds at all → no scan.
366
+ assert.equal(gateWouldScan({}), false);
367
+ });
368
+ // Thread 1 — promoted-cascade backfill. When the source-namespace dedup
369
+ // short-circuit fires, the helper must also patch promotion-target copies
370
+ // (profile + shared namespaces) so cross-namespace recall does not surface an
371
+ // expired fact. This proves the mechanism the orchestrator's promotion-cascade
372
+ // closure relies on: backfilling two separate storages patches each
373
+ // independently.
374
+ test("#1707 thread 1: backfill patches promoted copies across multiple storages (cascade)", async () => {
375
+ const { storage: sourceStorage, cleanup: cleanupSource } = await makeStorage(
376
+ "bitemporal-1707-cascade-source-",
377
+ );
378
+ const { storage: promotedStorage, cleanup: cleanupPromoted } = await makeStorage(
379
+ "bitemporal-1707-cascade-promoted-",
380
+ );
381
+ try {
382
+ const content = "The API rate limit is 100 req/min until June 2025.";
383
+ // Both namespaces hold the same fact, written WITHOUT bounds (stale copies).
384
+ const sourceId = await sourceStorage.writeMemory("fact", content, { confidence: 0.9 });
385
+ const promotedId = await promotedStorage.writeMemory("fact", content, { confidence: 0.9 });
386
+
387
+ // Simulate re-extraction that now carries a resolved invalidAt. The
388
+ // promotion-cascade closure calls the same backfill against each target.
389
+ const bounds = {
390
+ invalid_at: "2025-06-01T00:00:00.000Z",
391
+ observedAt: "2025-06-20T00:00:00.000Z",
392
+ eventTimeSource: "extracted" as const,
393
+ };
394
+ for (const storage of [sourceStorage, promotedStorage]) {
395
+ const all = await storage.readAllMemories();
396
+ const existing = all.find(
397
+ (m) =>
398
+ m.frontmatter.category === "fact" &&
399
+ (m.frontmatter.status ?? "active") === "active" &&
400
+ !m.frontmatter.invalid_at &&
401
+ ContentHashIndex.normalizeContent(m.content ?? "") ===
402
+ ContentHashIndex.normalizeContent(content),
403
+ );
404
+ assert.ok(existing, "must find the existing copy in each storage");
405
+ const ok = await storage.writeMemoryFrontmatter(existing!, bounds);
406
+ assert.equal(ok, true);
407
+ }
408
+
409
+ // Both copies now carry the resolved end bound — cross-namespace recall
410
+ // honours the expiry instead of surfacing the stale promoted copy.
411
+ const sourceAfter = (await sourceStorage.readAllMemories()).find(
412
+ (m) => m.frontmatter.id === sourceId,
413
+ );
414
+ const promotedAfter = (await promotedStorage.readAllMemories()).find(
415
+ (m) => m.frontmatter.id === promotedId,
416
+ );
417
+ assert.ok(sourceAfter && promotedAfter);
418
+ assert.equal(sourceAfter!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z");
419
+ assert.equal(promotedAfter!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z");
420
+ } finally {
421
+ await cleanupSource();
422
+ await cleanupPromoted();
423
+ }
424
+ });
425
+
426
+ // Thread 3 — non-fact guard. The source dedup backfill call site is gated on
427
+ // writeCategory === "fact", so a non-fact duplicate (preference/decision/
428
+ // procedure) never reaches the fact-only backfill scan. This proves the
429
+ // helper's category guard that makes that call-site gate correct: a non-fact
430
+ // copy is never matched/patched even if its content hash collides.
431
+ test("#1707 thread 3: backfill never patches a non-fact copy (category guard)", async () => {
432
+ const { storage, cleanup } = await makeStorage("bitemporal-1707-nonfact-");
433
+ try {
434
+ const content = "The user prefers dark mode.";
435
+ // A preference (non-fact) whose content would collide with an incoming
436
+ // duplicate candidate.
437
+ const id = await storage.writeMemory("preference", content, { confidence: 0.9 });
438
+ const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
439
+ assert.ok(before);
440
+ assert.equal(before!.frontmatter.category, "preference");
441
+ assert.equal(before!.frontmatter.invalid_at, undefined);
442
+
443
+ // The helper's lookup filters category === "fact" — a preference copy is
444
+ // never eligible, so no patch is issued even when bounds are present.
445
+ const all = await storage.readAllMemories();
446
+ const existing = all.find(
447
+ (m) =>
448
+ m.frontmatter.category === "fact" && // ← the guard under test
449
+ (m.frontmatter.status ?? "active") === "active" &&
450
+ ContentHashIndex.normalizeContent(m.content ?? "") ===
451
+ ContentHashIndex.normalizeContent(content),
452
+ );
453
+ assert.equal(existing, undefined, "non-fact copy must not be matched by the fact-only lookup");
454
+
455
+ // No patch → the preference copy is untouched.
456
+ const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
457
+ assert.ok(after);
458
+ assert.equal(after!.frontmatter.invalid_at, undefined);
459
+ assert.equal(after!.frontmatter.category, "preference");
460
+ } finally {
461
+ await cleanup();
462
+ }
463
+ });
@@ -153,6 +153,12 @@ import {
153
153
  type GraphConstructionCapabilitySet,
154
154
  } from "./capabilities.js";
155
155
 
156
+ import {
157
+ resolveMemoryLifecycleCapabilities,
158
+ type MemoryLifecycleCapabilitySet,
159
+ } from "./capabilities.js";
160
+
161
+
156
162
  const GRAPH_FIELD_TO_FLAG: Record<keyof GraphConstructionCapabilitySet, string> = {
157
163
  entityGraph: "entityGraphEnabled",
158
164
  timeGraph: "timeGraphEnabled",
@@ -306,3 +312,152 @@ test("resolveGraphConstructionCapabilities matches pre-migration config reads (p
306
312
  );
307
313
  }
308
314
  });
315
+
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // MemoryLifecycleCapabilitySet — gate-parity tests (issue #1523 batch 3).
319
+ //
320
+ // Same invariant as the sets above: every caps field must project from its
321
+ // `<field>Enabled` config flag, so a future edit that maps a field to the wrong
322
+ // flag fails loudly here. All ten flags are required booleans on PluginConfig
323
+ // (defaults resolved at the parse boundary), so the projection is a pure
324
+ // passthrough — no optional-default variants needed.
325
+ //
326
+ // Parity contract: a caps-resolved run and a config-derived run MUST produce
327
+ // identical boolean values for every gate — on AND off.
328
+ // ---------------------------------------------------------------------------
329
+
330
+ const LIFECYCLE_FIELD_TO_FLAG: Record<keyof MemoryLifecycleCapabilitySet, string> = {
331
+ temporalSupersession: "temporalSupersessionEnabled",
332
+ temporalMemoryTree: "temporalMemoryTreeEnabled",
333
+ lifecyclePolicy: "lifecyclePolicyEnabled",
334
+ lifecycleFilterStale: "lifecycleFilterStaleEnabled",
335
+ lifecycleMetrics: "lifecycleMetricsEnabled",
336
+ extractionScopeClassification: "extractionScopeClassificationEnabled",
337
+ extractionJudge: "extractionJudgeEnabled",
338
+ extractionDedupe: "extractionDedupeEnabled",
339
+ extractionTelemetryPrefilter: "extractionTelemetryPrefilterEnabled",
340
+ extractionJudgeTelemetry: "extractionJudgeTelemetryEnabled",
341
+ embeddingFallback: "embeddingFallbackEnabled",
342
+ };
343
+
344
+ const LIFECYCLE_FIELDS = Object.keys(LIFECYCLE_FIELD_TO_FLAG) as Array<
345
+ keyof MemoryLifecycleCapabilitySet
346
+ >;
347
+
348
+ test("resolveMemoryLifecycleCapabilities projects every field from its flag (true variant)", () => {
349
+ const overrides: Record<string, boolean> = {};
350
+ for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = true;
351
+ const config = parseConfig(overrides);
352
+ const caps = resolveMemoryLifecycleCapabilities(config);
353
+
354
+ for (const field of LIFECYCLE_FIELDS) {
355
+ const flag = LIFECYCLE_FIELD_TO_FLAG[field];
356
+ assert.equal(
357
+ caps[field],
358
+ (config as unknown as Record<string, boolean>)[flag],
359
+ `caps.${field} must equal config.${flag} (true variant)`,
360
+ );
361
+ assert.equal(caps[field], true, `caps.${field} should be true here`);
362
+ }
363
+ });
364
+
365
+ test("resolveMemoryLifecycleCapabilities projects every field from its flag (false variant)", () => {
366
+ const overrides: Record<string, boolean> = {};
367
+ for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = false;
368
+ const config = parseConfig(overrides);
369
+ const caps = resolveMemoryLifecycleCapabilities(config);
370
+
371
+ for (const field of LIFECYCLE_FIELDS) {
372
+ const flag = LIFECYCLE_FIELD_TO_FLAG[field];
373
+ assert.equal(
374
+ caps[field],
375
+ (config as unknown as Record<string, boolean>)[flag],
376
+ `caps.${field} must equal config.${flag} (false variant)`,
377
+ );
378
+ assert.equal(caps[field], false, `caps.${field} should be false here`);
379
+ }
380
+ });
381
+
382
+ test("resolveMemoryLifecycleCapabilities returns a frozen object", () => {
383
+ const config = parseConfig({});
384
+ const caps = resolveMemoryLifecycleCapabilities(config);
385
+ assert.equal(Object.isFrozen(caps), true);
386
+ });
387
+
388
+ test("resolveMemoryLifecycleCapabilities matches pre-migration config reads (parity contract)", () => {
389
+ // For representative on/off combinations, the caps-resolved values MUST be
390
+ // identical to what the old scattered `this.config.<flag>Enabled` reads
391
+ // produced. All ten flags used bare reads (no `!== false`/`=== true` at the
392
+ // call site that would change the value — the two sites that wrote
393
+ // `=== true` were redundant coercion on an already-boolean config field),
394
+ // so parity is a direct equality against the resolved config boolean.
395
+ const cases: Record<string, Record<string, boolean>> = {
396
+ "all-off": {
397
+ temporalSupersessionEnabled: false,
398
+ temporalMemoryTreeEnabled: false,
399
+ lifecyclePolicyEnabled: false,
400
+ lifecycleFilterStaleEnabled: false,
401
+ lifecycleMetricsEnabled: false,
402
+ extractionScopeClassificationEnabled: false,
403
+ extractionJudgeEnabled: false,
404
+ extractionDedupeEnabled: false,
405
+ extractionTelemetryPrefilterEnabled: false,
406
+ extractionJudgeTelemetryEnabled: false,
407
+ embeddingFallbackEnabled: false,
408
+ },
409
+ "all-on": {
410
+ temporalSupersessionEnabled: true,
411
+ temporalMemoryTreeEnabled: true,
412
+ lifecyclePolicyEnabled: true,
413
+ lifecycleFilterStaleEnabled: true,
414
+ lifecycleMetricsEnabled: true,
415
+ extractionScopeClassificationEnabled: true,
416
+ extractionJudgeEnabled: true,
417
+ extractionDedupeEnabled: true,
418
+ extractionTelemetryPrefilterEnabled: true,
419
+ extractionJudgeTelemetryEnabled: true,
420
+ embeddingFallbackEnabled: true,
421
+ },
422
+ "temporal-on-rest-off": {
423
+ temporalSupersessionEnabled: true,
424
+ temporalMemoryTreeEnabled: true,
425
+ lifecyclePolicyEnabled: false,
426
+ lifecycleFilterStaleEnabled: false,
427
+ lifecycleMetricsEnabled: false,
428
+ extractionScopeClassificationEnabled: false,
429
+ extractionJudgeEnabled: false,
430
+ extractionDedupeEnabled: false,
431
+ extractionTelemetryPrefilterEnabled: false,
432
+ extractionJudgeTelemetryEnabled: false,
433
+ embeddingFallbackEnabled: false,
434
+ },
435
+ "extraction-on-rest-off": {
436
+ temporalSupersessionEnabled: false,
437
+ temporalMemoryTreeEnabled: false,
438
+ lifecyclePolicyEnabled: false,
439
+ lifecycleFilterStaleEnabled: false,
440
+ lifecycleMetricsEnabled: false,
441
+ extractionScopeClassificationEnabled: true,
442
+ extractionJudgeEnabled: true,
443
+ extractionDedupeEnabled: true,
444
+ extractionTelemetryPrefilterEnabled: true,
445
+ extractionJudgeTelemetryEnabled: true,
446
+ embeddingFallbackEnabled: true,
447
+ },
448
+ };
449
+
450
+ for (const [label, overrides] of Object.entries(cases)) {
451
+ const config = parseConfig(overrides);
452
+ const caps = resolveMemoryLifecycleCapabilities(config);
453
+
454
+ for (const field of LIFECYCLE_FIELDS) {
455
+ const flag = LIFECYCLE_FIELD_TO_FLAG[field];
456
+ assert.equal(
457
+ caps[field],
458
+ (config as unknown as Record<string, boolean>)[flag],
459
+ `[${label}] caps.${field} must equal config.${flag}`,
460
+ );
461
+ }
462
+ }
463
+ });
@@ -203,3 +203,94 @@ export function resolveAccessSetupCapabilities(
203
203
  recallAuditAnomalyDetection: config.recallAuditAnomalyDetectionEnabled === true,
204
204
  });
205
205
  }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Memory-lifecycle capability set (issue #1523 batch 3).
209
+ //
210
+ // The flags below gate memory WRITE/EXTRACTION (extraction judge, scope
211
+ // classification, dedupe, telemetry prefilter), temporal supersession of
212
+ // stored facts, and the lifecycle policy pass (promotion / decay / stale
213
+ // filtering). They are read on the persistExtraction write path, the recall
214
+ // candidate-filter path, and the maintenance / lifecycle-pass path — so, like
215
+ // the graph-construction set above, they get their own projection resolved
216
+ // ONCE at each operation entry that reads them (never re-derived from raw
217
+ // config mid-operation — the gate-divergence defect class #1523 targets).
218
+ //
219
+ // Every field projects from an already-resolved PluginConfig boolean (defaults
220
+ // are applied at the config-parse boundary, rule 36), so the resolver is a
221
+ // pure projection — no `!== false` / `=== true` re-coercion here.
222
+ // ---------------------------------------------------------------------------
223
+
224
+ /**
225
+ * Frozen projection of memory-lifecycle feature gates (issue #1523 batch 3).
226
+ *
227
+ * Resolved once per write / recall-filter / maintenance operation and threaded
228
+ * down. Composition lives ONLY in {@link resolveMemoryLifecycleCapabilities}.
229
+ */
230
+ export interface MemoryLifecycleCapabilitySet {
231
+ /** `temporalSupersessionEnabled` — supersede stale structured facts on write, filter on recall. */
232
+ readonly temporalSupersession: boolean;
233
+ /** `temporalMemoryTreeEnabled` — temporal-memory-tree build + recall tier. */
234
+ readonly temporalMemoryTree: boolean;
235
+ /** `lifecyclePolicyEnabled` — lifecycle promotion / decay metadata pass. */
236
+ readonly lifecyclePolicy: boolean;
237
+ /** `lifecycleFilterStaleEnabled` — filter stale-lifecycle memories from recall. */
238
+ readonly lifecycleFilterStale: boolean;
239
+ /** `lifecycleMetricsEnabled` — emit lifecycle-pass metrics. */
240
+ readonly lifecycleMetrics: boolean;
241
+ /** `extractionScopeClassificationEnabled` — LLM classifies each fact's scope. */
242
+ readonly extractionScopeClassification: boolean;
243
+ /** `extractionJudgeEnabled` — LLM-as-judge fact-worthiness gate. */
244
+ readonly extractionJudge: boolean;
245
+ /** `extractionDedupeEnabled` — dedupe against recent extractions. */
246
+ readonly extractionDedupe: boolean;
247
+ /** `extractionTelemetryPrefilterEnabled` — skip mechanical-telemetry transcripts. */
248
+ readonly extractionTelemetryPrefilter: boolean;
249
+ /** `extractionJudgeTelemetryEnabled` — collect judge verdict telemetry. */
250
+ readonly extractionJudgeTelemetry: boolean;
251
+ /** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */
252
+ readonly embeddingFallback: boolean;
253
+ }
254
+
255
+ /**
256
+ * Config projection consumed by {@link resolveMemoryLifecycleCapabilities}.
257
+ */
258
+ export type MemoryLifecycleConfigProjection = Pick<
259
+ PluginConfig,
260
+ | "temporalSupersessionEnabled"
261
+ | "temporalMemoryTreeEnabled"
262
+ | "lifecyclePolicyEnabled"
263
+ | "lifecycleFilterStaleEnabled"
264
+ | "lifecycleMetricsEnabled"
265
+ | "extractionScopeClassificationEnabled"
266
+ | "extractionJudgeEnabled"
267
+ | "extractionDedupeEnabled"
268
+ | "extractionTelemetryPrefilterEnabled"
269
+ | "extractionJudgeTelemetryEnabled"
270
+ | "embeddingFallbackEnabled"
271
+ >;
272
+
273
+ /**
274
+ * Resolve the {@link MemoryLifecycleCapabilitySet} from parsed config.
275
+ *
276
+ * Call this ONCE per write / recall-filter / maintenance operation entry and
277
+ * thread the result down — exactly the pattern {@link resolveCapabilities}
278
+ * established for recall.
279
+ */
280
+ export function resolveMemoryLifecycleCapabilities(
281
+ config: MemoryLifecycleConfigProjection,
282
+ ): MemoryLifecycleCapabilitySet {
283
+ return Object.freeze({
284
+ temporalSupersession: config.temporalSupersessionEnabled,
285
+ temporalMemoryTree: config.temporalMemoryTreeEnabled,
286
+ lifecyclePolicy: config.lifecyclePolicyEnabled,
287
+ lifecycleFilterStale: config.lifecycleFilterStaleEnabled,
288
+ lifecycleMetrics: config.lifecycleMetricsEnabled,
289
+ extractionScopeClassification: config.extractionScopeClassificationEnabled,
290
+ extractionJudge: config.extractionJudgeEnabled,
291
+ extractionDedupe: config.extractionDedupeEnabled,
292
+ extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,
293
+ extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,
294
+ embeddingFallback: config.embeddingFallbackEnabled,
295
+ });
296
+ }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { strict as assert } from "node:assert";
10
- import { mkdir, rm, readFile } from "node:fs/promises";
10
+ import { mkdir, rm, readFile, utimes, unlink, appendFile } from "node:fs/promises";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
13
  import { test } from "node:test";
@@ -22,6 +22,7 @@ import {
22
22
  sessionBelongsToPrincipal,
23
23
  chatSessionFile,
24
24
  cleanupExpiredChatSessions,
25
+ __resetTranscriptSeqForTest,
25
26
  } from "./chat-session.js";
26
27
  import type { ChatSessionState } from "./chat-types.js";
27
28
  import type { ChatToolExecutor } from "./chat-engine.js";
@@ -270,3 +271,105 @@ test("budget exhaustion summary LLM failure does not crash (Thread 16)", async (
270
271
  assert.ok(result.reply.length > 0, "Should return a fallback reply");
271
272
  assert.ok(result.skippedTools !== undefined, "Should report skipped tools");
272
273
  });
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // TTL boundary (issue #1685 item 1 / #1687 Thread 21)
277
+ // ---------------------------------------------------------------------------
278
+
279
+ test("cleanupExpiredChatSessions honors TTL boundary [0, ttlHours) — age >= ttl expires", async () => {
280
+ const dir = await makeTempDir();
281
+ const ttlHours = 2;
282
+ const ttlMs = ttlHours * 3600 * 1000;
283
+
284
+ // Session aged past the TTL (mtime set to ttlHours ago; by the time the
285
+ // sweep stat()s it a few ms have elapsed, so age >= ttl deterministically
286
+ // — the boundary is inclusive: age ∈ [0, ttl) survives, age >= ttl expires).
287
+ const expired = await createChatSession(dir, { principal: "expired" });
288
+ const expiredFile = chatSessionFile(dir, expired.id);
289
+ const atTtlSeconds = (Date.now() - ttlMs) / 1000;
290
+ await utimes(expiredFile, atTtlSeconds, atTtlSeconds);
291
+
292
+ // Session aged well under the TTL → must survive.
293
+ const fresh = await createChatSession(dir, { principal: "fresh" });
294
+ const freshFile = chatSessionFile(dir, fresh.id);
295
+ const underTtlSeconds = (Date.now() - (ttlMs - 6 * 60 * 1000)) / 1000; // ~1.9h old
296
+ await utimes(freshFile, underTtlSeconds, underTtlSeconds);
297
+
298
+ const removed = await cleanupExpiredChatSessions(dir, ttlHours);
299
+ assert.equal(removed, 1, "only the expired session should be removed");
300
+
301
+ // The fresh session file must still be on disk.
302
+ const freshStillExists = await readFile(freshFile, "utf8").then(() => true).catch(() => false);
303
+ assert.ok(freshStillExists, "session aged under the TTL must survive the sweep");
304
+
305
+ // The expired session file must be gone.
306
+ const expiredGone = await readFile(expiredFile, "utf8").then(() => false).catch(() => true);
307
+ assert.ok(expiredGone, "session aged at/over the TTL must be swept");
308
+ });
309
+
310
+
311
+ test("appendTranscriptEntry emits strictly-increasing seqs even within the same millisecond (issue #1687 review)", async () => {
312
+ const dir = await makeTempDir();
313
+ const session = await createChatSession(dir, { principal: "seq" });
314
+ // Append several entries as fast as possible (same-ms collisions would have
315
+ // shared a Date.now()-based seq before the monotonic counter fix).
316
+ const entries: number[] = [];
317
+ for (let i = 0; i < 8; i++) {
318
+ const e = await appendTranscriptEntry(dir, session.id, { role: "user", content: `m${i}` });
319
+ entries.push(e.seq);
320
+ }
321
+ // Every seq must be distinct and strictly increasing.
322
+ for (let i = 1; i < entries.length; i++) {
323
+ assert.ok(entries[i]! > entries[i - 1]!, `seq ${entries[i]} must exceed ${entries[i - 1]}`);
324
+ }
325
+ assert.equal(new Set(entries).size, entries.length, "seqs must be unique");
326
+ });
327
+
328
+ test("appendTranscriptEntry refuses to recreate a swept session (chat_session_expired) — codex P2 #1687", async () => {
329
+ const dir = await makeTempDir();
330
+ const session = await createChatSession(dir, { principal: "alice" });
331
+ // Simulate the TTL sweep unlinking the file mid-turn (resurrection race).
332
+ await unlink(chatSessionFile(dir, session.id));
333
+ // Must throw instead of silently recreating a headerless (public) file.
334
+ await assert.rejects(
335
+ appendTranscriptEntry(dir, session.id, { role: "user", content: "x" }),
336
+ /chat_session_expired/,
337
+ );
338
+ });
339
+
340
+
341
+ test("loadChatSession raises the seq counter above the on-disk max on backward clock skew (issue #1718)", async () => {
342
+ const dir = await makeTempDir();
343
+ const session = await createChatSession(dir, { principal: "skew" });
344
+ // Simulate a previous process that ran with a forward-skewed clock: append a
345
+ // transcript line whose seq is far in the future relative to the current
346
+ // Date.now(). A later restart with a normal (or backward-stepped) clock must
347
+ // still issue seqs strictly greater than this persisted line.
348
+ const futureSeq = Date.now() + 10_000_000;
349
+ await appendFile(
350
+ chatSessionFile(dir, session.id),
351
+ JSON.stringify({
352
+ seq: futureSeq,
353
+ ts: new Date().toISOString(),
354
+ role: "user",
355
+ content: "future-dated line from a forward-skewed prior process",
356
+ }) + "\n",
357
+ "utf8",
358
+ );
359
+ // Simulate a process restart whose module-level counter starts below the
360
+ // persisted on-disk seq (the backward-clock-skew window).
361
+ __resetTranscriptSeqForTest();
362
+ // Resume the session: loadChatSession must raise the counter above the
363
+ // on-disk max so the next append is strictly greater, regardless of the
364
+ // current Date.now().
365
+ const loaded = await loadChatSession(dir, session.id);
366
+ assert.ok(loaded, "session must load");
367
+ const next = await appendTranscriptEntry(dir, session.id, {
368
+ role: "assistant",
369
+ content: "resumed after backward clock skew",
370
+ });
371
+ assert.ok(
372
+ next.seq > futureSeq,
373
+ `seq ${next.seq} must exceed the on-disk max ${futureSeq} after a backward clock skew (issue #1718)`,
374
+ );
375
+ });