@remnic/core 9.7.11 → 9.7.12

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 (277) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +7 -7
  3. package/dist/access-boundary.d.ts +6 -6
  4. package/dist/access-boundary.js +8 -8
  5. package/dist/access-cli.js +31 -31
  6. package/dist/access-http.d.ts +6 -6
  7. package/dist/access-http.js +12 -12
  8. package/dist/access-identity-continuity-surface.d.ts +5 -5
  9. package/dist/access-identity-continuity-surface.js +7 -7
  10. package/dist/access-lcm-surface.d.ts +6 -6
  11. package/dist/access-lcm-surface.js +7 -7
  12. package/dist/access-mcp.d.ts +6 -6
  13. package/dist/access-mcp.js +11 -11
  14. package/dist/access-observe-write-surface.d.ts +6 -6
  15. package/dist/access-observe-write-surface.js +7 -7
  16. package/dist/access-operations-batch.js +9 -9
  17. package/dist/access-operations.d.ts +6 -6
  18. package/dist/access-operations.js +10 -10
  19. package/dist/access-recall-surface.d.ts +6 -6
  20. package/dist/access-recall-surface.js +7 -7
  21. package/dist/{access-service-DSuzikHQ.d.ts → access-service-tHd3eQpd.d.ts} +4 -4
  22. package/dist/access-service.d.ts +6 -6
  23. package/dist/access-service.js +7 -7
  24. package/dist/access-surface-catalog.d.ts +6 -6
  25. package/dist/action-confidence.d.ts +1 -1
  26. package/dist/active-memory-bridge.d.ts +1 -1
  27. package/dist/active-recall.d.ts +1 -1
  28. package/dist/active-recall.js +1 -1
  29. package/dist/behavior-learner.d.ts +1 -1
  30. package/dist/behavior-signals.d.ts +1 -1
  31. package/dist/bootstrap.d.ts +5 -5
  32. package/dist/briefing.d.ts +2 -2
  33. package/dist/briefing.js +3 -3
  34. package/dist/buffer-surprise-report.d.ts +1 -1
  35. package/dist/buffer.d.ts +2 -2
  36. package/dist/bulk-import/index.d.ts +3 -3
  37. package/dist/calibration.d.ts +1 -1
  38. package/dist/capabilities.d.ts +1 -1
  39. package/dist/{catalog-BNwjACa5.d.ts → catalog-DEMiw6HZ.d.ts} +48 -1
  40. package/dist/causal-behavior.d.ts +1 -1
  41. package/dist/causal-consolidation.d.ts +1 -1
  42. package/dist/causal-consolidation.js +4 -4
  43. package/dist/causal-trajectory-graph.d.ts +1 -1
  44. package/dist/{chunk-U4KHQTJ6.js → chunk-2AL7N3BH.js} +4 -2
  45. package/dist/{chunk-U4KHQTJ6.js.map → chunk-2AL7N3BH.js.map} +1 -1
  46. package/dist/{chunk-L4H6MQ7I.js → chunk-2RSL6YRA.js} +2 -2
  47. package/dist/{chunk-JYRFO4EH.js → chunk-2ZPQUTMW.js} +4 -4
  48. package/dist/{chunk-673NPXE3.js → chunk-3SIC3E65.js} +2 -2
  49. package/dist/{chunk-OVMS3DCB.js → chunk-675YU5IK.js} +2 -2
  50. package/dist/{chunk-7LMEY5KZ.js → chunk-6N7R7TR2.js} +34 -1
  51. package/dist/chunk-6N7R7TR2.js.map +1 -0
  52. package/dist/{chunk-W6R43NLA.js → chunk-AOWPZ7TM.js} +17 -17
  53. package/dist/{chunk-TGBJ2OMK.js → chunk-BQTSGK5Y.js} +2 -2
  54. package/dist/{chunk-6LM6PPDX.js → chunk-CE3GMHT4.js} +2 -2
  55. package/dist/{chunk-YXOAQAH2.js → chunk-CLFY35ED.js} +3 -3
  56. package/dist/{chunk-HQ7QZJM5.js → chunk-D4TLWDYR.js} +2 -2
  57. package/dist/{chunk-LQKITFGB.js → chunk-FUUF7FMX.js} +2 -2
  58. package/dist/{chunk-3ZVFLMFQ.js → chunk-G5PVJJRA.js} +1 -1
  59. package/dist/chunk-G5PVJJRA.js.map +1 -0
  60. package/dist/{chunk-MXRMNKSY.js → chunk-I3GKTDM2.js} +3 -3
  61. package/dist/{chunk-EQF5763Z.js → chunk-IQULRIZM.js} +2 -2
  62. package/dist/{chunk-DJCKXI4M.js → chunk-NVXQTRBL.js} +2 -2
  63. package/dist/{chunk-GHVJASQ4.js → chunk-OCY3CRPG.js} +3 -3
  64. package/dist/{chunk-YLTG6BO4.js → chunk-OIFZII6J.js} +2 -2
  65. package/dist/{chunk-XF4KOVDK.js → chunk-PA6DMYEK.js} +6 -6
  66. package/dist/{chunk-3OQ4IZJN.js → chunk-QPZCAVDC.js} +7 -7
  67. package/dist/{chunk-DLWYAN6A.js → chunk-T6U7HDIY.js} +2 -2
  68. package/dist/{chunk-7XRUYC6G.js → chunk-TITSEN2M.js} +3 -3
  69. package/dist/{chunk-JZM5QUTE.js → chunk-TMGK6YE7.js} +171 -9
  70. package/dist/chunk-TMGK6YE7.js.map +1 -0
  71. package/dist/{chunk-KXLRMM35.js → chunk-TRH533CA.js} +30 -7
  72. package/dist/chunk-TRH533CA.js.map +1 -0
  73. package/dist/{chunk-UTDA43TB.js → chunk-TWGGNUWJ.js} +2 -2
  74. package/dist/{chunk-SBFDL2EQ.js → chunk-UTZW3X2R.js} +2 -2
  75. package/dist/{chunk-SECMZACR.js → chunk-XUQUBZ7O.js} +2 -2
  76. package/dist/{chunk-5U65RIVI.js → chunk-YI7XUXLX.js} +16 -15
  77. package/dist/chunk-YI7XUXLX.js.map +1 -0
  78. package/dist/{chunk-QHT4W3RU.js → chunk-ZASJ53ML.js} +6 -6
  79. package/dist/{chunk-WUDBMLC6.js → chunk-ZC3HG22I.js} +2 -2
  80. package/dist/{chunk-VCEH5UYS.js → chunk-ZRXSHGXT.js} +2 -2
  81. package/dist/{cli-DACyuK8-.d.ts → cli-D8Tsa4Sh.d.ts} +5 -5
  82. package/dist/cli.d.ts +8 -8
  83. package/dist/cli.js +25 -25
  84. package/dist/compounding/engine.d.ts +2 -2
  85. package/dist/compounding/engine.js +3 -3
  86. package/dist/compounding/preference-consolidator.d.ts +1 -1
  87. package/dist/compression-optimizer.d.ts +1 -1
  88. package/dist/config.d.ts +1 -1
  89. package/dist/config.js +1 -1
  90. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  91. package/dist/connectors/codex-materialize-runner.js +3 -3
  92. package/dist/connectors/codex-materialize.d.ts +1 -1
  93. package/dist/connectors/index.d.ts +1 -1
  94. package/dist/connectors/index.js +3 -3
  95. package/dist/consolidation-provenance-check.d.ts +2 -2
  96. package/dist/consolidation-undo.d.ts +2 -2
  97. package/dist/contradiction/index.d.ts +2 -2
  98. package/dist/conversation-index/backend.d.ts +1 -1
  99. package/dist/conversation-index/chunker.d.ts +1 -1
  100. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  101. package/dist/conversation-index/indexer.d.ts +1 -1
  102. package/dist/conversation-index/search.d.ts +1 -1
  103. package/dist/day-summary.d.ts +1 -1
  104. package/dist/delinearize.d.ts +1 -1
  105. package/dist/direct-answer-wiring.d.ts +1 -1
  106. package/dist/direct-answer.d.ts +1 -1
  107. package/dist/embedding-fallback.d.ts +1 -1
  108. package/dist/enrichment/index.d.ts +1 -1
  109. package/dist/entity-retrieval.d.ts +2 -2
  110. package/dist/entity-retrieval.js +3 -3
  111. package/dist/entity-schema.d.ts +1 -1
  112. package/dist/explicit-capture.d.ts +5 -5
  113. package/dist/extraction-faithfulness.d.ts +1 -1
  114. package/dist/extraction-judge-telemetry.d.ts +1 -1
  115. package/dist/extraction-judge-training.d.ts +1 -1
  116. package/dist/extraction-judge.d.ts +1 -1
  117. package/dist/extraction.d.ts +1 -1
  118. package/dist/fallback-llm.d.ts +1 -1
  119. package/dist/graph-dashboard-diff.d.ts +1 -1
  120. package/dist/graph-dashboard-key.d.ts +1 -1
  121. package/dist/graph-dashboard-parser.d.ts +1 -1
  122. package/dist/graph-edge-reinforcement.d.ts +1 -1
  123. package/dist/graph-snapshot.d.ts +1 -1
  124. package/dist/graph.d.ts +1 -1
  125. package/dist/identity-continuity.d.ts +1 -1
  126. package/dist/importance.d.ts +1 -1
  127. package/dist/importers/index.d.ts +1 -1
  128. package/dist/in-flight-reads.d.ts +1 -1
  129. package/dist/index.d.ts +14 -14
  130. package/dist/index.js +31 -31
  131. package/dist/intent.d.ts +1 -1
  132. package/dist/lcm/engine.d.ts +1 -1
  133. package/dist/lcm/index.d.ts +1 -1
  134. package/dist/lcm/tools.d.ts +1 -1
  135. package/dist/lifecycle.d.ts +1 -1
  136. package/dist/live-connectors-runner.d.ts +1 -1
  137. package/dist/local-llm.d.ts +1 -1
  138. package/dist/local-model-endpoint.d.ts +1 -1
  139. package/dist/maintenance/memory-governance.d.ts +1 -1
  140. package/dist/maintenance/memory-governance.js +3 -3
  141. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  142. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  143. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  144. package/dist/memory-action-policy.d.ts +1 -1
  145. package/dist/memory-cache.d.ts +1 -1
  146. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  147. package/dist/memory-projection-store.d.ts +1 -1
  148. package/dist/memory-provenance.d.ts +1 -1
  149. package/dist/memory-worth-outcomes.d.ts +2 -2
  150. package/dist/models-json.d.ts +1 -1
  151. package/dist/namespaces/migrate.d.ts +3 -3
  152. package/dist/namespaces/migrate.js +4 -4
  153. package/dist/namespaces/principal.d.ts +1 -1
  154. package/dist/namespaces/search.d.ts +1 -1
  155. package/dist/namespaces/storage.d.ts +3 -3
  156. package/dist/namespaces/storage.js +3 -3
  157. package/dist/native-knowledge.d.ts +1 -1
  158. package/dist/operator-toolkit.d.ts +2 -2
  159. package/dist/operator-toolkit.js +8 -8
  160. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  161. package/dist/orchestration/maintenance.d.ts +2 -2
  162. package/dist/orchestration/maintenance.js +5 -5
  163. package/dist/{orchestrator-D83AvrPf.d.ts → orchestrator-B_WXBmuE.d.ts} +4 -4
  164. package/dist/orchestrator.d.ts +5 -5
  165. package/dist/orchestrator.js +31 -31
  166. package/dist/patterns-cli.d.ts +1 -1
  167. package/dist/{pipeline-BSqGpYl1.d.ts → pipeline-Dvtvt141.d.ts} +1 -1
  168. package/dist/policy-runtime.d.ts +1 -1
  169. package/dist/provenance.d.ts +1 -1
  170. package/dist/{qmd-CutnpRVe.d.ts → qmd-DwBLh_hl.d.ts} +1 -1
  171. package/dist/qmd-preflight.d.ts +2 -2
  172. package/dist/qmd-recall-cache.d.ts +1 -1
  173. package/dist/qmd.d.ts +2 -2
  174. package/dist/recall-disclosure-escalation.d.ts +1 -1
  175. package/dist/recall-explain-renderer.d.ts +1 -1
  176. package/dist/recall-explain-renderer.js +3 -3
  177. package/dist/recall-planner-llm.d.ts +1 -1
  178. package/dist/recall-state.d.ts +1 -1
  179. package/dist/recall-tag-filter.d.ts +1 -1
  180. package/dist/recall-timings.d.ts +1 -1
  181. package/dist/recall-xray-cli.d.ts +1 -1
  182. package/dist/recall-xray-cli.js +4 -4
  183. package/dist/recall-xray-renderer.d.ts +1 -1
  184. package/dist/recall-xray-renderer.js +3 -3
  185. package/dist/recall-xray.d.ts +1 -1
  186. package/dist/recall-xray.js +2 -2
  187. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  188. package/dist/replay/normalizers/claude.d.ts +1 -1
  189. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  190. package/dist/replay/normalizers/shared.d.ts +1 -1
  191. package/dist/replay/runner.d.ts +1 -1
  192. package/dist/replay/types.d.ts +1 -1
  193. package/dist/resolve-auth-token.d.ts +1 -1
  194. package/dist/resume-bundles.js +2 -2
  195. package/dist/retrieval-agents.d.ts +2 -2
  196. package/dist/retrieval-tiers.d.ts +1 -1
  197. package/dist/routing/engine.d.ts +1 -1
  198. package/dist/routing/store.d.ts +1 -1
  199. package/dist/search/embed-helper.d.ts +1 -1
  200. package/dist/search/factory.d.ts +1 -1
  201. package/dist/search/index.d.ts +1 -1
  202. package/dist/search/lancedb-backend.d.ts +1 -1
  203. package/dist/search/meilisearch-backend.d.ts +1 -1
  204. package/dist/search/noop-backend.d.ts +1 -1
  205. package/dist/search/orama-backend.d.ts +1 -1
  206. package/dist/search/port.d.ts +1 -1
  207. package/dist/search/remote-backend.d.ts +1 -1
  208. package/dist/{semantic-consolidation-btIVRC4l.d.ts → semantic-consolidation-Dia_6FOn.d.ts} +1 -1
  209. package/dist/semantic-consolidation.d.ts +2 -2
  210. package/dist/semantic-consolidation.js +4 -4
  211. package/dist/semantic-rule-promotion.js +3 -3
  212. package/dist/semantic-rule-verifier.d.ts +1 -1
  213. package/dist/semantic-rule-verifier.js +3 -3
  214. package/dist/session-observer-bands.d.ts +1 -1
  215. package/dist/session-observer-state.d.ts +1 -1
  216. package/dist/shared-context/manager.d.ts +1 -1
  217. package/dist/signal.d.ts +1 -1
  218. package/dist/{storage-BVv_Mqa0.d.ts → storage-RshM7i8-.d.ts} +18 -1
  219. package/dist/storage.d.ts +2 -2
  220. package/dist/storage.js +2 -2
  221. package/dist/summarizer.d.ts +1 -1
  222. package/dist/summary-snapshot.d.ts +1 -1
  223. package/dist/temporal-supersession.d.ts +2 -2
  224. package/dist/temporal-timeline-recall.d.ts +1 -1
  225. package/dist/temporal-validity.d.ts +1 -1
  226. package/dist/threading.d.ts +1 -1
  227. package/dist/tier-migration.d.ts +2 -2
  228. package/dist/tier-routing.d.ts +1 -1
  229. package/dist/topics.d.ts +1 -1
  230. package/dist/transcript.d.ts +1 -1
  231. package/dist/trust-score-stage.d.ts +1 -1
  232. package/dist/trust-score.d.ts +1 -1
  233. package/dist/{types-Cmkz7csa.d.ts → types-CHSXOq7s.d.ts} +34 -0
  234. package/dist/types.d.ts +1 -1
  235. package/dist/types.js +1 -1
  236. package/dist/utility-runtime.d.ts +1 -1
  237. package/dist/verified-recall.js +3 -3
  238. package/package.json +2 -2
  239. package/src/config.test.ts +44 -0
  240. package/src/config.ts +43 -0
  241. package/src/namespaces/catalog.test.ts +258 -0
  242. package/src/namespaces/catalog.ts +235 -10
  243. package/src/namespaces/storage.ts +9 -0
  244. package/src/orchestrator.ts +3 -0
  245. package/src/storage-catalog-touch-gating.test.ts +70 -0
  246. package/src/storage.ts +31 -5
  247. package/src/types.ts +34 -0
  248. package/dist/chunk-3ZVFLMFQ.js.map +0 -1
  249. package/dist/chunk-5U65RIVI.js.map +0 -1
  250. package/dist/chunk-7LMEY5KZ.js.map +0 -1
  251. package/dist/chunk-JZM5QUTE.js.map +0 -1
  252. package/dist/chunk-KXLRMM35.js.map +0 -1
  253. /package/dist/{chunk-L4H6MQ7I.js.map → chunk-2RSL6YRA.js.map} +0 -0
  254. /package/dist/{chunk-JYRFO4EH.js.map → chunk-2ZPQUTMW.js.map} +0 -0
  255. /package/dist/{chunk-673NPXE3.js.map → chunk-3SIC3E65.js.map} +0 -0
  256. /package/dist/{chunk-OVMS3DCB.js.map → chunk-675YU5IK.js.map} +0 -0
  257. /package/dist/{chunk-W6R43NLA.js.map → chunk-AOWPZ7TM.js.map} +0 -0
  258. /package/dist/{chunk-TGBJ2OMK.js.map → chunk-BQTSGK5Y.js.map} +0 -0
  259. /package/dist/{chunk-6LM6PPDX.js.map → chunk-CE3GMHT4.js.map} +0 -0
  260. /package/dist/{chunk-YXOAQAH2.js.map → chunk-CLFY35ED.js.map} +0 -0
  261. /package/dist/{chunk-HQ7QZJM5.js.map → chunk-D4TLWDYR.js.map} +0 -0
  262. /package/dist/{chunk-LQKITFGB.js.map → chunk-FUUF7FMX.js.map} +0 -0
  263. /package/dist/{chunk-MXRMNKSY.js.map → chunk-I3GKTDM2.js.map} +0 -0
  264. /package/dist/{chunk-EQF5763Z.js.map → chunk-IQULRIZM.js.map} +0 -0
  265. /package/dist/{chunk-DJCKXI4M.js.map → chunk-NVXQTRBL.js.map} +0 -0
  266. /package/dist/{chunk-GHVJASQ4.js.map → chunk-OCY3CRPG.js.map} +0 -0
  267. /package/dist/{chunk-YLTG6BO4.js.map → chunk-OIFZII6J.js.map} +0 -0
  268. /package/dist/{chunk-XF4KOVDK.js.map → chunk-PA6DMYEK.js.map} +0 -0
  269. /package/dist/{chunk-3OQ4IZJN.js.map → chunk-QPZCAVDC.js.map} +0 -0
  270. /package/dist/{chunk-DLWYAN6A.js.map → chunk-T6U7HDIY.js.map} +0 -0
  271. /package/dist/{chunk-7XRUYC6G.js.map → chunk-TITSEN2M.js.map} +0 -0
  272. /package/dist/{chunk-UTDA43TB.js.map → chunk-TWGGNUWJ.js.map} +0 -0
  273. /package/dist/{chunk-SBFDL2EQ.js.map → chunk-UTZW3X2R.js.map} +0 -0
  274. /package/dist/{chunk-SECMZACR.js.map → chunk-XUQUBZ7O.js.map} +0 -0
  275. /package/dist/{chunk-QHT4W3RU.js.map → chunk-ZASJ53ML.js.map} +0 -0
  276. /package/dist/{chunk-WUDBMLC6.js.map → chunk-ZC3HG22I.js.map} +0 -0
  277. /package/dist/{chunk-VCEH5UYS.js.map → chunk-ZRXSHGXT.js.map} +0 -0
@@ -2052,6 +2052,40 @@ interface PluginConfig {
2052
2052
  * existing single-namespace behavior is unchanged. Default on.
2053
2053
  */
2054
2054
  namespaceCatalogEnabled: boolean;
2055
+ /**
2056
+ * Size (bytes) at which a namespace-catalog touch triggers an in-lock
2057
+ * auto-compaction of `state/namespaces.jsonl` (issue #1903). The append-only
2058
+ * log grows one line per touch; once it exceeds this many bytes the next
2059
+ * touch folds it (last-record-wins, every namespace row preserved) and
2060
+ * atomically rewrites it. Default `16 * 1024 * 1024` (16 MB). `0` disables
2061
+ * auto-compaction (restores the pre-#1903 unbounded-append behavior).
2062
+ */
2063
+ namespacesCatalogCompactBytes: number;
2064
+ /**
2065
+ * Coalescing window (ms) for pure-timestamp READ touches on an
2066
+ * already-known namespace record (issue #1903). Repeated `markRead` touches
2067
+ * that only refresh `lastReadAt` are buffered per (namespace, kind) and
2068
+ * flushed once per window instead of appending a line each time.
2069
+ * Semantically-observable touches (first sight, provenance/field changes)
2070
+ * always flush immediately. Default `60000`. `0` disables read coalescing
2071
+ * (each touch appends immediately, the pre-#1903 behavior).
2072
+ */
2073
+ namespacesCatalogReadTouchCoalesceMs: number;
2074
+ /**
2075
+ * Coalescing window (ms) for pure-timestamp WRITE touches on an
2076
+ * already-known namespace record (issue #1903). Mirrors
2077
+ * `namespacesCatalogReadTouchCoalesceMs` for `markWrite`/`lastWriteAt`.
2078
+ * Default `1000`. `0` disables write coalescing.
2079
+ */
2080
+ namespacesCatalogWriteTouchCoalesceMs: number;
2081
+ /**
2082
+ * Whether pure state-file writes (`state/buffer.json`, ledgers, indexes)
2083
+ * touch the namespace catalog (issue #1903). State files are not namespace
2084
+ * memory data, so by default their writes do NOT record a catalog touch.
2085
+ * Set `true` to restore the pre-#1903 behavior where every secure-file
2086
+ * write touched the catalog. Default `false`.
2087
+ */
2088
+ namespacesCatalogTouchStateWrites: boolean;
2055
2089
  defaultNamespace: string;
2056
2090
  sharedNamespace: string;
2057
2091
  principalFromSessionKeyMode: PrincipalFromSessionKeyMode;
package/dist/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { aD as AccessTrackingEntry, A as ActiveRecallChatType, f as ActiveRecallModelFallbackPolicy, d as ActiveRecallPromptStyle, c as ActiveRecallQueryMode, e as ActiveRecallThinking, a3 as AgentAccessAuthToken, bp as AgentAccessHttpConfig, bq as AgentDefaultsConfig, br as AgentPersona, r as AgentPersonaModelConfig, bs as BehaviorLoopAdjustment, g as BehaviorLoopPolicyState, bt as BehaviorSignalDirection, B as BehaviorSignalEvent, bu as BehaviorSignalType, aW as BootstrapOptions, aX as BootstrapResult, l as BriefingActiveThread, bv as BriefingCalendarSourceError, b2 as BriefingConfig, j as BriefingFocus, i as BriefingFollowup, b3 as BriefingOpenCommitment, n as BriefingRecentEntity, m as BriefingResult, h as BriefingSections, b4 as BriefingWindow, bw as BufferEntryState, ay as BufferState, Z as BufferSurpriseEvent, o as BufferTurn, k as CalendarEvent, C as CalendarSource, bx as CaptureMode, by as ChatConfig, q as Checkpoint, bz as CodexCliReasoningEffort, bA as CodexCompactionFlushMode, b7 as CodexCompatConfig, bB as CodexConnectorConfig, aG as CodingContext, bC as CodingGraphLspConfig, a_ as CodingKnowledgeConfig, aZ as CodingModeConfig, bD as CompressionGuidelineActivationState, a5 as CompressionGuidelineOptimizerActionSummary, a4 as CompressionGuidelineOptimizerEventCounts, a6 as CompressionGuidelineOptimizerRuleUpdate, bE as CompressionGuidelineOptimizerSourceWindow, a9 as CompressionGuidelineOptimizerState, bF as ConfidenceTier, bG as ConsolidationAction, bH as ConsolidationItem, aY as ConsolidationObservation, z as ConsolidationResult, ah as ContinuityImprovementLoop, af as ContinuityIncidentCloseInput, ag as ContinuityIncidentOpenInput, ae as ContinuityIncidentRecord, bI as ContinuityIncidentState, bJ as ContinuityLoopCadence, ai as ContinuityLoopReviewInput, bK as ContinuityLoopStatus, aj as ContinuityLoopUpsertInput, bL as ContradictionScanConfig, U as ConversationThread, bM as CronConversationRecallMode, bN as CronRecallMode, b8 as DEFAULT_RECALL_DISCLOSURE, J as DaySummaryResult, bO as DreamingConfig, bP as DreamingNarrativePromptStyle, bQ as DreamsDeepSleepConfig, bR as DreamsLightSleepConfig, D as DreamsPhase, bS as DreamsPhaseStatus, bT as DreamsPhasesConfig, bU as DreamsRemConfig, a as DreamsRunResult, aH as DreamsStatusResult, bV as EngramTraceEvent, aC as EntityActivityEntry, E as EntityFile, aa as EntityMention, aB as EntityRelationship, ab as EntitySchemaDefinition, ac as EntitySchemaSectionDefinition, ad as EntityStructuredSection, aF as EntityTimelineEntry, b9 as ExtractedFact, bW as ExtractedProcedureStep, bX as ExtractedQuestion, bY as ExtractedReasoningTrace, bZ as ExtractedReasoningTraceStep, b_ as ExtractedRelationship, K as ExtractionFailureClass, b$ as ExtractionPassSource, y as ExtractionResult, F as FaithfulnessFrontmatter, aA as FileHygieneConfig, G as GatewayConfig, c0 as GitHubLiveConnectorConfig, c1 as GmailLiveConnectorConfig, c2 as GoogleDriveLiveConnectorConfig, c3 as HeartbeatConfig, c4 as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, Y as ImportanceLevel, ak as ImportanceScore, V as LifecycleState, L as LiveConnectorsConfig, c5 as LlmTraceCallback, c6 as LlmTraceEvent, am as MemoryActionEligibilityContext, bc as MemoryActionEligibilitySource, a8 as MemoryActionEvent, c7 as MemoryActionOutcome, c8 as MemoryActionPolicyDecision, an as MemoryActionPolicyResult, c9 as MemoryActionStatus, a7 as MemoryActionType, u as MemoryCategory, M as MemoryFile, p as MemoryFrontmatter, al as MemoryIntent, x as MemoryLifecycleEvent, ao as MemoryLifecycleEventType, ap as MemoryLifecycleStateSummary, ax as MemoryLink, ca as MemoryLinkType, bd as MemoryObservation, cb as MemoryOsPresetName, w as MemoryProjectionCurrentState, be as MemoryScope, v as MemoryStatus, aE as MemorySummary, az as MetaState, cc as ModelApi, cd as ModelDefinitionConfig, ce as ModelProviderAuthMode, aq as ModelProviderConfig, cf as NamespacePolicy, ar as NativeKnowledgeConfig, cg as NativeKnowledgeFolderRuleConfig, ch as NativeKnowledgeObsidianVaultConfig, ci as NativeKnowledgeOpenClawWorkspaceConfig, cj as NotionLiveConnectorConfig, P as PluginConfig, X as PolicyClass, ck as PrincipalFromSessionKeyMode, cl as PrincipalRule, cm as ProceduralConfig, as as ProvenanceConfig, N as ProvenanceSource, aw as QmdSearchExplain, Q as QmdSearchResult, cn as QuestionEntry, bf as RECALL_DISCLOSURE_LEVELS, co as ReasoningEffort, R as RecallDisclosure, cp as RecallPipelineConfig, b as RecallPlanMode, aV as RecallSectionConfig, s as RecallTierExplain, cq as RecallTraceEvent, cr as RelevanceFeedback, at as RetrievalTier, cs as SPECULATIVE_TTL_DAYS, ct as ScopeProfileAutoPromoteConfig, aI as ScopeProfileConfig, aJ as ScopeProfileLayerId, aK as ScopeProfilePromotionTarget, cu as ScopeProfileTeamProjectConfig, cv as ScopeTeamConfig, cw as ScoredEntity, a2 as SecretRef, cx as SemanticChunkingConfigShape, S as SessionObserverBandConfig, cy as SignalLevel, au as SignalScanResult, cz as SlotBehaviorConfig, cA as SlotMismatchMode, t as SourceConnectorProvenance, av as TopicScore, T as TranscriptEntry, cB as TriggerMode, O as TrustWeights, W as VerificationState, cC as confidenceTier, bm as isRecallDisclosure } from './types-Cmkz7csa.js';
1
+ export { aD as AccessTrackingEntry, A as ActiveRecallChatType, f as ActiveRecallModelFallbackPolicy, d as ActiveRecallPromptStyle, c as ActiveRecallQueryMode, e as ActiveRecallThinking, a3 as AgentAccessAuthToken, bp as AgentAccessHttpConfig, bq as AgentDefaultsConfig, br as AgentPersona, r as AgentPersonaModelConfig, bs as BehaviorLoopAdjustment, g as BehaviorLoopPolicyState, bt as BehaviorSignalDirection, B as BehaviorSignalEvent, bu as BehaviorSignalType, aW as BootstrapOptions, aX as BootstrapResult, l as BriefingActiveThread, bv as BriefingCalendarSourceError, b2 as BriefingConfig, j as BriefingFocus, i as BriefingFollowup, b3 as BriefingOpenCommitment, n as BriefingRecentEntity, m as BriefingResult, h as BriefingSections, b4 as BriefingWindow, bw as BufferEntryState, ay as BufferState, Z as BufferSurpriseEvent, o as BufferTurn, k as CalendarEvent, C as CalendarSource, bx as CaptureMode, by as ChatConfig, q as Checkpoint, bz as CodexCliReasoningEffort, bA as CodexCompactionFlushMode, b7 as CodexCompatConfig, bB as CodexConnectorConfig, aG as CodingContext, bC as CodingGraphLspConfig, a_ as CodingKnowledgeConfig, aZ as CodingModeConfig, bD as CompressionGuidelineActivationState, a5 as CompressionGuidelineOptimizerActionSummary, a4 as CompressionGuidelineOptimizerEventCounts, a6 as CompressionGuidelineOptimizerRuleUpdate, bE as CompressionGuidelineOptimizerSourceWindow, a9 as CompressionGuidelineOptimizerState, bF as ConfidenceTier, bG as ConsolidationAction, bH as ConsolidationItem, aY as ConsolidationObservation, z as ConsolidationResult, ah as ContinuityImprovementLoop, af as ContinuityIncidentCloseInput, ag as ContinuityIncidentOpenInput, ae as ContinuityIncidentRecord, bI as ContinuityIncidentState, bJ as ContinuityLoopCadence, ai as ContinuityLoopReviewInput, bK as ContinuityLoopStatus, aj as ContinuityLoopUpsertInput, bL as ContradictionScanConfig, U as ConversationThread, bM as CronConversationRecallMode, bN as CronRecallMode, b8 as DEFAULT_RECALL_DISCLOSURE, J as DaySummaryResult, bO as DreamingConfig, bP as DreamingNarrativePromptStyle, bQ as DreamsDeepSleepConfig, bR as DreamsLightSleepConfig, D as DreamsPhase, bS as DreamsPhaseStatus, bT as DreamsPhasesConfig, bU as DreamsRemConfig, a as DreamsRunResult, aH as DreamsStatusResult, bV as EngramTraceEvent, aC as EntityActivityEntry, E as EntityFile, aa as EntityMention, aB as EntityRelationship, ab as EntitySchemaDefinition, ac as EntitySchemaSectionDefinition, ad as EntityStructuredSection, aF as EntityTimelineEntry, b9 as ExtractedFact, bW as ExtractedProcedureStep, bX as ExtractedQuestion, bY as ExtractedReasoningTrace, bZ as ExtractedReasoningTraceStep, b_ as ExtractedRelationship, K as ExtractionFailureClass, b$ as ExtractionPassSource, y as ExtractionResult, F as FaithfulnessFrontmatter, aA as FileHygieneConfig, G as GatewayConfig, c0 as GitHubLiveConnectorConfig, c1 as GmailLiveConnectorConfig, c2 as GoogleDriveLiveConnectorConfig, c3 as HeartbeatConfig, c4 as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, Y as ImportanceLevel, ak as ImportanceScore, V as LifecycleState, L as LiveConnectorsConfig, c5 as LlmTraceCallback, c6 as LlmTraceEvent, am as MemoryActionEligibilityContext, bc as MemoryActionEligibilitySource, a8 as MemoryActionEvent, c7 as MemoryActionOutcome, c8 as MemoryActionPolicyDecision, an as MemoryActionPolicyResult, c9 as MemoryActionStatus, a7 as MemoryActionType, u as MemoryCategory, M as MemoryFile, p as MemoryFrontmatter, al as MemoryIntent, x as MemoryLifecycleEvent, ao as MemoryLifecycleEventType, ap as MemoryLifecycleStateSummary, ax as MemoryLink, ca as MemoryLinkType, bd as MemoryObservation, cb as MemoryOsPresetName, w as MemoryProjectionCurrentState, be as MemoryScope, v as MemoryStatus, aE as MemorySummary, az as MetaState, cc as ModelApi, cd as ModelDefinitionConfig, ce as ModelProviderAuthMode, aq as ModelProviderConfig, cf as NamespacePolicy, ar as NativeKnowledgeConfig, cg as NativeKnowledgeFolderRuleConfig, ch as NativeKnowledgeObsidianVaultConfig, ci as NativeKnowledgeOpenClawWorkspaceConfig, cj as NotionLiveConnectorConfig, P as PluginConfig, X as PolicyClass, ck as PrincipalFromSessionKeyMode, cl as PrincipalRule, cm as ProceduralConfig, as as ProvenanceConfig, N as ProvenanceSource, aw as QmdSearchExplain, Q as QmdSearchResult, cn as QuestionEntry, bf as RECALL_DISCLOSURE_LEVELS, co as ReasoningEffort, R as RecallDisclosure, cp as RecallPipelineConfig, b as RecallPlanMode, aV as RecallSectionConfig, s as RecallTierExplain, cq as RecallTraceEvent, cr as RelevanceFeedback, at as RetrievalTier, cs as SPECULATIVE_TTL_DAYS, ct as ScopeProfileAutoPromoteConfig, aI as ScopeProfileConfig, aJ as ScopeProfileLayerId, aK as ScopeProfilePromotionTarget, cu as ScopeProfileTeamProjectConfig, cv as ScopeTeamConfig, cw as ScoredEntity, a2 as SecretRef, cx as SemanticChunkingConfigShape, S as SessionObserverBandConfig, cy as SignalLevel, au as SignalScanResult, cz as SlotBehaviorConfig, cA as SlotMismatchMode, t as SourceConnectorProvenance, av as TopicScore, T as TranscriptEntry, cB as TriggerMode, O as TrustWeights, W as VerificationState, cC as confidenceTier, bm as isRecallDisclosure } from './types-CHSXOq7s.js';
2
2
  import './message-parts/index.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-3ZVFLMFQ.js";
7
+ } from "./chunk-G5PVJJRA.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-Cmkz7csa.js';
2
+ import './types-CHSXOq7s.js';
3
3
  import './message-parts/index.js';
4
4
  import './lifecycle.js';
5
5
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-YLTG6BO4.js";
4
+ } from "./chunk-OIFZII6J.js";
5
5
  import "./chunk-2A3OXAP2.js";
6
6
  import "./chunk-ZBJMUXZH.js";
7
- import "./chunk-KXLRMM35.js";
7
+ import "./chunk-TRH533CA.js";
8
8
  import "./chunk-L2B3EE7B.js";
9
9
  import "./chunk-B3ABVQ36.js";
10
10
  import "./chunk-5UZXUTVO.js";
@@ -15,7 +15,7 @@ import "./chunk-6CIAGZ6X.js";
15
15
  import "./chunk-J6A3CX5N.js";
16
16
  import "./chunk-4KJLYTLS.js";
17
17
  import "./chunk-SVOZFLIQ.js";
18
- import "./chunk-3ZVFLMFQ.js";
18
+ import "./chunk-G5PVJJRA.js";
19
19
  import "./chunk-RULE4VG5.js";
20
20
  import "./chunk-SCU65EZI.js";
21
21
  import "./chunk-RHOZ5LLO.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.7.11",
3
+ "version": "9.7.12",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3010,7 +3010,7 @@
3010
3010
  "core"
3011
3011
  ],
3012
3012
  "peerDependencies": {
3013
- "@remnic/coding-graph": "^9.7.11"
3013
+ "@remnic/coding-graph": "^9.7.12"
3014
3014
  },
3015
3015
  "peerDependenciesMeta": {
3016
3016
  "@remnic/coding-graph": {
@@ -2161,3 +2161,47 @@ test("parseConfig rejects non-positive extraction retry/breaker numerics and fal
2161
2161
  assert.equal(c.extractionBreakerAuthCooldownMs, 1_800_000, "non-positive auth cooldown -> default");
2162
2162
  assert.deepEqual(c.extractionRetryScheduleMs, [60_000, 300_000, 1_800_000, 7_200_000], "schedule with non-positive entry -> default");
2163
2163
  });
2164
+
2165
+ test("parseConfig namespace-catalog touch-path knobs: defaults, 0-accepting numerics, and validation (#1903)", () => {
2166
+ // Documented defaults when absent.
2167
+ const d = parseConfig({});
2168
+ assert.equal(d.namespacesCatalogCompactBytes, 16 * 1024 * 1024, "compactBytes default 16 MB");
2169
+ assert.equal(d.namespacesCatalogReadTouchCoalesceMs, 60_000, "read coalesce default 60000ms");
2170
+ assert.equal(d.namespacesCatalogWriteTouchCoalesceMs, 1_000, "write coalesce default 1000ms");
2171
+ assert.equal(d.namespacesCatalogTouchStateWrites, false, "state-write touch default false");
2172
+
2173
+ // 0 is an accepted disable switch for every numeric (resolveNonNegativeIntegerConfig).
2174
+ assert.equal(parseConfig({ namespacesCatalogCompactBytes: 0 }).namespacesCatalogCompactBytes, 0);
2175
+ assert.equal(parseConfig({ namespacesCatalogReadTouchCoalesceMs: 0 }).namespacesCatalogReadTouchCoalesceMs, 0);
2176
+ assert.equal(parseConfig({ namespacesCatalogWriteTouchCoalesceMs: 0 }).namespacesCatalogWriteTouchCoalesceMs, 0);
2177
+
2178
+ // Positive integers pass through; CLI/env numeric strings coerce.
2179
+ assert.equal(parseConfig({ namespacesCatalogCompactBytes: 4096 }).namespacesCatalogCompactBytes, 4096);
2180
+ assert.equal(parseConfig({ namespacesCatalogReadTouchCoalesceMs: "30000" }).namespacesCatalogReadTouchCoalesceMs, 30_000);
2181
+
2182
+ // Boolean-like coercion for the state-write toggle (gotcha #36).
2183
+ assert.equal(parseConfig({ namespacesCatalogTouchStateWrites: true }).namespacesCatalogTouchStateWrites, true);
2184
+ assert.equal(parseConfig({ namespacesCatalogTouchStateWrites: "true" }).namespacesCatalogTouchStateWrites, true);
2185
+ assert.equal(parseConfig({ namespacesCatalogTouchStateWrites: "off" }).namespacesCatalogTouchStateWrites, false);
2186
+
2187
+ // Negative and non-integer numerics are rejected with the documented message.
2188
+ for (const key of [
2189
+ "namespacesCatalogCompactBytes",
2190
+ "namespacesCatalogReadTouchCoalesceMs",
2191
+ "namespacesCatalogWriteTouchCoalesceMs",
2192
+ ]) {
2193
+ for (const bad of [-1, 1.5, "1.5", "abc", Number.NaN, Infinity]) {
2194
+ assert.throws(
2195
+ () => parseConfig({ [key]: bad }),
2196
+ new RegExp(`${key} must be a non-negative integer`),
2197
+ `${key}=${JSON.stringify(bad)} should throw`,
2198
+ );
2199
+ }
2200
+ }
2201
+
2202
+ // A present-but-malformed boolean toggle fails fast (gotcha #51).
2203
+ assert.throws(
2204
+ () => parseConfig({ namespacesCatalogTouchStateWrites: "maybe" }),
2205
+ /namespacesCatalogTouchStateWrites must be a boolean-like value/,
2206
+ );
2207
+ });
package/src/config.ts CHANGED
@@ -435,6 +435,26 @@ function resolvePositiveIntegerConfig(
435
435
  return coerced;
436
436
  }
437
437
 
438
+ function resolveNonNegativeIntegerConfig(
439
+ value: unknown,
440
+ defaultValue: number,
441
+ keyName: string,
442
+ ): number {
443
+ if (value === undefined || value === null) return defaultValue;
444
+ const coerced = coerceNumber(value);
445
+ if (
446
+ coerced === undefined ||
447
+ !Number.isFinite(coerced) ||
448
+ !Number.isInteger(coerced) ||
449
+ coerced < 0
450
+ ) {
451
+ throw new Error(
452
+ `${keyName} must be a non-negative integer; got ${JSON.stringify(value)}`,
453
+ );
454
+ }
455
+ return coerced;
456
+ }
457
+
438
458
  export function isOpenaiApiKeyDisabled(value: unknown): boolean {
439
459
  return value === false || (typeof value === "string" && value.trim().toLowerCase() === "false");
440
460
  }
@@ -2934,6 +2954,29 @@ export function parseConfig(
2934
2954
  // silently defaulting to enabled (CLAUDE.md rule #51); default to enabled
2935
2955
  // only when the value is absent.
2936
2956
  namespaceCatalogEnabled: resolveNamespaceCatalogEnabled(cfg.namespaceCatalogEnabled, rawOperatorConfig, runtimeSet),
2957
+ // Namespace catalog touch-path performance knobs (issue #1903). Numerics use
2958
+ // resolveNonNegativeIntegerConfig so 0 is an accepted disable switch; a
2959
+ // present-but-invalid value (negative/non-integer) is REJECTED (rule #51).
2960
+ namespacesCatalogCompactBytes: resolveNonNegativeIntegerConfig(
2961
+ cfg.namespacesCatalogCompactBytes,
2962
+ 16 * 1024 * 1024,
2963
+ "namespacesCatalogCompactBytes",
2964
+ ),
2965
+ namespacesCatalogReadTouchCoalesceMs: resolveNonNegativeIntegerConfig(
2966
+ cfg.namespacesCatalogReadTouchCoalesceMs,
2967
+ 60_000,
2968
+ "namespacesCatalogReadTouchCoalesceMs",
2969
+ ),
2970
+ namespacesCatalogWriteTouchCoalesceMs: resolveNonNegativeIntegerConfig(
2971
+ cfg.namespacesCatalogWriteTouchCoalesceMs,
2972
+ 1_000,
2973
+ "namespacesCatalogWriteTouchCoalesceMs",
2974
+ ),
2975
+ namespacesCatalogTouchStateWrites: resolveBooleanConfig(
2976
+ cfg.namespacesCatalogTouchStateWrites,
2977
+ false,
2978
+ "namespacesCatalogTouchStateWrites",
2979
+ ),
2937
2980
  // NOTE: namespace identifiers are intentionally NOT sanitized here — the
2938
2981
  // codebase rejects unsafe namespaces at the point of use (see
2939
2982
  // codex-materialize-runner and NamespaceStorageRouter / resolveNamespaceDir),
@@ -3694,3 +3694,261 @@ test("router resolve-hook serializer recovers after a prior hook rejects (issue
3694
3694
  await rm(memoryDir, { recursive: true, force: true });
3695
3695
  }
3696
3696
  });
3697
+
3698
+ // ── Issue #1903: touch-path performance (auto-compaction, coalescing, streaming) ──
3699
+
3700
+ async function countCatalogLines(memoryDir: string): Promise<number> {
3701
+ const raw = await readFile(path.join(memoryDir, "state", "namespaces.jsonl"), "utf8").catch(() => "");
3702
+ return raw.split("\n").filter((line) => line.trim().length > 0).length;
3703
+ }
3704
+
3705
+ // 1. Warm cache: with coalescing OFF every touch appends, but a warm cache means
3706
+ // none of them re-parses the JSONL after the initial warm-up parse.
3707
+ test("#1903 warm cache: repeated touches never re-parse the log (coalescing off)", async () => {
3708
+ const memoryDir = await mkMemoryDir();
3709
+ try {
3710
+ const catalog = new CountingNamespaceCatalog(
3711
+ makeConfig(memoryDir, {
3712
+ namespacesCatalogReadTouchCoalesceMs: 0,
3713
+ namespacesCatalogWriteTouchCoalesceMs: 0,
3714
+ }),
3715
+ );
3716
+ // Warm the cache: the first write creates the file; a read then performs the
3717
+ // one-time full parse and warms the in-process cache.
3718
+ await catalog.markWrite("alpha", { discoveredBy: "write" });
3719
+ await catalog.listNamespaces();
3720
+ const warm = catalog.catalogReadCount;
3721
+ assert.ok(warm >= 1, "the warm-up performed at least one full parse");
3722
+ for (let i = 0; i < 25; i++) {
3723
+ await catalog.markWrite("alpha", { discoveredBy: "write" });
3724
+ await catalog.markRead("alpha", { discoveredBy: "read" });
3725
+ }
3726
+ assert.equal(
3727
+ catalog.catalogReadCount,
3728
+ warm,
3729
+ "every subsequent touch is served from the warm cache without a re-parse",
3730
+ );
3731
+ } finally {
3732
+ await rm(memoryDir, { recursive: true, force: true });
3733
+ }
3734
+ });
3735
+
3736
+ // 2. Write-touch coalescing collapses many in-window writes into a single append,
3737
+ // and the newest buffered timestamp wins on flush.
3738
+ test("#1903 write-touch coalescing collapses 50 in-window writes to one append", async () => {
3739
+ const memoryDir = await mkMemoryDir();
3740
+ try {
3741
+ const catalog = new NamespaceCatalog(
3742
+ makeConfig(memoryDir, { namespacesCatalogWriteTouchCoalesceMs: 60_000 }),
3743
+ );
3744
+ // Seed the record, then read it back so the in-process cache is warm — the
3745
+ // following writes are then pure-timestamp refreshes on a known record and
3746
+ // are deferred (a cold cache would force the first one to flush immediately).
3747
+ await catalog.markWrite("alpha", { discoveredBy: "write" });
3748
+ await catalog.getNamespaceRecord("alpha");
3749
+ const linesAfterSeed = await countCatalogLines(memoryDir);
3750
+ let newest = new Date();
3751
+ for (let i = 0; i < 50; i++) {
3752
+ newest = new Date(Date.now() + (i + 1) * 1000);
3753
+ await catalog.markWrite("alpha", { discoveredBy: "write", at: newest });
3754
+ }
3755
+ assert.equal(
3756
+ await countCatalogLines(memoryDir),
3757
+ linesAfterSeed,
3758
+ "coalesced writes stay buffered — no per-touch append",
3759
+ );
3760
+ await catalog.flushPendingTouches();
3761
+ assert.equal(
3762
+ await countCatalogLines(memoryDir),
3763
+ linesAfterSeed + 1,
3764
+ "the 50 buffered writes collapse into exactly one append on flush",
3765
+ );
3766
+ const record = await catalog.getNamespaceRecord("alpha");
3767
+ assert.equal(
3768
+ record?.lastWriteAt,
3769
+ newest.toISOString(),
3770
+ "the newest in-window write timestamp wins",
3771
+ );
3772
+ } finally {
3773
+ await rm(memoryDir, { recursive: true, force: true });
3774
+ }
3775
+ });
3776
+
3777
+ // 3. Read-touch coalescing collapses in-window reads and keeps the latest freshness.
3778
+ test("#1903 read-touch coalescing keeps the latest in-window lastReadAt", async () => {
3779
+ const memoryDir = await mkMemoryDir();
3780
+ try {
3781
+ const catalog = new NamespaceCatalog(
3782
+ makeConfig(memoryDir, { namespacesCatalogReadTouchCoalesceMs: 60_000 }),
3783
+ );
3784
+ // Seed the read field, then warm the cache so the following reads defer.
3785
+ await catalog.markRead("alpha", { discoveredBy: "read" });
3786
+ await catalog.getNamespaceRecord("alpha");
3787
+ const linesAfterSeed = await countCatalogLines(memoryDir);
3788
+ let newest = new Date();
3789
+ for (let i = 0; i < 30; i++) {
3790
+ newest = new Date(Date.now() + (i + 1) * 1000);
3791
+ await catalog.markRead("alpha", { discoveredBy: "read", at: newest });
3792
+ }
3793
+ assert.equal(
3794
+ await countCatalogLines(memoryDir),
3795
+ linesAfterSeed,
3796
+ "coalesced reads stay buffered — no per-touch append",
3797
+ );
3798
+ await catalog.flushPendingTouches();
3799
+ assert.equal(await countCatalogLines(memoryDir), linesAfterSeed + 1);
3800
+ const record = await catalog.getNamespaceRecord("alpha");
3801
+ assert.equal(
3802
+ record?.lastReadAt,
3803
+ newest.toISOString(),
3804
+ "the newest in-window read timestamp wins",
3805
+ );
3806
+ } finally {
3807
+ await rm(memoryDir, { recursive: true, force: true });
3808
+ }
3809
+ });
3810
+
3811
+ // 4. Provenance upgrade under coalescing: a config pre-registration followed by a
3812
+ // write must read back "write" WITHOUT an explicit flush (immediate-flush rule).
3813
+ test("#1903 markWrite upgrades config→write under coalescing without an explicit flush", async () => {
3814
+ const memoryDir = await mkMemoryDir();
3815
+ try {
3816
+ const catalog = new NamespaceCatalog(
3817
+ makeConfig(memoryDir, {
3818
+ namespacesCatalogReadTouchCoalesceMs: 60_000,
3819
+ namespacesCatalogWriteTouchCoalesceMs: 60_000,
3820
+ }),
3821
+ );
3822
+ const ns = "project-origin-prov";
3823
+ const storageDir = path.join(memoryDir, "namespaces", namespaceIdentityToken(ns));
3824
+ await catalog.registerResolved(ns, storageDir);
3825
+ assert.equal(
3826
+ (await catalog.getNamespaceRecord(ns))?.discoveredBy,
3827
+ "config",
3828
+ "pre-registration is discovered by config",
3829
+ );
3830
+ // A real write must upgrade the provenance immediately even with coalescing on.
3831
+ await catalog.markWrite(ns, { discoveredBy: "write", storageDir });
3832
+ assert.equal(
3833
+ (await catalog.getNamespaceRecord(ns))?.discoveredBy,
3834
+ "write",
3835
+ "the config→write upgrade flushes immediately (not coalesced away)",
3836
+ );
3837
+ } finally {
3838
+ await rm(memoryDir, { recursive: true, force: true });
3839
+ }
3840
+ });
3841
+
3842
+ // 5. Zero-limit semantics: CompactBytes=0 never rewrites and CoalesceMs=0 appends
3843
+ // one line per touch (line count == touch count) — the pre-#1903 behavior.
3844
+ test("#1903 zero knobs restore append-per-touch with no compaction", async () => {
3845
+ const memoryDir = await mkMemoryDir();
3846
+ try {
3847
+ const catalog = new NamespaceCatalog(
3848
+ makeConfig(memoryDir, {
3849
+ namespacesCatalogCompactBytes: 0,
3850
+ namespacesCatalogReadTouchCoalesceMs: 0,
3851
+ namespacesCatalogWriteTouchCoalesceMs: 0,
3852
+ }),
3853
+ );
3854
+ const touches = 12;
3855
+ for (let i = 0; i < touches; i++) {
3856
+ await catalog.markWrite("alpha", { discoveredBy: "write" });
3857
+ }
3858
+ assert.equal(
3859
+ await countCatalogLines(memoryDir),
3860
+ touches,
3861
+ "no coalescing and no compaction → exactly one append per touch",
3862
+ );
3863
+ } finally {
3864
+ await rm(memoryDir, { recursive: true, force: true });
3865
+ }
3866
+ });
3867
+
3868
+ // 6. Auto-compaction bounds the log to one row per namespace and preserves every
3869
+ // namespace with its newest fields. A tiny limit forces compaction on each touch.
3870
+ test("#1903 auto-compaction bounds the log to one row per namespace and preserves all", async () => {
3871
+ const memoryDir = await mkMemoryDir();
3872
+ try {
3873
+ const catalog = new NamespaceCatalog(
3874
+ makeConfig(memoryDir, {
3875
+ namespacesCatalogCompactBytes: 100,
3876
+ namespacesCatalogReadTouchCoalesceMs: 0,
3877
+ namespacesCatalogWriteTouchCoalesceMs: 0,
3878
+ }),
3879
+ );
3880
+ const namespaces = [
3881
+ "project-origin-a",
3882
+ "project-origin-b",
3883
+ "project-origin-c",
3884
+ "project-origin-d",
3885
+ "project-origin-e",
3886
+ ];
3887
+ const newest = new Map<string, string>();
3888
+ for (let round = 0; round < 20; round++) {
3889
+ for (const ns of namespaces) {
3890
+ const at = new Date(Date.now() + (round * namespaces.length + namespaces.indexOf(ns)) * 1000);
3891
+ newest.set(ns, at.toISOString());
3892
+ await catalog.markWrite(ns, { discoveredBy: "write", at });
3893
+ }
3894
+ }
3895
+ assert.equal(
3896
+ await countCatalogLines(memoryDir),
3897
+ namespaces.length,
3898
+ "the log is bounded to exactly one row per namespace after compaction",
3899
+ );
3900
+ const list = await catalog.listNamespaces();
3901
+ for (const ns of namespaces) {
3902
+ const record = list.find((r) => r.namespace === ns);
3903
+ assert.ok(record, `namespace ${ns} survives compaction`);
3904
+ assert.equal(
3905
+ record?.lastWriteAt,
3906
+ newest.get(ns),
3907
+ `namespace ${ns} keeps its newest write timestamp through compaction`,
3908
+ );
3909
+ }
3910
+ } finally {
3911
+ await rm(memoryDir, { recursive: true, force: true });
3912
+ }
3913
+ });
3914
+
3915
+ // 7. Cross-process cache-coherence: compaction on one instance must never drop
3916
+ // another instance's rows (mirrors the rebuild-race tests). A third fresh
3917
+ // instance must read back every namespace touched by either.
3918
+ test("#1903 cross-instance compaction never drops another instance's rows", async () => {
3919
+ const memoryDir = await mkMemoryDir();
3920
+ try {
3921
+ const cfg = () =>
3922
+ makeConfig(memoryDir, {
3923
+ namespacesCatalogCompactBytes: 100,
3924
+ namespacesCatalogReadTouchCoalesceMs: 0,
3925
+ namespacesCatalogWriteTouchCoalesceMs: 0,
3926
+ });
3927
+ // Separate instances behave like separate processes (own lock owner id).
3928
+ const a = new NamespaceCatalog(cfg());
3929
+ const b = new NamespaceCatalog(cfg());
3930
+ const written = new Set<string>();
3931
+ for (let round = 0; round < 8; round++) {
3932
+ const nsA = `project-origin-a${round}`;
3933
+ const nsB = `project-origin-b${round}`;
3934
+ written.add(nsA);
3935
+ written.add(nsB);
3936
+ // Interleave: A appends a fresh namespace while B triggers a compaction of
3937
+ // the shared log. The cross-process lock serializes the two.
3938
+ await Promise.all([
3939
+ a.markWrite(nsA, { discoveredBy: "write" }),
3940
+ b.markWrite(nsB, { discoveredBy: "write" }),
3941
+ ]);
3942
+ }
3943
+ const fresh = new NamespaceCatalog(makeConfig(memoryDir));
3944
+ const list = await fresh.listNamespaces();
3945
+ for (const ns of written) {
3946
+ assert.ok(
3947
+ list.some((r) => r.namespace === ns),
3948
+ `namespace ${ns} must survive cross-instance interleaved compaction`,
3949
+ );
3950
+ }
3951
+ } finally {
3952
+ await rm(memoryDir, { recursive: true, force: true });
3953
+ }
3954
+ });