@remnic/core 9.3.705 → 9.3.706

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 (259) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +10 -10
  3. package/dist/access-cli.js +26 -25
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +5 -5
  6. package/dist/access-http.js +14 -14
  7. package/dist/access-mcp.d.ts +5 -5
  8. package/dist/access-mcp.js +13 -13
  9. package/dist/access-operations-batch.js +11 -11
  10. package/dist/access-operations.d.ts +5 -5
  11. package/dist/access-operations.js +12 -12
  12. package/dist/{access-service-C8hc8N2i.d.ts → access-service-j1c1gptF.d.ts} +3 -3
  13. package/dist/access-service.d.ts +5 -5
  14. package/dist/access-service.js +9 -9
  15. package/dist/access-surface-catalog.d.ts +5 -5
  16. package/dist/action-confidence.d.ts +1 -1
  17. package/dist/active-memory-bridge.d.ts +1 -1
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/behavior-learner.d.ts +1 -1
  20. package/dist/behavior-signals.d.ts +1 -1
  21. package/dist/bootstrap.d.ts +4 -4
  22. package/dist/briefing.d.ts +1 -1
  23. package/dist/briefing.js +5 -5
  24. package/dist/buffer-surprise-report.d.ts +1 -1
  25. package/dist/buffer.d.ts +1 -1
  26. package/dist/calibration.d.ts +1 -1
  27. package/dist/capabilities.d.ts +1 -1
  28. package/dist/{catalog-CAeU0e1C.d.ts → catalog-CKxilpzS.d.ts} +1 -1
  29. package/dist/causal-behavior.d.ts +1 -1
  30. package/dist/causal-consolidation.d.ts +1 -1
  31. package/dist/causal-consolidation.js +6 -6
  32. package/dist/{chunk-JZCKDTAS.js → chunk-24FGNOQS.js} +3 -3
  33. package/dist/{chunk-C2EHWNP2.js → chunk-2VQYHHWB.js} +9 -3
  34. package/dist/chunk-2VQYHHWB.js.map +1 -0
  35. package/dist/{chunk-47LPHBIP.js → chunk-3PTOZJOI.js} +2 -2
  36. package/dist/{chunk-2SYPOX45.js → chunk-4DZATVK5.js} +3 -3
  37. package/dist/{chunk-S5APSA3U.js → chunk-4E2QCH46.js} +2 -2
  38. package/dist/{chunk-KKK7YTYN.js → chunk-5GSUBCZM.js} +6 -2
  39. package/dist/chunk-5GSUBCZM.js.map +1 -0
  40. package/dist/{chunk-5DTWZ66X.js → chunk-66TSLESZ.js} +2 -2
  41. package/dist/{chunk-IREVVFTM.js → chunk-6CIAGZ6X.js} +9 -1
  42. package/dist/chunk-6CIAGZ6X.js.map +1 -0
  43. package/dist/{chunk-Y4PMATX2.js → chunk-6HPJMR5I.js} +2 -2
  44. package/dist/{chunk-M5MWSG4S.js → chunk-BGAHTI4C.js} +5 -5
  45. package/dist/{chunk-OM6ETLLH.js → chunk-C63MK3WL.js} +2 -2
  46. package/dist/{chunk-GT4VBKM4.js → chunk-EJDAXR7O.js} +5 -5
  47. package/dist/{chunk-ULVM34NP.js → chunk-ESMY4RJ4.js} +2 -2
  48. package/dist/{chunk-MNH2OODZ.js → chunk-GKI6LX5L.js} +2 -2
  49. package/dist/{chunk-KZMMAVR2.js → chunk-HIV5E57C.js} +2 -2
  50. package/dist/{chunk-SFOAQQDJ.js → chunk-IONFO7UK.js} +3 -3
  51. package/dist/{chunk-BMKGG7GC.js → chunk-IVCQW4C4.js} +2 -2
  52. package/dist/chunk-M7R5R6OJ.js +355 -0
  53. package/dist/chunk-M7R5R6OJ.js.map +1 -0
  54. package/dist/{chunk-IX3UQT4H.js → chunk-MLLTU5FX.js} +1 -1
  55. package/dist/chunk-MLLTU5FX.js.map +1 -0
  56. package/dist/{chunk-NFGJBEPT.js → chunk-MRX6ZXHZ.js} +2 -2
  57. package/dist/{chunk-GI3MTYDU.js → chunk-N75N5SNX.js} +3 -3
  58. package/dist/{chunk-BKBSE3BU.js → chunk-NM6TSEGQ.js} +2 -2
  59. package/dist/{chunk-S3WYRPSA.js → chunk-PDJQVAGM.js} +68 -31
  60. package/dist/chunk-PDJQVAGM.js.map +1 -0
  61. package/dist/{chunk-SY6WG6DB.js → chunk-RH2OSRQY.js} +2 -2
  62. package/dist/{chunk-YTMDF6S7.js → chunk-SPETAWFE.js} +2 -2
  63. package/dist/{chunk-JHAYR2JY.js → chunk-STYMKPFT.js} +7 -7
  64. package/dist/{chunk-OMNTQPZO.js → chunk-TKZ3AL5V.js} +2 -2
  65. package/dist/{chunk-IFR23TC5.js → chunk-VYZNWH4J.js} +37 -6
  66. package/dist/chunk-VYZNWH4J.js.map +1 -0
  67. package/dist/{chunk-KFU2SKDD.js → chunk-W2S3Z5MT.js} +2 -2
  68. package/dist/{chunk-6GJ2WQRK.js → chunk-W2WQ4LE7.js} +2 -2
  69. package/dist/{chunk-7ARKLLCV.js → chunk-WDH3KUZU.js} +2 -2
  70. package/dist/{chunk-7L54YZUI.js → chunk-WQADZ3ZY.js} +17 -17
  71. package/dist/{chunk-HEHPVBWA.js → chunk-WSWYIKXX.js} +4 -4
  72. package/dist/{chunk-WN4GHSDH.js → chunk-XKMDDM7P.js} +2 -2
  73. package/dist/{chunk-LY4FJLPT.js → chunk-Y6YHAGQP.js} +2 -2
  74. package/dist/{cli-ChE0XlJY.d.ts → cli-CbT-pyM4.d.ts} +3 -3
  75. package/dist/cli.d.ts +6 -6
  76. package/dist/cli.js +26 -26
  77. package/dist/compounding/engine.d.ts +1 -1
  78. package/dist/compounding/engine.js +5 -5
  79. package/dist/compounding/preference-consolidator.d.ts +1 -1
  80. package/dist/compression-optimizer.d.ts +1 -1
  81. package/dist/config.d.ts +1 -1
  82. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  83. package/dist/connectors/codex-materialize-runner.js +5 -5
  84. package/dist/connectors/codex-materialize.d.ts +1 -1
  85. package/dist/connectors/index.d.ts +1 -1
  86. package/dist/connectors/index.js +5 -5
  87. package/dist/consolidation-provenance-check.d.ts +1 -1
  88. package/dist/consolidation-undo.d.ts +1 -1
  89. package/dist/contradiction/index.d.ts +1 -1
  90. package/dist/conversation-index/backend.d.ts +1 -1
  91. package/dist/conversation-index/chunker.d.ts +1 -1
  92. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  93. package/dist/conversation-index/indexer.d.ts +1 -1
  94. package/dist/conversation-index/search.d.ts +1 -1
  95. package/dist/day-summary.d.ts +1 -1
  96. package/dist/delinearize.d.ts +1 -1
  97. package/dist/direct-answer-wiring.d.ts +1 -1
  98. package/dist/direct-answer.d.ts +1 -1
  99. package/dist/embedding-fallback.d.ts +1 -1
  100. package/dist/enrichment/index.d.ts +1 -1
  101. package/dist/entity-retrieval.d.ts +1 -1
  102. package/dist/entity-retrieval.js +5 -5
  103. package/dist/entity-schema.d.ts +1 -1
  104. package/dist/event-time.d.ts +35 -1
  105. package/dist/event-time.js +6 -320
  106. package/dist/event-time.js.map +1 -1
  107. package/dist/explicit-capture.d.ts +4 -4
  108. package/dist/extraction-faithfulness.d.ts +1 -1
  109. package/dist/extraction-judge-telemetry.d.ts +1 -1
  110. package/dist/extraction-judge-training.d.ts +1 -1
  111. package/dist/extraction-judge.d.ts +1 -1
  112. package/dist/extraction.d.ts +10 -1
  113. package/dist/extraction.js +2 -2
  114. package/dist/fallback-llm.d.ts +1 -1
  115. package/dist/{forget-6SOIPUMQ.js → forget-U66KPDPY.js} +3 -3
  116. package/dist/identity-continuity.d.ts +1 -1
  117. package/dist/importance.d.ts +1 -1
  118. package/dist/index.d.ts +9 -9
  119. package/dist/index.js +35 -34
  120. package/dist/index.js.map +1 -1
  121. package/dist/intent.d.ts +1 -1
  122. package/dist/lcm/engine.d.ts +1 -1
  123. package/dist/lcm/index.d.ts +1 -1
  124. package/dist/lcm/tools.d.ts +1 -1
  125. package/dist/lifecycle.d.ts +1 -1
  126. package/dist/live-connectors-runner.d.ts +1 -1
  127. package/dist/local-llm.d.ts +1 -1
  128. package/dist/maintenance/memory-governance.d.ts +1 -1
  129. package/dist/maintenance/memory-governance.js +5 -5
  130. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  131. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  132. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  133. package/dist/memory-action-policy.d.ts +1 -1
  134. package/dist/memory-cache.d.ts +1 -1
  135. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  136. package/dist/memory-projection-store.d.ts +1 -1
  137. package/dist/memory-provenance.d.ts +1 -1
  138. package/dist/memory-worth-outcomes.d.ts +1 -1
  139. package/dist/models-json.d.ts +1 -1
  140. package/dist/namespaces/migrate.d.ts +2 -2
  141. package/dist/namespaces/migrate.js +6 -6
  142. package/dist/namespaces/principal.d.ts +1 -1
  143. package/dist/namespaces/search.d.ts +1 -1
  144. package/dist/namespaces/storage.d.ts +2 -2
  145. package/dist/namespaces/storage.js +5 -5
  146. package/dist/native-knowledge.d.ts +1 -1
  147. package/dist/operator-toolkit.d.ts +1 -1
  148. package/dist/operator-toolkit.js +10 -10
  149. package/dist/orchestration/maintenance.d.ts +2 -2
  150. package/dist/orchestration/maintenance.js +7 -7
  151. package/dist/{orchestrator-m_PnhyHj.d.ts → orchestrator-D4ovYV3x.d.ts} +3 -3
  152. package/dist/orchestrator.d.ts +4 -4
  153. package/dist/orchestrator.js +20 -19
  154. package/dist/patterns-cli.d.ts +1 -1
  155. package/dist/policy-runtime.d.ts +1 -1
  156. package/dist/provenance.d.ts +1 -1
  157. package/dist/qmd-recall-cache.d.ts +1 -1
  158. package/dist/qmd.d.ts +1 -1
  159. package/dist/recall-disclosure-escalation.d.ts +1 -1
  160. package/dist/recall-explain-renderer.d.ts +1 -1
  161. package/dist/recall-explain-renderer.js +3 -3
  162. package/dist/recall-planner-llm.d.ts +1 -1
  163. package/dist/recall-state.d.ts +1 -1
  164. package/dist/recall-tag-filter.d.ts +1 -1
  165. package/dist/recall-xray-cli.d.ts +1 -1
  166. package/dist/recall-xray-cli.js +4 -4
  167. package/dist/recall-xray-renderer.d.ts +1 -1
  168. package/dist/recall-xray-renderer.js +3 -3
  169. package/dist/recall-xray.d.ts +1 -1
  170. package/dist/recall-xray.js +2 -2
  171. package/dist/resolve-auth-token.d.ts +1 -1
  172. package/dist/retrieval-agents.d.ts +1 -1
  173. package/dist/retrieval-tiers.d.ts +1 -1
  174. package/dist/routing/engine.d.ts +1 -1
  175. package/dist/routing/store.d.ts +1 -1
  176. package/dist/schemas.d.ts +38 -0
  177. package/dist/schemas.js +1 -1
  178. package/dist/search/embed-helper.d.ts +1 -1
  179. package/dist/search/factory.d.ts +1 -1
  180. package/dist/search/index.d.ts +1 -1
  181. package/dist/search/lancedb-backend.d.ts +1 -1
  182. package/dist/search/meilisearch-backend.d.ts +1 -1
  183. package/dist/search/noop-backend.d.ts +1 -1
  184. package/dist/search/orama-backend.d.ts +1 -1
  185. package/dist/search/port.d.ts +1 -1
  186. package/dist/search/remote-backend.d.ts +1 -1
  187. package/dist/{semantic-consolidation-DVBbq6Wr.d.ts → semantic-consolidation-DyMUCsfN.d.ts} +1 -1
  188. package/dist/semantic-consolidation.d.ts +2 -2
  189. package/dist/semantic-consolidation.js +6 -6
  190. package/dist/semantic-rule-promotion.js +5 -5
  191. package/dist/semantic-rule-verifier.d.ts +1 -1
  192. package/dist/semantic-rule-verifier.js +5 -5
  193. package/dist/session-observer-bands.d.ts +1 -1
  194. package/dist/session-observer-state.d.ts +1 -1
  195. package/dist/shared-context/manager.d.ts +1 -1
  196. package/dist/signal.d.ts +1 -1
  197. package/dist/storage.d.ts +5 -1
  198. package/dist/storage.js +4 -4
  199. package/dist/summarizer.d.ts +1 -1
  200. package/dist/summary-snapshot.d.ts +1 -1
  201. package/dist/temporal-supersession.d.ts +1 -1
  202. package/dist/temporal-supersession.js +2 -2
  203. package/dist/temporal-validity.d.ts +2 -2
  204. package/dist/temporal-validity.js +1 -1
  205. package/dist/threading.d.ts +1 -1
  206. package/dist/tier-migration.d.ts +1 -1
  207. package/dist/tier-routing.d.ts +1 -1
  208. package/dist/topics.d.ts +1 -1
  209. package/dist/transcript.d.ts +1 -1
  210. package/dist/{types-CJrvHJjx.d.ts → types-Couvz-L3.d.ts} +12 -2
  211. package/dist/types.d.ts +1 -1
  212. package/dist/types.js +1 -1
  213. package/dist/utility-runtime.d.ts +1 -1
  214. package/dist/verified-recall.js +5 -5
  215. package/package.json +2 -2
  216. package/src/event-time.ts +73 -0
  217. package/src/extraction-eventtime-wiring.test.ts +269 -0
  218. package/src/extraction.ts +50 -3
  219. package/src/orchestrator.ts +71 -17
  220. package/src/schemas.ts +12 -0
  221. package/src/storage.ts +13 -2
  222. package/src/temporal-validity.test.ts +33 -0
  223. package/src/temporal-validity.ts +24 -1
  224. package/src/types.ts +12 -2
  225. package/dist/chunk-C2EHWNP2.js.map +0 -1
  226. package/dist/chunk-IFR23TC5.js.map +0 -1
  227. package/dist/chunk-IREVVFTM.js.map +0 -1
  228. package/dist/chunk-IX3UQT4H.js.map +0 -1
  229. package/dist/chunk-KKK7YTYN.js.map +0 -1
  230. package/dist/chunk-S3WYRPSA.js.map +0 -1
  231. /package/dist/{chunk-JZCKDTAS.js.map → chunk-24FGNOQS.js.map} +0 -0
  232. /package/dist/{chunk-47LPHBIP.js.map → chunk-3PTOZJOI.js.map} +0 -0
  233. /package/dist/{chunk-2SYPOX45.js.map → chunk-4DZATVK5.js.map} +0 -0
  234. /package/dist/{chunk-S5APSA3U.js.map → chunk-4E2QCH46.js.map} +0 -0
  235. /package/dist/{chunk-5DTWZ66X.js.map → chunk-66TSLESZ.js.map} +0 -0
  236. /package/dist/{chunk-Y4PMATX2.js.map → chunk-6HPJMR5I.js.map} +0 -0
  237. /package/dist/{chunk-M5MWSG4S.js.map → chunk-BGAHTI4C.js.map} +0 -0
  238. /package/dist/{chunk-OM6ETLLH.js.map → chunk-C63MK3WL.js.map} +0 -0
  239. /package/dist/{chunk-GT4VBKM4.js.map → chunk-EJDAXR7O.js.map} +0 -0
  240. /package/dist/{chunk-ULVM34NP.js.map → chunk-ESMY4RJ4.js.map} +0 -0
  241. /package/dist/{chunk-MNH2OODZ.js.map → chunk-GKI6LX5L.js.map} +0 -0
  242. /package/dist/{chunk-KZMMAVR2.js.map → chunk-HIV5E57C.js.map} +0 -0
  243. /package/dist/{chunk-SFOAQQDJ.js.map → chunk-IONFO7UK.js.map} +0 -0
  244. /package/dist/{chunk-BMKGG7GC.js.map → chunk-IVCQW4C4.js.map} +0 -0
  245. /package/dist/{chunk-NFGJBEPT.js.map → chunk-MRX6ZXHZ.js.map} +0 -0
  246. /package/dist/{chunk-GI3MTYDU.js.map → chunk-N75N5SNX.js.map} +0 -0
  247. /package/dist/{chunk-BKBSE3BU.js.map → chunk-NM6TSEGQ.js.map} +0 -0
  248. /package/dist/{chunk-SY6WG6DB.js.map → chunk-RH2OSRQY.js.map} +0 -0
  249. /package/dist/{chunk-YTMDF6S7.js.map → chunk-SPETAWFE.js.map} +0 -0
  250. /package/dist/{chunk-JHAYR2JY.js.map → chunk-STYMKPFT.js.map} +0 -0
  251. /package/dist/{chunk-OMNTQPZO.js.map → chunk-TKZ3AL5V.js.map} +0 -0
  252. /package/dist/{chunk-KFU2SKDD.js.map → chunk-W2S3Z5MT.js.map} +0 -0
  253. /package/dist/{chunk-6GJ2WQRK.js.map → chunk-W2WQ4LE7.js.map} +0 -0
  254. /package/dist/{chunk-7ARKLLCV.js.map → chunk-WDH3KUZU.js.map} +0 -0
  255. /package/dist/{chunk-7L54YZUI.js.map → chunk-WQADZ3ZY.js.map} +0 -0
  256. /package/dist/{chunk-HEHPVBWA.js.map → chunk-WSWYIKXX.js.map} +0 -0
  257. /package/dist/{chunk-WN4GHSDH.js.map → chunk-XKMDDM7P.js.map} +0 -0
  258. /package/dist/{chunk-LY4FJLPT.js.map → chunk-Y6YHAGQP.js.map} +0 -0
  259. /package/dist/{forget-6SOIPUMQ.js.map → forget-U66KPDPY.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { k as MemoryFrontmatter } from './types-CJrvHJjx.js';
1
+ import { k as MemoryFrontmatter } from './types-Couvz-L3.js';
2
2
  import { StorageManager } from './storage.js';
3
3
  import './types-gb_SNtjR.js';
4
4
  import './message-parts/index.js';
@@ -6,8 +6,8 @@ import {
6
6
  shouldFilterSupersededFromRecall,
7
7
  shouldSupersedeExisting,
8
8
  supersessionKeysForFact
9
- } from "./chunk-OMNTQPZO.js";
10
- import "./chunk-IREVVFTM.js";
9
+ } from "./chunk-TKZ3AL5V.js";
10
+ import "./chunk-6CIAGZ6X.js";
11
11
  import "./chunk-NU3CSQ4H.js";
12
12
  import "./chunk-PZ5AY32C.js";
13
13
  export {
@@ -1,4 +1,4 @@
1
- import { k as MemoryFrontmatter } from './types-CJrvHJjx.js';
1
+ import { k as MemoryFrontmatter } from './types-Couvz-L3.js';
2
2
  import './types-gb_SNtjR.js';
3
3
  import './message-parts/index.js';
4
4
 
@@ -42,7 +42,7 @@ declare function effectiveInvalidAt(fm: Pick<MemoryFrontmatter, "invalid_at" | "
42
42
  * the historical filter (CLAUDE.md gotcha #34: distinguish empty from
43
43
  * malformed).
44
44
  */
45
- declare function isValidAsOf(fm: Pick<MemoryFrontmatter, "valid_at" | "invalid_at" | "created" | "supersededAt" | "status">, asOfMs: number): boolean;
45
+ declare function isValidAsOf(fm: Pick<MemoryFrontmatter, "valid_at" | "invalid_at" | "created" | "supersededAt" | "status" | "eventTimeSource">, asOfMs: number): boolean;
46
46
  /**
47
47
  * Bi-temporal injection filter (issue #1578).
48
48
  *
@@ -4,7 +4,7 @@ import {
4
4
  isValidAsOf,
5
5
  isValidityExpiredNow,
6
6
  parseAsOfTimestamp
7
- } from "./chunk-IREVVFTM.js";
7
+ } from "./chunk-6CIAGZ6X.js";
8
8
  import "./chunk-PZ5AY32C.js";
9
9
  export {
10
10
  effectiveInvalidAt,
@@ -1,4 +1,4 @@
1
- import { u as BufferTurn, am as ConversationThread } from './types-CJrvHJjx.js';
1
+ import { u as BufferTurn, am as ConversationThread } from './types-Couvz-L3.js';
2
2
  import './types-gb_SNtjR.js';
3
3
  import './message-parts/index.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { g as MemoryFile } from './types-CJrvHJjx.js';
1
+ import { g as MemoryFile } from './types-Couvz-L3.js';
2
2
  import { StorageManager } from './storage.js';
3
3
  import { MemoryTier } from './tier-routing.js';
4
4
  import { SearchBackend } from './search/port.js';
@@ -1,4 +1,4 @@
1
- import { g as MemoryFile } from './types-CJrvHJjx.js';
1
+ import { g as MemoryFile } from './types-Couvz-L3.js';
2
2
  import { LifecycleSignals } from './lifecycle.js';
3
3
  import './types-gb_SNtjR.js';
4
4
  import './message-parts/index.js';
package/dist/topics.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as MemoryFile, an as TopicScore } from './types-CJrvHJjx.js';
1
+ import { g as MemoryFile, an as TopicScore } from './types-Couvz-L3.js';
2
2
  import './types-gb_SNtjR.js';
3
3
  import './message-parts/index.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { P as PluginConfig, T as TranscriptEntry, v as Checkpoint } from './types-CJrvHJjx.js';
1
+ import { P as PluginConfig, T as TranscriptEntry, v as Checkpoint } from './types-Couvz-L3.js';
2
2
  import { SessionIntegrityReport } from './session-integrity.js';
3
3
  import './types-gb_SNtjR.js';
4
4
  import './message-parts/index.js';
@@ -1084,8 +1084,9 @@ interface PluginConfig {
1084
1084
  * Turn on to exclude validity-expired facts from default injection
1085
1085
  * candidates (they remain findable by explicit search and `as_of`
1086
1086
  * queries). Storage round-trips `observedAt`/`eventTimeSource`; the
1087
- * extraction prompt `resolveEventTime` write-time wiring lands in a
1088
- * follow-on PR, so new extractions are not yet event-time-resolved.
1087
+ * extraction prompt emits an optional per-fact `eventTime` expression
1088
+ * and `resolveFactEventTime` resolves it against the source turn
1089
+ * timestamp at write time (#1578 PR2).
1089
1090
  */
1090
1091
  temporalBiTemporal: boolean;
1091
1092
  /**
@@ -2987,6 +2988,15 @@ interface ExtractedFact {
2987
2988
  * walked through. Persisted under reasoning-traces/.
2988
2989
  */
2989
2990
  reasoningTrace?: ExtractedReasoningTrace;
2991
+ /**
2992
+ * Optional event-time expression for bi-temporal validity (#1578 PR2).
2993
+ * Verbatim temporal anchor extracted from the conversation ("2025-03-01",
2994
+ * "last March", "since 2024", "until 2025-06-01"). Resolved against the
2995
+ * source turn timestamp at write time by `resolveFactEventTime` — never
2996
+ * re-resolved at read. Absent when the fact carries no temporal anchor or
2997
+ * when `temporal.biTemporal` is off.
2998
+ */
2999
+ eventTime?: string;
2990
3000
  }
2991
3001
  interface ExtractedReasoningTraceStep {
2992
3002
  order: number;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import './types-gb_SNtjR.js';
2
2
  import './message-parts/index.js';
3
- export { au as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking, J as AgentAccessAuthToken, b8 as AgentAccessHttpConfig, b9 as AgentDefaultsConfig, ba as AgentPersona, f as AgentPersonaModelConfig, bb as BehaviorLoopAdjustment, e as BehaviorLoopPolicyState, bc as BehaviorSignalDirection, B as BehaviorSignalEvent, bd as BehaviorSignalType, aH as BootstrapOptions, aI as BootstrapResult, r as BriefingActiveThread, be as BriefingCalendarSourceError, aU as BriefingConfig, p as BriefingFocus, o as BriefingFollowup, aV as BriefingOpenCommitment, t as BriefingRecentEntity, s as BriefingResult, n as BriefingSections, aW as BriefingWindow, bf as BufferEntryState, ap as BufferState, z as BufferSurpriseEvent, u as BufferTurn, q as CalendarEvent, C as CalendarSource, bg as CaptureMode, v as Checkpoint, bh as CodexCliReasoningEffort, bi as CodexCompactionFlushMode, aX as CodexCompatConfig, bj as CodexConnectorConfig, aG as CodingContext, bk as CodingKnowledgeConfig, aR as CodingModeConfig, bl as CompressionGuidelineActivationState, N as CompressionGuidelineOptimizerActionSummary, K as CompressionGuidelineOptimizerEventCounts, O as CompressionGuidelineOptimizerRuleUpdate, bm as CompressionGuidelineOptimizerSourceWindow, X as CompressionGuidelineOptimizerState, bn as ConfidenceTier, bo as ConsolidationAction, bp as ConsolidationItem, aJ as ConsolidationObservation, a1 as ConsolidationResult, a6 as ContinuityImprovementLoop, a4 as ContinuityIncidentCloseInput, a5 as ContinuityIncidentOpenInput, a3 as ContinuityIncidentRecord, bq as ContinuityIncidentState, br as ContinuityLoopCadence, a7 as ContinuityLoopReviewInput, bs as ContinuityLoopStatus, a8 as ContinuityLoopUpsertInput, bt as ContradictionScanConfig, am as ConversationThread, bu as CronConversationRecallMode, bv as CronRecallMode, aY as DEFAULT_RECALL_DISCLOSURE, a2 as DaySummaryResult, bw as DreamingConfig, bx as DreamingNarrativePromptStyle, by as DreamsDeepSleepConfig, bz as DreamsLightSleepConfig, aP as DreamsPhase, bA as DreamsPhaseStatus, bB as DreamsPhasesConfig, bC as DreamsRemConfig, aQ as DreamsRunResult, aO as DreamsStatusResult, bD as EngramTraceEvent, at as EntityActivityEntry, E as EntityFile, Y as EntityMention, as as EntityRelationship, Z as EntitySchemaDefinition, _ as EntitySchemaSectionDefinition, $ as EntityStructuredSection, aK as EntityTimelineEntry, aZ as ExtractedFact, bE as ExtractedProcedureStep, bF as ExtractedQuestion, bG as ExtractedReasoningTrace, bH as ExtractedReasoningTraceStep, bI as ExtractedRelationship, bJ as ExtractionPassSource, a0 as ExtractionResult, F as FaithfulnessFrontmatter, ar as FileHygieneConfig, G as GatewayConfig, bK as GitHubLiveConnectorConfig, bL as GmailLiveConnectorConfig, bM as GoogleDriveLiveConnectorConfig, bN as HeartbeatConfig, bO as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, y as ImportanceLevel, a9 as ImportanceScore, w as LifecycleState, L as LiveConnectorsConfig, bP as LlmTraceCallback, bQ as LlmTraceEvent, ac as MemoryActionEligibilityContext, a_ as MemoryActionEligibilitySource, W as MemoryActionEvent, bR as MemoryActionOutcome, bS as MemoryActionPolicyDecision, ad as MemoryActionPolicyResult, bT as MemoryActionStatus, U as MemoryActionType, M as MemoryCategory, g as MemoryFile, k as MemoryFrontmatter, aa as MemoryIntent, m as MemoryLifecycleEvent, ae as MemoryLifecycleEventType, af as MemoryLifecycleStateSummary, ao as MemoryLink, bU as MemoryLinkType, a$ as MemoryObservation, bV as MemoryOsPresetName, l as MemoryProjectionCurrentState, b0 as MemoryScope, h as MemoryStatus, av as MemorySummary, aq as MetaState, bW as ModelApi, bX as ModelDefinitionConfig, bY as ModelProviderAuthMode, ag as ModelProviderConfig, bZ as NamespacePolicy, ah as NativeKnowledgeConfig, b_ as NativeKnowledgeFolderRuleConfig, b$ as NativeKnowledgeObsidianVaultConfig, c0 as NativeKnowledgeOpenClawWorkspaceConfig, c1 as NotionLiveConnectorConfig, P as PluginConfig, x as PolicyClass, c2 as PrincipalFromSessionKeyMode, c3 as PrincipalRule, c4 as ProceduralConfig, aj as ProvenanceConfig, ai as ProvenanceSource, ab as QmdSearchExplain, Q as QmdSearchResult, c5 as QuestionEntry, b1 as RECALL_DISCLOSURE_LEVELS, c6 as ReasoningEffort, j as RecallDisclosure, c7 as RecallPipelineConfig, i as RecallPlanMode, c8 as RecallSectionConfig, R as RecallTierExplain, c9 as RecallTraceEvent, ca as RelevanceFeedback, ak as RetrievalTier, cb as SPECULATIVE_TTL_DAYS, cc as ScopeProfileAutoPromoteConfig, aN as ScopeProfileConfig, aL as ScopeProfileLayerId, aM as ScopeProfilePromotionTarget, cd as ScopeProfileTeamProjectConfig, ce as ScopeTeamConfig, cf as ScoredEntity, D as SecretRef, cg as SemanticChunkingConfigShape, S as SessionObserverBandConfig, ch as SignalLevel, al as SignalScanResult, ci as SlotBehaviorConfig, cj as SlotMismatchMode, an as TopicScore, T as TranscriptEntry, ck as TriggerMode, V as VerificationState, cl as confidenceTier, b7 as isRecallDisclosure } from './types-CJrvHJjx.js';
3
+ export { au as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking, J as AgentAccessAuthToken, b8 as AgentAccessHttpConfig, b9 as AgentDefaultsConfig, ba as AgentPersona, f as AgentPersonaModelConfig, bb as BehaviorLoopAdjustment, e as BehaviorLoopPolicyState, bc as BehaviorSignalDirection, B as BehaviorSignalEvent, bd as BehaviorSignalType, aH as BootstrapOptions, aI as BootstrapResult, r as BriefingActiveThread, be as BriefingCalendarSourceError, aU as BriefingConfig, p as BriefingFocus, o as BriefingFollowup, aV as BriefingOpenCommitment, t as BriefingRecentEntity, s as BriefingResult, n as BriefingSections, aW as BriefingWindow, bf as BufferEntryState, ap as BufferState, z as BufferSurpriseEvent, u as BufferTurn, q as CalendarEvent, C as CalendarSource, bg as CaptureMode, v as Checkpoint, bh as CodexCliReasoningEffort, bi as CodexCompactionFlushMode, aX as CodexCompatConfig, bj as CodexConnectorConfig, aG as CodingContext, bk as CodingKnowledgeConfig, aR as CodingModeConfig, bl as CompressionGuidelineActivationState, N as CompressionGuidelineOptimizerActionSummary, K as CompressionGuidelineOptimizerEventCounts, O as CompressionGuidelineOptimizerRuleUpdate, bm as CompressionGuidelineOptimizerSourceWindow, X as CompressionGuidelineOptimizerState, bn as ConfidenceTier, bo as ConsolidationAction, bp as ConsolidationItem, aJ as ConsolidationObservation, a1 as ConsolidationResult, a6 as ContinuityImprovementLoop, a4 as ContinuityIncidentCloseInput, a5 as ContinuityIncidentOpenInput, a3 as ContinuityIncidentRecord, bq as ContinuityIncidentState, br as ContinuityLoopCadence, a7 as ContinuityLoopReviewInput, bs as ContinuityLoopStatus, a8 as ContinuityLoopUpsertInput, bt as ContradictionScanConfig, am as ConversationThread, bu as CronConversationRecallMode, bv as CronRecallMode, aY as DEFAULT_RECALL_DISCLOSURE, a2 as DaySummaryResult, bw as DreamingConfig, bx as DreamingNarrativePromptStyle, by as DreamsDeepSleepConfig, bz as DreamsLightSleepConfig, aP as DreamsPhase, bA as DreamsPhaseStatus, bB as DreamsPhasesConfig, bC as DreamsRemConfig, aQ as DreamsRunResult, aO as DreamsStatusResult, bD as EngramTraceEvent, at as EntityActivityEntry, E as EntityFile, Y as EntityMention, as as EntityRelationship, Z as EntitySchemaDefinition, _ as EntitySchemaSectionDefinition, $ as EntityStructuredSection, aK as EntityTimelineEntry, aZ as ExtractedFact, bE as ExtractedProcedureStep, bF as ExtractedQuestion, bG as ExtractedReasoningTrace, bH as ExtractedReasoningTraceStep, bI as ExtractedRelationship, bJ as ExtractionPassSource, a0 as ExtractionResult, F as FaithfulnessFrontmatter, ar as FileHygieneConfig, G as GatewayConfig, bK as GitHubLiveConnectorConfig, bL as GmailLiveConnectorConfig, bM as GoogleDriveLiveConnectorConfig, bN as HeartbeatConfig, bO as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, y as ImportanceLevel, a9 as ImportanceScore, w as LifecycleState, L as LiveConnectorsConfig, bP as LlmTraceCallback, bQ as LlmTraceEvent, ac as MemoryActionEligibilityContext, a_ as MemoryActionEligibilitySource, W as MemoryActionEvent, bR as MemoryActionOutcome, bS as MemoryActionPolicyDecision, ad as MemoryActionPolicyResult, bT as MemoryActionStatus, U as MemoryActionType, M as MemoryCategory, g as MemoryFile, k as MemoryFrontmatter, aa as MemoryIntent, m as MemoryLifecycleEvent, ae as MemoryLifecycleEventType, af as MemoryLifecycleStateSummary, ao as MemoryLink, bU as MemoryLinkType, a$ as MemoryObservation, bV as MemoryOsPresetName, l as MemoryProjectionCurrentState, b0 as MemoryScope, h as MemoryStatus, av as MemorySummary, aq as MetaState, bW as ModelApi, bX as ModelDefinitionConfig, bY as ModelProviderAuthMode, ag as ModelProviderConfig, bZ as NamespacePolicy, ah as NativeKnowledgeConfig, b_ as NativeKnowledgeFolderRuleConfig, b$ as NativeKnowledgeObsidianVaultConfig, c0 as NativeKnowledgeOpenClawWorkspaceConfig, c1 as NotionLiveConnectorConfig, P as PluginConfig, x as PolicyClass, c2 as PrincipalFromSessionKeyMode, c3 as PrincipalRule, c4 as ProceduralConfig, aj as ProvenanceConfig, ai as ProvenanceSource, ab as QmdSearchExplain, Q as QmdSearchResult, c5 as QuestionEntry, b1 as RECALL_DISCLOSURE_LEVELS, c6 as ReasoningEffort, j as RecallDisclosure, c7 as RecallPipelineConfig, i as RecallPlanMode, c8 as RecallSectionConfig, R as RecallTierExplain, c9 as RecallTraceEvent, ca as RelevanceFeedback, ak as RetrievalTier, cb as SPECULATIVE_TTL_DAYS, cc as ScopeProfileAutoPromoteConfig, aN as ScopeProfileConfig, aL as ScopeProfileLayerId, aM as ScopeProfilePromotionTarget, cd as ScopeProfileTeamProjectConfig, ce as ScopeTeamConfig, cf as ScoredEntity, D as SecretRef, cg as SemanticChunkingConfigShape, S as SessionObserverBandConfig, ch as SignalLevel, al as SignalScanResult, ci as SlotBehaviorConfig, cj as SlotMismatchMode, an as TopicScore, T as TranscriptEntry, ck as TriggerMode, V as VerificationState, cl as confidenceTier, b7 as isRecallDisclosure } from './types-Couvz-L3.js';
package/dist/types.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  SPECULATIVE_TTL_DAYS,
5
5
  confidenceTier,
6
6
  isRecallDisclosure
7
- } from "./chunk-IX3UQT4H.js";
7
+ } from "./chunk-MLLTU5FX.js";
8
8
  import "./chunk-PZ5AY32C.js";
9
9
  export {
10
10
  DEFAULT_RECALL_DISCLOSURE,
@@ -1,5 +1,5 @@
1
1
  import { TierRoutingPolicy } from './tier-routing.js';
2
- import './types-CJrvHJjx.js';
2
+ import './types-Couvz-L3.js';
3
3
  import './types-gb_SNtjR.js';
4
4
  import './message-parts/index.js';
5
5
  import './lifecycle.js';
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-NFGJBEPT.js";
4
+ } from "./chunk-MRX6ZXHZ.js";
5
5
  import "./chunk-7FL4CNPV.js";
6
6
  import "./chunk-ZBJMUXZH.js";
7
- import "./chunk-C2EHWNP2.js";
7
+ import "./chunk-2VQYHHWB.js";
8
8
  import "./chunk-M7XQSUBB.js";
9
9
  import "./chunk-3JJWNZTT.js";
10
10
  import "./chunk-5UZXUTVO.js";
11
11
  import "./chunk-5GPPACXK.js";
12
- import "./chunk-OMNTQPZO.js";
13
- import "./chunk-IREVVFTM.js";
12
+ import "./chunk-TKZ3AL5V.js";
13
+ import "./chunk-6CIAGZ6X.js";
14
14
  import "./chunk-SVOZFLIQ.js";
15
15
  import "./chunk-LTJAMRGI.js";
16
16
  import "./chunk-J6A3CX5N.js";
17
- import "./chunk-IX3UQT4H.js";
17
+ import "./chunk-MLLTU5FX.js";
18
18
  import "./chunk-RULE4VG5.js";
19
19
  import "./chunk-SCU65EZI.js";
20
20
  import "./chunk-MB5RSUW6.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.705",
3
+ "version": "9.3.706",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2966,7 +2966,7 @@
2966
2966
  "core"
2967
2967
  ],
2968
2968
  "peerDependencies": {
2969
- "@remnic/coding-graph": "^9.3.705"
2969
+ "@remnic/coding-graph": "^9.3.706"
2970
2970
  },
2971
2971
  "peerDependenciesMeta": {
2972
2972
  "@remnic/coding-graph": {
package/src/event-time.ts CHANGED
@@ -286,6 +286,14 @@ function resolveQuarter(
286
286
  */
287
287
  function resolveAbsolute(raw: string): number | null {
288
288
  const trimmed = raw.trim();
289
+ // YYYY-MM (ISO year-month, no day) → first day of that month, UTC
290
+ // (#1578 review r3: the prompt example "until 2025-06" must resolve, not
291
+ // fall back to "assumed").
292
+ const ym = /^(\d{4})-(\d{2})$/.exec(trimmed);
293
+ if (ym) {
294
+ const ms = buildDateMs(Number(ym[1]), Number(ym[2]), 1);
295
+ return ms === null ? null : startOfDayUtc(ms);
296
+ }
289
297
  const ms = parseFlexibleIsoTimestamp(trimmed);
290
298
  if (ms === null) return null;
291
299
  // Date-only inputs (no T) anchor to start-of-day UTC.
@@ -469,6 +477,17 @@ function resolveEndBound(anchorMs: number, period: string): number | null {
469
477
  return buildDateMs(Number(trimmed) + 1, 1, 1);
470
478
  }
471
479
 
480
+ // YYYY-MM (year-month) end bound: exclusive end = start of the FOLLOWING
481
+ // month, so "until 2025-06" spans all of June (#1578 review r3).
482
+ const ymEnd = /^(\d{4})-(\d{2})$/.exec(trimmed);
483
+ if (ymEnd) {
484
+ const startMs = buildDateMs(Number(ymEnd[1]), Number(ymEnd[2]), 1);
485
+ if (startMs === null) return null;
486
+ const nd = new Date(startMs);
487
+ nd.setUTCMonth(nd.getUTCMonth() + 1);
488
+ return startOfDayUtc(nd.getTime());
489
+ }
490
+
472
491
  // Absolute date/datetime end bound.
473
492
  const absMs = resolveAbsolute(trimmed);
474
493
  if (absMs !== null) {
@@ -546,3 +565,57 @@ function resolveEndBound(anchorMs: number, period: string): number | null {
546
565
  }
547
566
  return null;
548
567
  }
568
+
569
+ /**
570
+ * Resolved bi-temporal fields for a single extracted fact (issue #1578 PR2).
571
+ *
572
+ * - `validFrom` / `validUntil` — the event-time `[from, until)` interval.
573
+ * When `eventTimeSource === "assumed"`, `validFrom` is copied from
574
+ * `observedAt` (the ingestion anchor) so every bi-temporal-on fact carries
575
+ * at least a start bound.
576
+ * - `observedAt` — always the ingestion anchor (the source turn timestamp).
577
+ * - `eventTimeSource` — `"extracted"` when the expression resolved to a real
578
+ * event-time interval; `"assumed"` when the expression was absent or
579
+ * unresolvable.
580
+ *
581
+ * This is the single write-time helper every extraction entry path calls
582
+ * (rule 39). It is pure: (expression, anchor) in, interval out. Resolution
583
+ * never touches `Date.now()` — replay/import of old transcripts resolves
584
+ * against the old anchor.
585
+ */
586
+ export interface FactEventTime {
587
+ validFrom?: string;
588
+ validUntil?: string;
589
+ observedAt: string;
590
+ eventTimeSource: "extracted" | "assumed";
591
+ }
592
+
593
+ /**
594
+ * Resolve a single fact's event-time expression against the ingestion anchor.
595
+ *
596
+ * Returns the bi-temporal interval to persist alongside the fact. When the
597
+ * expression is absent, empty, or unresolvable, falls back to
598
+ * `eventTimeSource: "assumed"` with `validFrom = observedAt` — per the
599
+ * #1578 frontmatter contract. When the expression resolves but only pins one
600
+ * bound ("since 2024" → validFrom only; "until 2025-06" → validUntil only),
601
+ * only the resolved bound is returned.
602
+ */
603
+ export function resolveFactEventTime(
604
+ expression: string | undefined | null,
605
+ anchorIso: string,
606
+ ): FactEventTime {
607
+ const resolved = resolveEventTime(expression, anchorIso);
608
+ if (resolved.ok && (resolved.validFrom !== undefined || resolved.validUntil !== undefined)) {
609
+ return {
610
+ ...(resolved.validFrom !== undefined ? { validFrom: resolved.validFrom } : {}),
611
+ ...(resolved.validUntil !== undefined ? { validUntil: resolved.validUntil } : {}),
612
+ observedAt: anchorIso,
613
+ eventTimeSource: "extracted",
614
+ };
615
+ }
616
+ return {
617
+ validFrom: anchorIso,
618
+ observedAt: anchorIso,
619
+ eventTimeSource: "assumed",
620
+ };
621
+ }
@@ -0,0 +1,269 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { mkdtemp, rm } from "node:fs/promises";
6
+
7
+ import { resolveFactEventTime } from "./event-time.js";
8
+ import { ExtractionEngine } from "./extraction.js";
9
+ import { ExtractedFactSchema } from "./schemas.js";
10
+ import { StorageManager } from "./storage.js";
11
+
12
+ /**
13
+ * Issue #1578 PR 2 — extraction event-time wiring.
14
+ *
15
+ * Proves three contracts:
16
+ * 1. resolveFactEventTime anchors to the SOURCE TURN timestamp, never
17
+ * wall-clock — replay/import of an old transcript yields old-era event
18
+ * times (the core bi-temporal invariant from the #1578 design).
19
+ * 2. Extraction normalization passes the per-fact `eventTime` expression
20
+ * through from raw LLM JSON (camelCase and snake_case payloads) so the
21
+ * persist path can resolve it at write time.
22
+ * 3. Storage round-trips `invalid_at` so the bi-temporal interval survives
23
+ * write → read (complementing PR 1's observedAt/eventTimeSource test).
24
+ */
25
+
26
+ // ── 1. Replay anchoring ──────────────────────────────────────────────────
27
+
28
+ test("resolveFactEventTime: replay of a 2025 transcript anchors 'last March' to 2025-03, not today", () => {
29
+ // The anchor is the source turn timestamp from an old (2025) transcript.
30
+ const anchor2025 = "2025-04-10T12:00:00.000Z";
31
+ const result = resolveFactEventTime("last March", anchor2025);
32
+ assert.equal(result.eventTimeSource, "extracted");
33
+ assert.ok(result.validFrom, "validFrom must be set for a resolvable expression");
34
+ // "last March" relative to April 2025 → March 2025.
35
+ assert.ok(result.validFrom!.startsWith("2025-03"), `expected 2025-03, got ${result.validFrom}`);
36
+ // Critical bi-temporal invariant: NOT anchored to today's wall-clock.
37
+ assert.ok(!result.validFrom!.startsWith("2026"), "must not anchor to wall-clock");
38
+ assert.equal(result.observedAt, anchor2025);
39
+ });
40
+
41
+ test("resolveFactEventTime: same expression, different anchors → different validFrom", () => {
42
+ // The identical phrase resolves differently depending on the anchor —
43
+ // proving the resolver is anchor-driven, not Date.now()-driven.
44
+ const old = resolveFactEventTime("yesterday", "2025-01-15T10:00:00.000Z");
45
+ const recent = resolveFactEventTime("yesterday", "2026-07-05T10:00:00.000Z");
46
+ assert.ok(old.validFrom!.startsWith("2025-01-14"), `got ${old.validFrom}`);
47
+ assert.ok(recent.validFrom!.startsWith("2026-07-04"), `got ${recent.validFrom}`);
48
+ assert.notEqual(old.validFrom, recent.validFrom);
49
+ });
50
+
51
+ test("resolveFactEventTime: absent expression → 'assumed' with validFrom = anchor", () => {
52
+ const anchor = "2025-06-01T00:00:00.000Z";
53
+ const result = resolveFactEventTime(undefined, anchor);
54
+ assert.equal(result.eventTimeSource, "assumed");
55
+ assert.equal(result.validFrom, anchor);
56
+ assert.equal(result.observedAt, anchor);
57
+ assert.equal(result.validUntil, undefined);
58
+ });
59
+
60
+ test("resolveFactEventTime: 'since 2024' → validFrom only (open-ended start)", () => {
61
+ const result = resolveFactEventTime("since 2024", "2025-06-01T00:00:00.000Z");
62
+ assert.equal(result.eventTimeSource, "extracted");
63
+ assert.ok(result.validFrom!.startsWith("2024-01-01"));
64
+ assert.equal(result.validUntil, undefined);
65
+ });
66
+
67
+ test("resolveFactEventTime: unresolvable garbage → 'assumed' fallback", () => {
68
+ const anchor = "2025-06-01T00:00:00.000Z";
69
+ const result = resolveFactEventTime("sometime maybe???", anchor);
70
+ assert.equal(result.eventTimeSource, "assumed");
71
+ assert.equal(result.validFrom, anchor);
72
+ });
73
+
74
+ // ── 2. Extraction normalization passes eventTime through ─────────────────
75
+
76
+ function makeEngine(): ExtractionEngine {
77
+ // Minimal construction: the facts normalizer is a pure data transform
78
+ // (no LLM / I/O). Stub the optional deps so the constructor doesn't
79
+ // spin up real clients.
80
+ return new ExtractionEngine(
81
+ { memoryDir: "/tmp" } as any,
82
+ undefined, // profiler → safe default
83
+ {} as any, // localLlm stub
84
+ undefined, // gatewayConfig
85
+ {} as any, // modelRegistry stub
86
+ );
87
+ }
88
+
89
+ test("extraction normalization: camelCase 'eventTime' passes through to ExtractedFact", () => {
90
+ const engine = makeEngine();
91
+ const result = (engine as any).normalizeExtractionResultPayload({
92
+ facts: [
93
+ { content: "We moved offices in March.", category: "fact", eventTime: "last March" },
94
+ ],
95
+ entities: [],
96
+ });
97
+ assert.equal(result.facts.length, 1);
98
+ assert.equal(result.facts[0].eventTime, "last March");
99
+ });
100
+
101
+ test("extraction normalization: snake_case 'event_time' also captured", () => {
102
+ const engine = makeEngine();
103
+ const result = (engine as any).normalizeExtractionResultPayload({
104
+ facts: [
105
+ { content: "Switched to PostgreSQL.", category: "fact", event_time: "2025-01-15" },
106
+ ],
107
+ entities: [],
108
+ });
109
+ assert.equal(result.facts[0].eventTime, "2025-01-15");
110
+ });
111
+
112
+ test("extraction normalization: absent eventTime → undefined (assumed deferred to write time)", () => {
113
+ const engine = makeEngine();
114
+ const result = (engine as any).normalizeExtractionResultPayload({
115
+ facts: [{ content: "The sky is blue.", category: "fact" }],
116
+ entities: [],
117
+ });
118
+ assert.equal(result.facts[0].eventTime, undefined);
119
+ });
120
+
121
+ // ── 2b. Schema preserves snake_case event_time (gateway fix, chatgpt-codex) ─
122
+
123
+ test("ExtractedFactSchema: snake_case event_time survives schema parse (gateway strip-unknown fix)", () => {
124
+ // Zod .object() strips unknown keys by default. Before the event_time alias
125
+ // was added, the gateway fallback path (parseWithSchemaDetailed → schema.parse)
126
+ // silently dropped event_time, losing the temporal bound (#1578).
127
+ const parsed = ExtractedFactSchema.parse({
128
+ content: "Switched to PostgreSQL.",
129
+ category: "fact",
130
+ confidence: 0.9,
131
+ tags: [],
132
+ event_time: "2025-01-15",
133
+ });
134
+ assert.equal(parsed.event_time, "2025-01-15");
135
+ });
136
+
137
+ test("ExtractedFactSchema: camelCase eventTime also survives schema parse (no regression)", () => {
138
+ const parsed = ExtractedFactSchema.parse({
139
+ content: "We moved offices in March.",
140
+ category: "fact",
141
+ confidence: 0.8,
142
+ tags: [],
143
+ eventTime: "2025-03",
144
+ });
145
+ assert.equal(parsed.eventTime, "2025-03");
146
+ });
147
+
148
+ // ── 3. Storage round-trips invalid_at ────────────────────────────────────
149
+
150
+ test("storage: invalid_at round-trips through writeMemoryFrontmatter → readAllMemories", async () => {
151
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-et-invalidAt-"));
152
+ try {
153
+ const storage = new StorageManager(dir);
154
+ await storage.ensureDirectories();
155
+
156
+ const id = await storage.writeMemory("fact", "Old stack was Node 18.", {
157
+ validAt: "2025-01-01T00:00:00.000Z",
158
+ observedAt: "2025-06-01T00:00:00.000Z",
159
+ eventTimeSource: "extracted",
160
+ });
161
+
162
+ const before = await storage.readAllMemories();
163
+ const mem = before.find((m) => m.frontmatter.id === id);
164
+ assert.ok(mem);
165
+
166
+ const ok = await storage.writeMemoryFrontmatter(mem!, {
167
+ invalid_at: "2025-06-15T00:00:00.000Z",
168
+ });
169
+ assert.equal(ok, true);
170
+
171
+ const after = await storage.readAllMemories();
172
+ const patched = after.find((m) => m.frontmatter.id === id);
173
+ assert.ok(patched, "patched fact must be readable");
174
+ assert.equal(patched!.frontmatter.invalid_at, "2025-06-15T00:00:00.000Z");
175
+ // Bi-temporal provenance preserved through the frontmatter patch.
176
+ assert.equal(patched!.frontmatter.observedAt, "2025-06-01T00:00:00.000Z");
177
+ assert.equal(patched!.frontmatter.eventTimeSource, "extracted");
178
+ assert.equal(patched!.frontmatter.valid_at, "2025-01-01T00:00:00.000Z");
179
+ } finally {
180
+ await rm(dir, { recursive: true, force: true });
181
+ }
182
+ });
183
+
184
+ test("storage: writeMemory accepts invalidAt directly (end-bound at extraction write time)", async () => {
185
+ // Proves Fix C: writeMemory now accepts invalidAt so "until X" expressions
186
+ // persist invalid_at at write time without a separate updateMemoryFrontmatter call.
187
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-et-writeMem-invalid-"));
188
+ try {
189
+ const storage = new StorageManager(dir);
190
+ await storage.ensureDirectories();
191
+ const id = await storage.writeMemory("fact", "We used MongoDB until June 2025.", {
192
+ validAt: "2024-01-01T00:00:00.000Z",
193
+ invalidAt: "2025-06-01T00:00:00.000Z",
194
+ observedAt: "2025-06-20T00:00:00.000Z",
195
+ eventTimeSource: "extracted",
196
+ });
197
+ const mems = await storage.readAllMemories();
198
+ const mem = mems.find((m) => m.frontmatter.id === id);
199
+ assert.ok(mem);
200
+ assert.equal(mem!.frontmatter.valid_at, "2024-01-01T00:00:00.000Z");
201
+ assert.equal(mem!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z");
202
+ assert.equal(mem!.frontmatter.observedAt, "2025-06-20T00:00:00.000Z");
203
+ assert.equal(mem!.frontmatter.eventTimeSource, "extracted");
204
+ } finally {
205
+ await rm(dir, { recursive: true, force: true });
206
+ }
207
+ });
208
+
209
+ test("resolveFactEventTime: 'through 2026' → validUntil only (open-ended end bound)", () => {
210
+ const result = resolveFactEventTime("through 2026", "2025-06-20T00:00:00.000Z");
211
+ assert.equal(result.eventTimeSource, "extracted");
212
+ assert.equal(result.validFrom, undefined);
213
+ assert.ok(result.validUntil, "validUntil must be set for an end-bound expression");
214
+ // "through 2026" → exclusive end = start of 2027.
215
+ assert.equal(result.validUntil, "2027-01-01T00:00:00.000Z");
216
+ });
217
+ test("storage: writeChunk propagates invalidAt/observedAt/eventTimeSource from the parent fact (#1578 PR2)", async () => {
218
+ // Proves the cursor-bugbot fix: an independently-surfaced chunk expires at the
219
+ // same invalid_at window as its parent so recall's isValidityExpiredNow check
220
+ // fires on chunks too, not just the chunked parent write.
221
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-et-chunk-invalid-"));
222
+ try {
223
+ const storage = new StorageManager(dir);
224
+ await storage.ensureDirectories();
225
+ const parentId = await storage.writeMemory("fact", "We used MongoDB until June 2025.", {
226
+ validAt: "2024-01-01T00:00:00.000Z",
227
+ invalidAt: "2025-06-01T00:00:00.000Z",
228
+ observedAt: "2025-06-20T00:00:00.000Z",
229
+ eventTimeSource: "extracted",
230
+ });
231
+ const chunkId = await storage.writeChunk(
232
+ parentId,
233
+ 0,
234
+ 1,
235
+ "fact",
236
+ "We used MongoDB until June 2025.",
237
+ {
238
+ validAt: "2024-01-01T00:00:00.000Z",
239
+ invalidAt: "2025-06-01T00:00:00.000Z",
240
+ observedAt: "2025-06-20T00:00:00.000Z",
241
+ eventTimeSource: "extracted",
242
+ },
243
+ );
244
+ const mems = await storage.readAllMemories();
245
+ const chunk = mems.find((m) => m.frontmatter.id === chunkId);
246
+ assert.ok(chunk, "chunk must be readable");
247
+ assert.equal(chunk!.frontmatter.valid_at, "2024-01-01T00:00:00.000Z");
248
+ assert.equal(chunk!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z",
249
+ "chunk must carry the parent's end bound so it expires independently");
250
+ assert.equal(chunk!.frontmatter.observedAt, "2025-06-20T00:00:00.000Z");
251
+ assert.equal(chunk!.frontmatter.eventTimeSource, "extracted");
252
+ } finally {
253
+ await rm(dir, { recursive: true, force: true });
254
+ }
255
+ });
256
+ test("resolveFactEventTime: 'until 2025-06' (YYYY-MM) → invalidAt = start of following month (#1578 r3)", () => {
257
+ const result = resolveFactEventTime("until 2025-06", "2025-06-20T00:00:00.000Z");
258
+ assert.equal(result.eventTimeSource, "extracted");
259
+ assert.equal(result.validFrom, undefined);
260
+ assert.equal(result.validUntil, "2025-07-01T00:00:00.000Z",
261
+ "YYYY-MM end bound must span the whole month, not fall back to assumed");
262
+ });
263
+
264
+ test("resolveFactEventTime: bare '2025-03' (YYYY-MM) → validFrom = first of month (#1578 r3)", () => {
265
+ const result = resolveFactEventTime("2025-03", "2025-06-20T00:00:00.000Z");
266
+ assert.equal(result.eventTimeSource, "extracted");
267
+ assert.equal(result.validFrom, "2025-03-01T00:00:00.000Z");
268
+ assert.equal(result.validUntil, undefined);
269
+ });
package/src/extraction.ts CHANGED
@@ -279,6 +279,12 @@ export class ExtractionEngine {
279
279
  typeof f?.quote === "string" && f.quote.trim().length > 0
280
280
  ? f.quote
281
281
  : undefined,
282
+ eventTime:
283
+ typeof f?.eventTime === "string" && f.eventTime.trim().length > 0
284
+ ? f.eventTime.trim()
285
+ : typeof f?.event_time === "string" && f.event_time.trim().length > 0
286
+ ? f.event_time.trim()
287
+ : undefined,
282
288
  }))
283
289
  .filter((f: any) => f.content.length > 0)
284
290
  : [];
@@ -750,6 +756,11 @@ export class ExtractionEngine {
750
756
  this.config.provenance?.enabled
751
757
  ? '- Source quotes: For each fact, include a "quote" field with the EXACT verbatim words from the conversation that support the fact (a contiguous span from a single turn, not a paraphrase). Cap at ~300 chars.'
752
758
  : "",
759
+ // #1578: emit the same event-time guidance as the primary extraction
760
+ // paths so proactive-recovered facts also carry an optional eventTime
761
+ // (chatgpt-codex thread on extraction.ts:1607). Returns "" when the
762
+ // bi-temporal gate is off, keeping the prompt unchanged by default.
763
+ this.eventTimePromptInstruction(),
753
764
  "",
754
765
  "Base extracted facts (do not repeat):",
755
766
  factsPreview || "(none)",
@@ -1250,10 +1261,23 @@ export class ExtractionEngine {
1250
1261
  // reasoningTrace through normalizeReasoningTrace before passing it on so
1251
1262
  // gateway output matches the shape local/direct-client paths produce.
1252
1263
  const normalizedFacts = result.facts.map((f: any) => {
1253
- if (!f?.reasoningTrace) return f;
1264
+ if (!f) return f;
1265
+ // Gateway tolerance: collapse snake_case event_time → camelCase
1266
+ // eventTime so the gateway path matches the local/direct/proactive
1267
+ // normalization (#1578 r3 — cursor bugbot).
1268
+ const eventTime =
1269
+ typeof f.eventTime === "string" && f.eventTime.trim().length > 0
1270
+ ? f.eventTime.trim()
1271
+ : typeof f.event_time === "string" && f.event_time.trim().length > 0
1272
+ ? f.event_time.trim()
1273
+ : undefined;
1274
+ if (!f.reasoningTrace && eventTime === undefined) return f;
1254
1275
  return {
1255
1276
  ...f,
1256
- reasoningTrace: normalizeReasoningTrace(f.reasoningTrace) ?? undefined,
1277
+ ...(f.reasoningTrace
1278
+ ? { reasoningTrace: normalizeReasoningTrace(f.reasoningTrace) ?? undefined }
1279
+ : {}),
1280
+ ...(eventTime !== undefined ? { eventTime } : {}),
1257
1281
  };
1258
1282
  });
1259
1283
  const sanitized = this.sanitizeExtractionResult({
@@ -1394,7 +1418,7 @@ Examples of when to add structuredAttributes:
1394
1418
  - Decisions: {"chosen": "PostgreSQL", "rejected": "MongoDB", "reason": "ACID compliance"}
1395
1419
  - Quantities/measurements: {"budget": "50000", "team_size": "5", "deadline": "2024-06-01"}
1396
1420
  Only add structuredAttributes when there are concrete values. Skip for abstract or narrative facts.
1397
-
1421
+ ${this.eventTimePromptInstruction()}
1398
1422
  Also generate:
1399
1423
  1. 1-3 genuine questions you're curious about from this conversation
1400
1424
  2. Profile updates about user patterns/behaviors (if any)
@@ -1595,6 +1619,28 @@ ${truncatedConversation}`;
1595
1619
  return { facts, entities, profileUpdates: [], questions: [] };
1596
1620
  }
1597
1621
 
1622
+ /**
1623
+ * Bi-temporal event-time extraction instruction (#1578 PR2). Emitted on
1624
+ * every extraction entry path when `temporal.biTemporal` is on so the LLM
1625
+ * emits an optional per-fact `eventTime` expression. The expression is
1626
+ * resolved against the source turn timestamp at write time — never
1627
+ * wall-clock — so replay/import of old transcripts anchors correctly.
1628
+ * Returns an empty string when the gate is off (byte-identical prompt).
1629
+ */
1630
+ private eventTimePromptInstruction(): string {
1631
+ if (!this.config.temporalBiTemporal) return "";
1632
+ return `
1633
+ === Event Time (bi-temporal) ===
1634
+ When a fact has an explicit temporal anchor — a date, month, season, or relative time expression stating WHEN the fact became (or stopped being) true — capture it verbatim in an "eventTime" field on that fact. Examples:
1635
+ - "We moved offices in March" → "eventTime": "last March"
1636
+ - "The API has been rate-limited since 2024" → "eventTime": "since 2024"
1637
+ - "I switched to PostgreSQL on 2025-01-15" → "eventTime": "2025-01-15"
1638
+ - "We used MongoDB until June 2025" → "eventTime": "until 2025-06"
1639
+ Accepted forms: ISO dates ("2025-03-01"), year-month ("2025-03"), month/season + year ("March 2025", "summer 2024"), relative ("yesterday", "last week", "this month", "next year", "last December"), and open-ended ("since 2024", "until 2025-06").
1640
+ Omit "eventTime" when the fact has no explicit temporal anchor — do NOT guess or infer dates. The system resolves the expression against the conversation's own timestamp, not today's date.
1641
+ `;
1642
+ }
1643
+
1598
1644
  /**
1599
1645
  * Build extraction instructions shared between local and cloud LLM.
1600
1646
  */
@@ -1663,6 +1709,7 @@ ${existingEntities.join(", ")}
1663
1709
 
1664
1710
  When you see something that matches a known entity, use THAT name exactly. Only create a NEW entity if nothing in this list represents it.
1665
1711
  ` : ""}
1712
+ ${this.eventTimePromptInstruction()}
1666
1713
  Also extract relationships between entities mentioned in the conversation.
1667
1714
  - Format: {source: "entity-name", target: "entity-name", label: "relationship description"}
1668
1715
  - Max 5 relationships per extraction