@remnic/core 9.64.0 → 9.64.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +13 -13
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +14 -14
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +13 -13
  7. package/dist/access-cli.js +47 -47
  8. package/dist/access-coding-context-resolution.d.ts +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +11 -11
  10. package/dist/access-extraction-force-flush.js +13 -13
  11. package/dist/access-health-types.d.ts +1 -1
  12. package/dist/access-http-lcm-compaction.d.ts +11 -11
  13. package/dist/access-http-lifecycle-flush.d.ts +11 -11
  14. package/dist/access-http-offline-stream.d.ts +11 -11
  15. package/dist/access-http-query.d.ts +2 -2
  16. package/dist/access-http-query.js +15 -15
  17. package/dist/access-http.d.ts +12 -12
  18. package/dist/access-http.js +24 -24
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +13 -13
  21. package/dist/access-lcm-surface.d.ts +11 -11
  22. package/dist/access-lcm-surface.js +13 -13
  23. package/dist/access-mcp.d.ts +11 -11
  24. package/dist/access-mcp.js +18 -18
  25. package/dist/access-memory-search-fanout.d.ts +2 -2
  26. package/dist/{access-namespace-preflight-CrF0FKaz.d.ts → access-namespace-preflight-Dj9uR4Cq.d.ts} +1 -1
  27. package/dist/access-namespace-preflight.d.ts +3 -3
  28. package/dist/access-namespace-preflight.js +13 -13
  29. package/dist/access-observe-write-surface.d.ts +11 -11
  30. package/dist/access-observe-write-surface.js +13 -13
  31. package/dist/access-offline-manifest.d.ts +9 -9
  32. package/dist/access-operations-batch.js +14 -14
  33. package/dist/access-operations.d.ts +14 -14
  34. package/dist/access-operations.js +17 -17
  35. package/dist/access-recall-concurrency.d.ts +11 -11
  36. package/dist/access-recall-concurrency.js +13 -13
  37. package/dist/access-recall-response.d.ts +11 -11
  38. package/dist/access-recall-response.js +13 -13
  39. package/dist/access-recall-surface.d.ts +11 -11
  40. package/dist/access-recall-surface.js +13 -13
  41. package/dist/access-schema.d.ts +88 -88
  42. package/dist/{access-service-BqF4UK8Z.d.ts → access-service-Bd0lzaHp.d.ts} +71 -71
  43. package/dist/access-service-helpers.d.ts +11 -11
  44. package/dist/access-service.d.ts +11 -11
  45. package/dist/access-service.js +13 -13
  46. package/dist/access-surface-catalog.d.ts +11 -11
  47. package/dist/access-surface-catalog.js +15 -15
  48. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  49. package/dist/action-confidence.d.ts +1 -1
  50. package/dist/active-memory-bridge.d.ts +1 -1
  51. package/dist/active-recall.d.ts +1 -1
  52. package/dist/active-recall.js +1 -1
  53. package/dist/ambient-provenance.d.ts +1 -1
  54. package/dist/artifact-search.d.ts +1 -1
  55. package/dist/behavior-learner.d.ts +1 -1
  56. package/dist/behavior-signals.d.ts +1 -1
  57. package/dist/bootstrap.d.ts +9 -9
  58. package/dist/briefing-window.d.ts +2 -2
  59. package/dist/briefing.d.ts +2 -2
  60. package/dist/briefing.js +3 -3
  61. package/dist/buffer-surprise-report.d.ts +1 -1
  62. package/dist/buffer-turn-helpers.d.ts +1 -1
  63. package/dist/buffer.d.ts +2 -2
  64. package/dist/buffer.js +2 -2
  65. package/dist/bulk-import/index.d.ts +3 -3
  66. package/dist/calibration.d.ts +1 -1
  67. package/dist/capabilities.d.ts +1 -1
  68. package/dist/{catalog-C0KIwZqR.d.ts → catalog-rKYbN7zF.d.ts} +1 -1
  69. package/dist/causal-behavior.d.ts +1 -1
  70. package/dist/causal-consolidation.d.ts +1 -1
  71. package/dist/causal-consolidation.js +4 -4
  72. package/dist/causal-trajectory-graph.d.ts +1 -1
  73. package/dist/{chunk-5SZ6CLGJ.js → chunk-2RZCOG63.js} +3 -2
  74. package/dist/{chunk-5SZ6CLGJ.js.map → chunk-2RZCOG63.js.map} +1 -1
  75. package/dist/{chunk-7DRSWPUV.js → chunk-3CYVTO6A.js} +3 -3
  76. package/dist/{chunk-LJQGQUNS.js → chunk-4FSCGYTQ.js} +2 -2
  77. package/dist/{chunk-ML4MI2RR.js → chunk-4KKVDOCD.js} +24 -24
  78. package/dist/{chunk-Z3Q5I5F6.js → chunk-5Q5BEWJ3.js} +2 -2
  79. package/dist/{chunk-6BV6ZZZA.js → chunk-6I6IOMKS.js} +2 -2
  80. package/dist/{chunk-GIMQJ5RX.js → chunk-7D3TTGHY.js} +20 -11
  81. package/dist/chunk-7D3TTGHY.js.map +1 -0
  82. package/dist/{chunk-4IIDTPSK.js → chunk-7X35RPN3.js} +2 -2
  83. package/dist/{chunk-FEP45MYY.js → chunk-AGC3Q53R.js} +2 -2
  84. package/dist/{chunk-ZWYXSF2T.js → chunk-AL43367I.js} +96 -4
  85. package/dist/chunk-AL43367I.js.map +1 -0
  86. package/dist/{chunk-WCWIV2JH.js → chunk-B2GICNOJ.js} +2 -2
  87. package/dist/{chunk-BTBAHTQ5.js → chunk-CI6YIT5S.js} +2 -2
  88. package/dist/{chunk-CUDSA46O.js → chunk-CUIAHZPZ.js} +2 -2
  89. package/dist/{chunk-BTUIHRRR.js → chunk-D267K4V5.js} +2 -2
  90. package/dist/{chunk-QICOU4RY.js → chunk-DLZBOEJY.js} +2 -2
  91. package/dist/{chunk-AR253JBM.js → chunk-DPPTG6VJ.js} +1 -1
  92. package/dist/chunk-DPPTG6VJ.js.map +1 -0
  93. package/dist/{chunk-BPLPGOUJ.js → chunk-DRS3OX77.js} +2 -2
  94. package/dist/{chunk-3F3CVVDS.js → chunk-DTQ3OB7O.js} +2 -2
  95. package/dist/{chunk-Z4UPRZR6.js → chunk-DZ2AVS2H.js} +2 -2
  96. package/dist/{chunk-YINSSARY.js → chunk-EAWJSY2Z.js} +1 -1
  97. package/dist/{chunk-QFPX7QZG.js → chunk-FMGTFMT3.js} +9 -9
  98. package/dist/{chunk-CTD5CPD2.js → chunk-GSQDPL45.js} +3 -3
  99. package/dist/{chunk-UUYQWKHO.js → chunk-IHPYTG6F.js} +2 -2
  100. package/dist/{chunk-QATILW4B.js → chunk-IPUUMLSM.js} +21 -21
  101. package/dist/{chunk-2BRCV2IV.js → chunk-JE3AKPOO.js} +2 -2
  102. package/dist/{chunk-DZFPHARJ.js → chunk-KOE3RLNN.js} +2 -2
  103. package/dist/{chunk-WQ6TRWAE.js → chunk-NMQXUGTU.js} +3 -3
  104. package/dist/{chunk-65NQFK7D.js → chunk-OTVHRZZD.js} +2 -2
  105. package/dist/{chunk-77E7LLMV.js → chunk-OWAYQK5V.js} +8 -8
  106. package/dist/{chunk-YGSDWO3U.js → chunk-P4SD7BSA.js} +2 -2
  107. package/dist/{chunk-G5F4MXXC.js → chunk-Q3IRE6E4.js} +2 -2
  108. package/dist/{chunk-HZXO2CJ6.js → chunk-QHNO6AND.js} +2 -2
  109. package/dist/{chunk-5OOTHWGJ.js → chunk-RMB22Q3R.js} +2 -2
  110. package/dist/{chunk-BB2LH5JM.js → chunk-RNORDDD6.js} +11 -1
  111. package/dist/chunk-RNORDDD6.js.map +1 -0
  112. package/dist/{chunk-R7XE5ZUR.js → chunk-UIYYS2YI.js} +2 -2
  113. package/dist/{chunk-SYU5JOBQ.js → chunk-UWSETW6O.js} +2 -2
  114. package/dist/{chunk-CK3DZ6OE.js → chunk-VCNGVU62.js} +2 -2
  115. package/dist/{chunk-KG2CT762.js → chunk-VSKA2ATE.js} +2 -2
  116. package/dist/{chunk-PQODT53H.js → chunk-WDFXRHOZ.js} +3 -3
  117. package/dist/{chunk-6NMZXEJ7.js → chunk-WNO5W2TP.js} +2 -2
  118. package/dist/{chunk-63JMI45N.js → chunk-XBRGJJK7.js} +2 -2
  119. package/dist/{chunk-S6KMN6MJ.js → chunk-XFAWGUNX.js} +2 -2
  120. package/dist/{chunk-SRLQROQJ.js → chunk-XSPRZZEX.js} +3 -3
  121. package/dist/{chunk-EHC3FIJ4.js → chunk-Y5ZIQFWH.js} +2 -2
  122. package/dist/{chunk-6IKXAAJI.js → chunk-YY63AMGP.js} +2 -2
  123. package/dist/{chunk-7ML7STOD.js → chunk-ZGGTIBWN.js} +2 -2
  124. package/dist/{chunk-X6HNXVXS.js → chunk-ZUIVJ3BB.js} +4 -4
  125. package/dist/{cli-BX0O688c.d.ts → cli-0dDmOsJ8.d.ts} +5 -5
  126. package/dist/cli.d.ts +13 -13
  127. package/dist/cli.js +37 -37
  128. package/dist/coding/pre-action-gate.d.ts +1 -1
  129. package/dist/compounding/engine.d.ts +2 -2
  130. package/dist/compounding/engine.js +3 -3
  131. package/dist/compounding/preference-consolidator.d.ts +1 -1
  132. package/dist/compression-optimizer.d.ts +1 -1
  133. package/dist/config.d.ts +1 -1
  134. package/dist/config.js +1 -1
  135. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  136. package/dist/connectors/codex-materialize-runner.js +3 -3
  137. package/dist/connectors/codex-materialize.d.ts +1 -1
  138. package/dist/connectors/index.d.ts +1 -1
  139. package/dist/connectors/index.js +3 -3
  140. package/dist/consolidation-provenance-check.d.ts +2 -2
  141. package/dist/consolidation-undo.d.ts +2 -2
  142. package/dist/contradiction/index.d.ts +2 -2
  143. package/dist/converge-config.d.ts +1 -1
  144. package/dist/convergence-refresh.d.ts +2 -2
  145. package/dist/convergence-refresh.js +5 -5
  146. package/dist/conversation-index/backend.d.ts +1 -1
  147. package/dist/conversation-index/chunker.d.ts +1 -1
  148. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  149. package/dist/conversation-index/indexer.d.ts +1 -1
  150. package/dist/conversation-index/search.d.ts +1 -1
  151. package/dist/corpus-watermark.d.ts +1 -1
  152. package/dist/corpus-watermark.js +5 -5
  153. package/dist/day-summary.d.ts +1 -1
  154. package/dist/delinearize.d.ts +1 -1
  155. package/dist/dependency-propagation-config.d.ts +1 -1
  156. package/dist/{dependency-propagation-delivery-Cph4PnBe.d.ts → dependency-propagation-delivery-Cr8u-XwC.d.ts} +1 -1
  157. package/dist/direct-answer-wiring.d.ts +1 -1
  158. package/dist/direct-answer.d.ts +1 -1
  159. package/dist/embedding-fallback.d.ts +1 -1
  160. package/dist/enrichment/index.d.ts +1 -1
  161. package/dist/entity-origin-fields.d.ts +1 -1
  162. package/dist/entity-retrieval.d.ts +2 -2
  163. package/dist/entity-retrieval.js +3 -3
  164. package/dist/entity-schema.d.ts +1 -1
  165. package/dist/episodic-context.d.ts +1 -1
  166. package/dist/explicit-capture.d.ts +9 -9
  167. package/dist/external-wiki-access.d.ts +11 -11
  168. package/dist/external-wiki-access.js +14 -14
  169. package/dist/external-wiki-collection-registration.d.ts +1 -1
  170. package/dist/external-wiki-collection.d.ts +1 -1
  171. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  172. package/dist/extraction-error-classification.d.ts +1 -1
  173. package/dist/extraction-faithfulness.d.ts +1 -1
  174. package/dist/extraction-judge-telemetry.d.ts +1 -1
  175. package/dist/extraction-judge-training.d.ts +1 -1
  176. package/dist/extraction-judge.d.ts +1 -1
  177. package/dist/extraction-liveness.d.ts +1 -1
  178. package/dist/extraction-normalization.d.ts +1 -1
  179. package/dist/extraction-prompt.d.ts +1 -1
  180. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  181. package/dist/extraction-source-grounding.d.ts +1 -1
  182. package/dist/extraction.d.ts +1 -1
  183. package/dist/extraction.js +6 -6
  184. package/dist/fallback-llm.d.ts +1 -1
  185. package/dist/graph-dashboard-diff.d.ts +1 -1
  186. package/dist/graph-dashboard-key.d.ts +1 -1
  187. package/dist/graph-dashboard-parser.d.ts +1 -1
  188. package/dist/graph-edge-reinforcement.d.ts +1 -1
  189. package/dist/graph-path-reconstruction.d.ts +1 -1
  190. package/dist/graph-path-scoring.d.ts +1 -1
  191. package/dist/graph-snapshot.d.ts +1 -1
  192. package/dist/graph.d.ts +1 -1
  193. package/dist/harmonic-retrieval.js +1 -1
  194. package/dist/importance.d.ts +1 -1
  195. package/dist/importers/index.d.ts +1 -1
  196. package/dist/in-flight-reads.d.ts +1 -1
  197. package/dist/index.d.ts +697 -697
  198. package/dist/index.js +47 -47
  199. package/dist/intent.d.ts +1 -1
  200. package/dist/lcm/engine.d.ts +1 -1
  201. package/dist/lcm/index.d.ts +1 -1
  202. package/dist/lcm/tools.d.ts +1 -1
  203. package/dist/lifecycle.d.ts +1 -1
  204. package/dist/live-connectors-runner.d.ts +1 -1
  205. package/dist/local-llm.d.ts +1 -1
  206. package/dist/local-model-endpoint.d.ts +1 -1
  207. package/dist/maintenance/memory-governance.d.ts +1 -1
  208. package/dist/maintenance/memory-governance.js +3 -3
  209. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  210. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  211. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  212. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  213. package/dist/{maintenance-BNq31xpo.d.ts → maintenance-BpNjuZk4.d.ts} +3 -3
  214. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  215. package/dist/memory-action-policy.d.ts +1 -1
  216. package/dist/memory-cache.d.ts +1 -1
  217. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  218. package/dist/memory-projection-store.d.ts +1 -1
  219. package/dist/memory-provenance.d.ts +1 -1
  220. package/dist/memory-snapshot.d.ts +1 -1
  221. package/dist/memory-worth-outcomes.d.ts +2 -2
  222. package/dist/models-json.d.ts +1 -1
  223. package/dist/namespaces/migrate.d.ts +3 -3
  224. package/dist/namespaces/migrate.js +4 -4
  225. package/dist/namespaces/principal.d.ts +1 -1
  226. package/dist/namespaces/search.d.ts +1 -1
  227. package/dist/namespaces/storage.d.ts +3 -3
  228. package/dist/namespaces/storage.js +3 -3
  229. package/dist/native-knowledge.d.ts +1 -1
  230. package/dist/offline-sync-impression-drain.d.ts +1 -1
  231. package/dist/operator-doctor-corpus.d.ts +1 -1
  232. package/dist/operator-doctor-corpus.js +6 -6
  233. package/dist/operator-doctor-replica.d.ts +1 -1
  234. package/dist/operator-toolkit.d.ts +3 -3
  235. package/dist/operator-toolkit.js +12 -12
  236. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  237. package/dist/orchestration/maintenance.d.ts +4 -4
  238. package/dist/orchestration/maintenance.js +9 -9
  239. package/dist/{orchestrator-CBuUOP2J.d.ts → orchestrator-B3ZQ8CTJ.d.ts} +8 -8
  240. package/dist/orchestrator.d.ts +9 -9
  241. package/dist/orchestrator.js +47 -47
  242. package/dist/patterns-cli.d.ts +1 -1
  243. package/dist/{pipeline-CW_Q4WvX.d.ts → pipeline-q5emoXD6.d.ts} +1 -1
  244. package/dist/policy-runtime.d.ts +1 -1
  245. package/dist/proactive-contention.d.ts +1 -1
  246. package/dist/provenance.d.ts +1 -1
  247. package/dist/{public-http-BeJ1_JI1.d.ts → public-http-CQN-i7Ai.d.ts} +1 -1
  248. package/dist/{qmd-q-cAzYuC.d.ts → qmd-YC6YGphs.d.ts} +1 -1
  249. package/dist/qmd-preflight.d.ts +2 -2
  250. package/dist/qmd-recall-cache.d.ts +1 -1
  251. package/dist/qmd.d.ts +2 -2
  252. package/dist/recall-concurrency-config.d.ts +1 -1
  253. package/dist/recall-disclosure-escalation.d.ts +1 -1
  254. package/dist/recall-explain-renderer.d.ts +1 -1
  255. package/dist/recall-explain-renderer.js +3 -3
  256. package/dist/recall-memory-map.d.ts +1 -1
  257. package/dist/recall-planner-llm.d.ts +1 -1
  258. package/dist/recall-state.d.ts +1 -1
  259. package/dist/recall-tag-filter.d.ts +1 -1
  260. package/dist/recall-timings.d.ts +1 -1
  261. package/dist/recall-xray-cli.d.ts +1 -1
  262. package/dist/recall-xray-cli.js +4 -4
  263. package/dist/recall-xray-renderer.d.ts +1 -1
  264. package/dist/recall-xray-renderer.js +3 -3
  265. package/dist/recall-xray.d.ts +1 -1
  266. package/dist/recall-xray.js +2 -2
  267. package/dist/reconcile/cursor.d.ts +1 -1
  268. package/dist/reconcile/manifest.d.ts +1 -1
  269. package/dist/reconcile/plan.d.ts +1 -1
  270. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  271. package/dist/replay/normalizers/claude.d.ts +1 -1
  272. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  273. package/dist/replay/normalizers/shared.d.ts +1 -1
  274. package/dist/replay/runner.d.ts +1 -1
  275. package/dist/replay/types.d.ts +1 -1
  276. package/dist/replica-divergence.d.ts +1 -1
  277. package/dist/replica-peers-config.d.ts +1 -1
  278. package/dist/resolve-auth-token.d.ts +1 -1
  279. package/dist/resume-bundles.js +2 -2
  280. package/dist/retrieval-agents.d.ts +2 -2
  281. package/dist/retrieval-tiers.d.ts +1 -1
  282. package/dist/routing/engine.d.ts +1 -1
  283. package/dist/routing/store.d.ts +1 -1
  284. package/dist/salvage-envelope.d.ts +1 -1
  285. package/dist/schemas.d.ts +108 -108
  286. package/dist/{scope-profiles-CIr4mXgp.d.ts → scope-profiles-De8F7YQ6.d.ts} +1 -1
  287. package/dist/search/embed-helper.d.ts +1 -1
  288. package/dist/search/factory.d.ts +1 -1
  289. package/dist/search/index.d.ts +1 -1
  290. package/dist/search/lancedb-backend.d.ts +1 -1
  291. package/dist/search/meilisearch-backend.d.ts +1 -1
  292. package/dist/search/noop-backend.d.ts +1 -1
  293. package/dist/search/orama-backend.d.ts +1 -1
  294. package/dist/search/port.d.ts +1 -1
  295. package/dist/search/remote-backend.d.ts +1 -1
  296. package/dist/{semantic-consolidation-BZ6J46j-.d.ts → semantic-consolidation-Bf-hm2LQ.d.ts} +1 -1
  297. package/dist/semantic-consolidation.d.ts +2 -2
  298. package/dist/semantic-consolidation.js +4 -4
  299. package/dist/semantic-rule-promotion.js +3 -3
  300. package/dist/semantic-rule-verifier.d.ts +1 -1
  301. package/dist/semantic-rule-verifier.js +3 -3
  302. package/dist/{service-CCl4iyyY.d.ts → service-D83osSRn.d.ts} +2 -2
  303. package/dist/session-observer-bands.d.ts +1 -1
  304. package/dist/session-observer-state.d.ts +1 -1
  305. package/dist/shared-context/manager.d.ts +9 -9
  306. package/dist/signal.d.ts +1 -1
  307. package/dist/source-agent-qualifier.d.ts +1 -1
  308. package/dist/source-agent-qualifier.js +5 -5
  309. package/dist/{storage-BPXmJKIt.d.ts → storage-BHWbpbhj.d.ts} +1 -1
  310. package/dist/storage.d.ts +2 -2
  311. package/dist/storage.js +2 -2
  312. package/dist/summarizer.d.ts +1 -1
  313. package/dist/summary-snapshot.d.ts +1 -1
  314. package/dist/support-passport/index.d.ts +14 -14
  315. package/dist/support-passport/index.js +16 -16
  316. package/dist/temporal-supersession.d.ts +2 -2
  317. package/dist/temporal-timeline-recall.d.ts +1 -1
  318. package/dist/temporal-validity.d.ts +1 -1
  319. package/dist/threading.d.ts +1 -1
  320. package/dist/tier-migration.d.ts +2 -2
  321. package/dist/tier-routing.d.ts +1 -1
  322. package/dist/topics.d.ts +1 -1
  323. package/dist/transcript.d.ts +1 -1
  324. package/dist/transfer/types.d.ts +66 -66
  325. package/dist/trust-score-stage.d.ts +1 -1
  326. package/dist/trust-score.d.ts +1 -1
  327. package/dist/{types-CltSWcND.d.ts → types-CpgsN-Gb.d.ts} +24 -3
  328. package/dist/types.d.ts +1 -1
  329. package/dist/types.js +1 -1
  330. package/dist/utility-runtime.d.ts +1 -1
  331. package/dist/verified-recall.js +3 -3
  332. package/dist/write-envelope.d.ts +1 -1
  333. package/package.json +2 -2
  334. package/src/config.ts +5 -6
  335. package/src/location/cli.ts +163 -0
  336. package/src/location/config.test.ts +75 -0
  337. package/src/location/config.ts +105 -0
  338. package/src/location/intervals.test.ts +202 -0
  339. package/src/location/intervals.ts +221 -0
  340. package/src/location/parse-companions.ts +12 -0
  341. package/src/location/pipeline.test.ts +324 -0
  342. package/src/location/pipeline.ts +245 -0
  343. package/src/location/recall-exclusion.test.ts +36 -0
  344. package/src/location/registry.test.ts +74 -0
  345. package/src/location/registry.ts +54 -0
  346. package/src/location/store.test.ts +170 -0
  347. package/src/location/store.ts +478 -0
  348. package/src/location/types.ts +95 -0
  349. package/src/orchestration/generic-recall-paths.ts +17 -0
  350. package/src/orchestration/orchestrator-helpers.ts +22 -0
  351. package/src/orchestration/qmd-result-resolver.ts +1 -0
  352. package/src/types.ts +2 -3
  353. package/dist/chunk-AR253JBM.js.map +0 -1
  354. package/dist/chunk-BB2LH5JM.js.map +0 -1
  355. package/dist/chunk-GIMQJ5RX.js.map +0 -1
  356. package/dist/chunk-ZWYXSF2T.js.map +0 -1
  357. /package/dist/{chunk-7DRSWPUV.js.map → chunk-3CYVTO6A.js.map} +0 -0
  358. /package/dist/{chunk-LJQGQUNS.js.map → chunk-4FSCGYTQ.js.map} +0 -0
  359. /package/dist/{chunk-ML4MI2RR.js.map → chunk-4KKVDOCD.js.map} +0 -0
  360. /package/dist/{chunk-Z3Q5I5F6.js.map → chunk-5Q5BEWJ3.js.map} +0 -0
  361. /package/dist/{chunk-6BV6ZZZA.js.map → chunk-6I6IOMKS.js.map} +0 -0
  362. /package/dist/{chunk-4IIDTPSK.js.map → chunk-7X35RPN3.js.map} +0 -0
  363. /package/dist/{chunk-FEP45MYY.js.map → chunk-AGC3Q53R.js.map} +0 -0
  364. /package/dist/{chunk-WCWIV2JH.js.map → chunk-B2GICNOJ.js.map} +0 -0
  365. /package/dist/{chunk-BTBAHTQ5.js.map → chunk-CI6YIT5S.js.map} +0 -0
  366. /package/dist/{chunk-CUDSA46O.js.map → chunk-CUIAHZPZ.js.map} +0 -0
  367. /package/dist/{chunk-BTUIHRRR.js.map → chunk-D267K4V5.js.map} +0 -0
  368. /package/dist/{chunk-QICOU4RY.js.map → chunk-DLZBOEJY.js.map} +0 -0
  369. /package/dist/{chunk-BPLPGOUJ.js.map → chunk-DRS3OX77.js.map} +0 -0
  370. /package/dist/{chunk-3F3CVVDS.js.map → chunk-DTQ3OB7O.js.map} +0 -0
  371. /package/dist/{chunk-Z4UPRZR6.js.map → chunk-DZ2AVS2H.js.map} +0 -0
  372. /package/dist/{chunk-YINSSARY.js.map → chunk-EAWJSY2Z.js.map} +0 -0
  373. /package/dist/{chunk-QFPX7QZG.js.map → chunk-FMGTFMT3.js.map} +0 -0
  374. /package/dist/{chunk-CTD5CPD2.js.map → chunk-GSQDPL45.js.map} +0 -0
  375. /package/dist/{chunk-UUYQWKHO.js.map → chunk-IHPYTG6F.js.map} +0 -0
  376. /package/dist/{chunk-QATILW4B.js.map → chunk-IPUUMLSM.js.map} +0 -0
  377. /package/dist/{chunk-2BRCV2IV.js.map → chunk-JE3AKPOO.js.map} +0 -0
  378. /package/dist/{chunk-DZFPHARJ.js.map → chunk-KOE3RLNN.js.map} +0 -0
  379. /package/dist/{chunk-WQ6TRWAE.js.map → chunk-NMQXUGTU.js.map} +0 -0
  380. /package/dist/{chunk-65NQFK7D.js.map → chunk-OTVHRZZD.js.map} +0 -0
  381. /package/dist/{chunk-77E7LLMV.js.map → chunk-OWAYQK5V.js.map} +0 -0
  382. /package/dist/{chunk-YGSDWO3U.js.map → chunk-P4SD7BSA.js.map} +0 -0
  383. /package/dist/{chunk-G5F4MXXC.js.map → chunk-Q3IRE6E4.js.map} +0 -0
  384. /package/dist/{chunk-HZXO2CJ6.js.map → chunk-QHNO6AND.js.map} +0 -0
  385. /package/dist/{chunk-5OOTHWGJ.js.map → chunk-RMB22Q3R.js.map} +0 -0
  386. /package/dist/{chunk-R7XE5ZUR.js.map → chunk-UIYYS2YI.js.map} +0 -0
  387. /package/dist/{chunk-SYU5JOBQ.js.map → chunk-UWSETW6O.js.map} +0 -0
  388. /package/dist/{chunk-CK3DZ6OE.js.map → chunk-VCNGVU62.js.map} +0 -0
  389. /package/dist/{chunk-KG2CT762.js.map → chunk-VSKA2ATE.js.map} +0 -0
  390. /package/dist/{chunk-PQODT53H.js.map → chunk-WDFXRHOZ.js.map} +0 -0
  391. /package/dist/{chunk-6NMZXEJ7.js.map → chunk-WNO5W2TP.js.map} +0 -0
  392. /package/dist/{chunk-63JMI45N.js.map → chunk-XBRGJJK7.js.map} +0 -0
  393. /package/dist/{chunk-S6KMN6MJ.js.map → chunk-XFAWGUNX.js.map} +0 -0
  394. /package/dist/{chunk-SRLQROQJ.js.map → chunk-XSPRZZEX.js.map} +0 -0
  395. /package/dist/{chunk-EHC3FIJ4.js.map → chunk-Y5ZIQFWH.js.map} +0 -0
  396. /package/dist/{chunk-6IKXAAJI.js.map → chunk-YY63AMGP.js.map} +0 -0
  397. /package/dist/{chunk-7ML7STOD.js.map → chunk-ZGGTIBWN.js.map} +0 -0
  398. /package/dist/{chunk-X6HNXVXS.js.map → chunk-ZUIVJ3BB.js.map} +0 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Location day-sync pipeline (issue #2044).
3
+ *
4
+ * For one local day, each configured (enabled, registered) provider's
5
+ * observations are fetched over the day's half-open UTC window, folded into
6
+ * place-visit segments, merged with the other sources' last-known day
7
+ * payloads from sync state, and rendered to the day document. Sync state
8
+ * advances only AFTER the day document (when any) is durable, so a failed
9
+ * write never moves the watermark past data that did not land. An empty
10
+ * fetch is a successful sync — never conflated with a provider failure
11
+ * (AGENTS.md rule 22).
12
+ */
13
+
14
+ import { getLocationProvider } from "./registry.js";
15
+ import { locationDayWindow, observationSegments } from "./intervals.js";
16
+ import {
17
+ composeLocationDayBody,
18
+ composeLocationDayMeta,
19
+ loadLocationSyncState,
20
+ serializeLocationDay,
21
+ updateLocationSourceDay,
22
+ writeLocationDay,
23
+ type LocationDaySourceEntry,
24
+ } from "./store.js";
25
+ import type { LocationConfig, LocationObservation, LocationProvider, LocationSegment } from "./types.js";
26
+
27
+ const MAX_SYNC_PAGES = 10_000;
28
+
29
+ export interface LocationDaySyncOptions {
30
+ config: LocationConfig;
31
+ memoryDir: string;
32
+ date: string;
33
+ signal?: AbortSignal;
34
+ /** Runaway-pagination guard; defaults to MAX_SYNC_PAGES. */
35
+ maxPages?: number;
36
+ now?: () => Date;
37
+ /** Provider lookup seam (defaults to the registry); tests inject doubles. */
38
+ getProvider?: (id: string) => LocationProvider | undefined;
39
+ }
40
+
41
+ export interface LocationSourceSyncResult {
42
+ sourceId: string;
43
+ status: "synced" | "skipped" | "failed";
44
+ skipReason?: "source-disabled" | "provider-not-registered";
45
+ fetched: number;
46
+ dayWritten: boolean;
47
+ stateSaved: boolean;
48
+ /** Sanitized provider error (no tokens, URLs, or coordinates). */
49
+ error?: string;
50
+ }
51
+
52
+ /** Redact credential, URL query, and coordinate shapes from a provider error. */
53
+ export function sanitizeProviderError(error: unknown): string {
54
+ const raw = error instanceof Error ? error.message : String(error);
55
+ return raw
56
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
57
+ .replace(/[?#][^ \t"']*/g, "[redacted]")
58
+ .replace(/[+-]?\d+\.\d{3,},\s?[+-]?\d+\.\d{3,}/g, "[coordinates]")
59
+ .slice(0, 300);
60
+ }
61
+
62
+ function assertValidObservation(observation: LocationObservation): void {
63
+ if (typeof observation !== "object" || observation === null) {
64
+ throw new TypeError("location provider returned a non-object observation");
65
+ }
66
+ if (typeof observation.observedAtUtc !== "string" || !Number.isFinite(Date.parse(observation.observedAtUtc))) {
67
+ throw new TypeError("location provider returned an observation with an invalid observedAtUtc");
68
+ }
69
+ const place = observation.place;
70
+ if (typeof place !== "object" || place === null || typeof place.id !== "string" || place.id.length === 0) {
71
+ throw new TypeError("location provider returned an observation without a valid place");
72
+ }
73
+ if (typeof place.label !== "string" || place.label.trim().length === 0) {
74
+ throw new TypeError(`location provider returned place '${place.id}' without a label`);
75
+ }
76
+ }
77
+
78
+ /** Fetch every observation one provider holds for the window, all pages. */
79
+ async function fetchWindow(
80
+ provider: LocationProvider,
81
+ window: { startUtc: string; endUtc: string },
82
+ options: LocationDaySyncOptions,
83
+ ): Promise<LocationObservation[]> {
84
+ const maxPages = options.maxPages ?? MAX_SYNC_PAGES;
85
+ if (!Number.isInteger(maxPages) || maxPages < 1) {
86
+ throw new RangeError("location sync maxPages must be a positive integer");
87
+ }
88
+ const observations: LocationObservation[] = [];
89
+ const seenCursors = new Set<string>();
90
+ let cursor: string | null = null;
91
+ let pages = 0;
92
+ let completed = false;
93
+ while (pages < maxPages) {
94
+ options.signal?.throwIfAborted();
95
+ const page = await provider.fetchObservations({
96
+ startUtc: window.startUtc,
97
+ endUtc: window.endUtc,
98
+ cursor,
99
+ signal: options.signal,
100
+ });
101
+ pages += 1;
102
+ for (const observation of page.observations) assertValidObservation(observation);
103
+ observations.push(...page.observations);
104
+ if (page.nextCursor === null) {
105
+ completed = true;
106
+ break;
107
+ }
108
+ if (typeof page.nextCursor !== "string" || page.nextCursor.length === 0) {
109
+ throw new TypeError("location provider returned an invalid cursor");
110
+ }
111
+ if (seenCursors.has(page.nextCursor)) {
112
+ throw new Error("location provider returned a repeated cursor");
113
+ }
114
+ seenCursors.add(page.nextCursor);
115
+ cursor = page.nextCursor;
116
+ }
117
+ // Only a loop that ran out of its page budget without a terminal null
118
+ // cursor is a runaway; completing on the final allowed page is normal.
119
+ if (!completed) {
120
+ throw new Error(`location provider exceeded ${maxPages} pages`);
121
+ }
122
+ return observations;
123
+ }
124
+
125
+ /**
126
+ * Sync every configured source for one local day. The master gate
127
+ * (`config.enabled === false`) short-circuits every path: no providers are
128
+ * contacted and nothing touches the filesystem. A provider failure fails
129
+ * that source only (state not advanced) and never blocks the others.
130
+ */
131
+ export async function syncLocationDay(options: LocationDaySyncOptions): Promise<LocationSourceSyncResult[]> {
132
+ if (!options.config.enabled) return [];
133
+ const window = locationDayWindow(options.date, options.config.timezone);
134
+ const now = options.now ?? (() => new Date());
135
+ const resolveProvider = options.getProvider ?? getLocationProvider;
136
+
137
+ const results: LocationSourceSyncResult[] = [];
138
+ for (const source of options.config.sources) {
139
+ if (!source.enabled) {
140
+ results.push({
141
+ sourceId: source.id,
142
+ status: "skipped",
143
+ skipReason: "source-disabled",
144
+ fetched: 0,
145
+ dayWritten: false,
146
+ stateSaved: false,
147
+ });
148
+ continue;
149
+ }
150
+ const provider = resolveProvider(source.id);
151
+ if (provider === undefined) {
152
+ results.push({
153
+ sourceId: source.id,
154
+ status: "skipped",
155
+ skipReason: "provider-not-registered",
156
+ fetched: 0,
157
+ dayWritten: false,
158
+ stateSaved: false,
159
+ });
160
+ continue;
161
+ }
162
+
163
+ let observations: LocationObservation[];
164
+ try {
165
+ observations = await fetchWindow(provider, window, options);
166
+ } catch (error) {
167
+ results.push({
168
+ sourceId: source.id,
169
+ status: "failed",
170
+ fetched: 0,
171
+ dayWritten: false,
172
+ stateSaved: false,
173
+ ...(error instanceof Error && error.name === "AbortError"
174
+ ? { error: "aborted" }
175
+ : { error: sanitizeProviderError(error) }),
176
+ });
177
+ continue;
178
+ }
179
+
180
+ try {
181
+ options.signal?.throwIfAborted();
182
+ const segments = observationSegments(observations, window, {
183
+ retainCoordinates: options.config.retainCoordinates,
184
+ });
185
+ const payload = { observationCount: observations.length, segments, providerDisplayName: provider.displayName };
186
+ const merged = await mergeDaySources(options.memoryDir, source.id, options.date, payload);
187
+ let dayWritten = false;
188
+ if (Object.values(merged).some((entry) => entry.observationCount > 0)) {
189
+ const body = composeLocationDayBody(options.date, options.config.timezone, merged);
190
+ const meta = composeLocationDayMeta(options.date, options.config.timezone, merged, body);
191
+ dayWritten = await writeLocationDay(options.memoryDir, options.date, serializeLocationDay(meta, body));
192
+ }
193
+ // State advances only after the durable day write above succeeded.
194
+ await updateLocationSourceDay(
195
+ options.memoryDir,
196
+ source.id,
197
+ options.date,
198
+ payload,
199
+ now().toISOString(),
200
+ );
201
+ results.push({
202
+ sourceId: source.id,
203
+ status: "synced",
204
+ fetched: observations.length,
205
+ dayWritten,
206
+ stateSaved: true,
207
+ });
208
+ } catch (error) {
209
+ results.push({
210
+ sourceId: source.id,
211
+ status: "failed",
212
+ fetched: observations.length,
213
+ dayWritten: false,
214
+ stateSaved: false,
215
+ ...(error instanceof Error && error.name === "AbortError"
216
+ ? { error: "aborted" }
217
+ : { error: sanitizeProviderError(error) }),
218
+ });
219
+ }
220
+ }
221
+ return results;
222
+ }
223
+
224
+ /**
225
+ * The day-document source map: this source's fresh payload plus every OTHER
226
+ * source's last-known payload from sync state. Rendered documents therefore
227
+ * survive a re-sync of one source without re-fetching the others.
228
+ */
229
+ async function mergeDaySources(
230
+ memoryDir: string,
231
+ sourceId: string,
232
+ date: string,
233
+ payload: { observationCount: number; segments: LocationSegment[] },
234
+ ): Promise<Record<string, LocationDaySourceEntry>> {
235
+ const state = await loadLocationSyncState(memoryDir);
236
+ const merged: Record<string, LocationDaySourceEntry> = {};
237
+ for (const [otherId, otherState] of Object.entries(state.sources)) {
238
+ if (otherId === sourceId) continue;
239
+ const day = otherState.days?.[date];
240
+ if (day === undefined) continue;
241
+ merged[otherId] = { ...day, segments: [...day.segments] };
242
+ }
243
+ merged[sourceId] = payload;
244
+ return merged;
245
+ }
@@ -0,0 +1,36 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { isSearchExcludedPath } from "../orchestration/generic-recall-paths.js";
5
+ import { isLocationDayPath } from "../orchestration/orchestrator-helpers.js";
6
+
7
+ const MEMORY_DIR = "/tmp/remnic-fixture-memory";
8
+
9
+ test("isLocationDayPath matches only the top-level day-file shape", () => {
10
+ assert.equal(isLocationDayPath("locations/2026-08-17.md", MEMORY_DIR), true);
11
+ assert.equal(isLocationDayPath(`${MEMORY_DIR}/locations/2026-08-17.md`, MEMORY_DIR), true);
12
+ assert.equal(isLocationDayPath("facts/notes/locations.md", MEMORY_DIR), false);
13
+ assert.equal(isLocationDayPath("facts/locations/2026-08-17.md", MEMORY_DIR), false, "nested stays recallable");
14
+ assert.equal(isLocationDayPath("locations/2026-08-17.md"), true, "root-unaware best-effort fallback");
15
+ assert.equal(isLocationDayPath("locations/not-a-date.md"), false);
16
+ });
17
+
18
+ test("isSearchExcludedPath keeps location day documents out of generic search and recall", () => {
19
+ assert.equal(isSearchExcludedPath("locations/2026-08-17.md"), true);
20
+ assert.equal(
21
+ isSearchExcludedPath("qmd://collection/locations/2026-08-17.md", {}, "qmd"),
22
+ true,
23
+ "collection-qualified QMD paths are stripped and re-tested",
24
+ );
25
+ assert.equal(
26
+ isSearchExcludedPath(`${MEMORY_DIR}/namespaces/team/locations/2026-08-17.md`, { memoryDir: MEMORY_DIR }),
27
+ true,
28
+ "a namespaced day document is excluded via the namespace root",
29
+ );
30
+ assert.equal(isSearchExcludedPath("facts/locations-tips.md"), false);
31
+ assert.equal(
32
+ isSearchExcludedPath("namespaces/locations/facts/a.md", { memoryDir: MEMORY_DIR }),
33
+ false,
34
+ "a namespace literally named locations keeps its memories recallable",
35
+ );
36
+ });
@@ -0,0 +1,74 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ clearLocationProviders,
6
+ getLocationProvider,
7
+ listLocationProviders,
8
+ registerLocationProvider,
9
+ } from "./registry.js";
10
+ import type { LocationProvider } from "./types.js";
11
+
12
+ function provider(overrides: Partial<LocationProvider> = {}): LocationProvider {
13
+ return {
14
+ id: "fixture-provider",
15
+ displayName: "Fixture Provider",
16
+ async verify() {
17
+ return { ok: true };
18
+ },
19
+ async fetchObservations() {
20
+ return { observations: [], nextCursor: null };
21
+ },
22
+ ...overrides,
23
+ };
24
+ }
25
+
26
+ test("registerLocationProvider registers, looks up, and lists sorted", () => {
27
+ clearLocationProviders();
28
+ registerLocationProvider(provider({ id: "zeta", displayName: "Z" }));
29
+ registerLocationProvider(provider({ id: "alpha", displayName: "A" }));
30
+ assert.deepEqual(listLocationProviders(), ["alpha", "zeta"]);
31
+ assert.equal(getLocationProvider("alpha")?.displayName, "A");
32
+ assert.equal(getLocationProvider("missing"), undefined);
33
+ });
34
+
35
+ test("registerLocationProvider rejects duplicate ids", () => {
36
+ clearLocationProviders();
37
+ registerLocationProvider(provider());
38
+ assert.throws(() => registerLocationProvider(provider()), /already registered/);
39
+ });
40
+
41
+ test("registerLocationProvider rejects invalid ids", () => {
42
+ clearLocationProviders();
43
+ assert.throws(() => registerLocationProvider(provider({ id: "" })), RangeError);
44
+ assert.throws(() => registerLocationProvider(provider({ id: "Upper" })), RangeError);
45
+ assert.throws(() => registerLocationProvider(provider({ id: "has space" })), RangeError);
46
+ assert.throws(() => registerLocationProvider(provider({ id: "../traversal" })), RangeError);
47
+ assert.throws(
48
+ () => registerLocationProvider(provider({ id: "x", displayName: "" as unknown as string })),
49
+ TypeError,
50
+ );
51
+ assert.throws(
52
+ () =>
53
+ registerLocationProvider(
54
+ provider({ id: "x", verify: undefined as unknown as LocationProvider["verify"] }),
55
+ ),
56
+ TypeError,
57
+ );
58
+ assert.throws(
59
+ () =>
60
+ registerLocationProvider(
61
+ provider({ id: "x", fetchObservations: undefined as unknown as LocationProvider["fetchObservations"] }),
62
+ ),
63
+ TypeError,
64
+ );
65
+ assert.deepEqual(listLocationProviders(), []);
66
+ });
67
+
68
+ test("clearLocationProviders resets the registry", () => {
69
+ clearLocationProviders();
70
+ registerLocationProvider(provider());
71
+ clearLocationProviders();
72
+ assert.deepEqual(listLocationProviders(), []);
73
+ assert.equal(getLocationProvider("fixture-provider"), undefined);
74
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Location provider registry (issue #2044).
3
+ *
4
+ * In-memory only: host adapters register provider implementations at startup.
5
+ * Core never statically imports an optional provider package — the registry
6
+ * holds whatever the host wired in, and a configured source whose provider is
7
+ * not registered is skipped (not an error), so partial installs keep working.
8
+ */
9
+
10
+ import type { LocationProvider } from "./types.js";
11
+
12
+ const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
13
+
14
+ const providers = new Map<string, LocationProvider>();
15
+
16
+ function assertValidProvider(provider: LocationProvider): void {
17
+ if (typeof provider !== "object" || provider === null) {
18
+ throw new TypeError("location provider must be an object");
19
+ }
20
+ if (typeof provider.id !== "string" || !PROVIDER_ID_PATTERN.test(provider.id)) {
21
+ throw new RangeError(
22
+ "location provider id must be a non-empty lowercase kebab string (a-z, 0-9, hyphens)",
23
+ );
24
+ }
25
+ if (typeof provider.displayName !== "string" || provider.displayName.trim().length === 0) {
26
+ throw new TypeError(`location provider '${provider.id}' must declare a non-empty displayName`);
27
+ }
28
+ if (typeof provider.verify !== "function" || typeof provider.fetchObservations !== "function") {
29
+ throw new TypeError(
30
+ `location provider '${provider.id}' must implement verify() and fetchObservations()`,
31
+ );
32
+ }
33
+ }
34
+
35
+ export function registerLocationProvider(provider: LocationProvider): void {
36
+ assertValidProvider(provider);
37
+ if (providers.has(provider.id)) {
38
+ throw new Error(`location provider '${provider.id}' is already registered`);
39
+ }
40
+ providers.set(provider.id, provider);
41
+ }
42
+
43
+ export function getLocationProvider(id: string): LocationProvider | undefined {
44
+ return typeof id === "string" ? providers.get(id) : undefined;
45
+ }
46
+
47
+ export function listLocationProviders(): string[] {
48
+ return [...providers.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
49
+ }
50
+
51
+ /** Test-only: reset the registry between cases. */
52
+ export function clearLocationProviders(): void {
53
+ providers.clear();
54
+ }
@@ -0,0 +1,170 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, rm, symlink, writeFile, readFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import {
8
+ composeLocationDayBody,
9
+ composeLocationDayMeta,
10
+ emptyLocationSyncState,
11
+ listLocationDayDates,
12
+ loadLocationSyncState,
13
+ locationDayFilePath,
14
+ locationSyncStateFilePath,
15
+ parseLocationDaySummary,
16
+ serializeLocationDay,
17
+ updateLocationSourceDay,
18
+ writeLocationDay,
19
+ } from "./store.js";
20
+ import type { LocationDaySourceEntry } from "./store.js";
21
+
22
+ function entry(overrides: Partial<LocationDaySourceEntry> = {}): LocationDaySourceEntry {
23
+ return {
24
+ observationCount: 1,
25
+ segments: [
26
+ {
27
+ startUtc: "2026-08-17T09:00:00.000Z",
28
+ endUtc: "2026-08-17T09:30:00.000Z",
29
+ place: { id: "home", label: "Home" },
30
+ },
31
+ ],
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ test("day rendering is deterministic across source-map insertion order", () => {
37
+ const a: Record<string, LocationDaySourceEntry> = {
38
+ reitti: entry({ providerDisplayName: "Reitti" }),
39
+ other: entry({
40
+ providerDisplayName: "Other",
41
+ observationCount: 0,
42
+ segments: [],
43
+ }),
44
+ };
45
+ const b: Record<string, LocationDaySourceEntry> = {
46
+ other: entry({ providerDisplayName: "Other", observationCount: 0, segments: [] }),
47
+ reitti: entry({ providerDisplayName: "Reitti" }),
48
+ };
49
+ const render = (sources: Record<string, LocationDaySourceEntry>) => {
50
+ const body = composeLocationDayBody("2026-08-17", "UTC", sources);
51
+ return serializeLocationDay(composeLocationDayMeta("2026-08-17", "UTC", sources, body), body);
52
+ };
53
+ const first = render(a);
54
+ const second = render(b);
55
+ assert.equal(first, second, "identical content renders byte-identical regardless of key order");
56
+ const reparsed = parseLocationDaySummary(first);
57
+ assert.deepEqual(reparsed, {
58
+ date: "2026-08-17",
59
+ timezone: "UTC",
60
+ sources: ["other", "reitti"],
61
+ observationCount: 1,
62
+ contentHash: parseLocationDaySummary(second)!.contentHash,
63
+ });
64
+ assert.equal(parseLocationDaySummary("# not a location day\n"), null);
65
+ });
66
+
67
+ test("writeLocationDay is idempotent and read-back verifies content", async () => {
68
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-location-store-"));
69
+ try {
70
+ const body = composeLocationDayBody("2026-08-17", "UTC", { reitti: entry() });
71
+ const serialized = serializeLocationDay(
72
+ composeLocationDayMeta("2026-08-17", "UTC", { reitti: entry() }, body),
73
+ body,
74
+ );
75
+ assert.equal(await writeLocationDay(memoryDir, "2026-08-17", serialized), true);
76
+ assert.equal(await writeLocationDay(memoryDir, "2026-08-17", serialized), false, "unchanged content rewrites nothing");
77
+ assert.equal(await readFile(locationDayFilePath(memoryDir, "2026-08-17"), "utf-8"), serialized);
78
+ assert.deepEqual(await listLocationDayDates(memoryDir), ["2026-08-17"]);
79
+ } finally {
80
+ await rm(memoryDir, { recursive: true, force: true });
81
+ }
82
+ });
83
+
84
+ test("locationDayFilePath rejects traversal-shaped dates", () => {
85
+ assert.throws(() => locationDayFilePath("/tmp/x", "../../etc/passwd"), RangeError);
86
+ assert.throws(() => locationDayFilePath("/tmp/x", "2026-13-01"), RangeError);
87
+ });
88
+
89
+ test("store rejects a symlinked locations directory even when it resolves inside memoryDir", async () => {
90
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-location-symlink-"));
91
+ try {
92
+ const memoryDir = path.join(root, "memory");
93
+ const target = path.join(root, "elsewhere");
94
+ await mkdir(memoryDir, { recursive: true });
95
+ await mkdir(target, { recursive: true });
96
+ await symlink(target, path.join(memoryDir, "locations"));
97
+ await assert.rejects(
98
+ () => writeLocationDay(memoryDir, "2026-08-17", "---\nkind: location-day\n"),
99
+ /symlink/,
100
+ );
101
+ } finally {
102
+ await rm(root, { recursive: true, force: true });
103
+ }
104
+ });
105
+
106
+ test("store rejects a symlinked day file that escapes the memory dir", async () => {
107
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-location-symlinkfile-"));
108
+ try {
109
+ const memoryDir = path.join(root, "memory");
110
+ await mkdir(path.join(memoryDir, "locations"), { recursive: true });
111
+ await writeFile(path.join(root, "secret.md"), "outside", "utf-8");
112
+ await symlink(path.join(root, "secret.md"), path.join(memoryDir, "locations", "2026-08-17.md"));
113
+ await assert.rejects(
114
+ () => writeLocationDay(memoryDir, "2026-08-17", "---\nkind: location-day\n"),
115
+ /outside the memory dir|symbolic link/,
116
+ );
117
+ } finally {
118
+ await rm(root, { recursive: true, force: true });
119
+ }
120
+ });
121
+
122
+ test("sync state cold-starts on absence and corrupt JSON, then persists atomically", async () => {
123
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-location-state-"));
124
+ try {
125
+ assert.deepEqual(await loadLocationSyncState(memoryDir), emptyLocationSyncState());
126
+ await mkdir(path.dirname(locationSyncStateFilePath(memoryDir)), { recursive: true });
127
+ await writeFile(locationSyncStateFilePath(memoryDir), "{not json", "utf-8");
128
+ assert.deepEqual(await loadLocationSyncState(memoryDir), emptyLocationSyncState());
129
+ await updateLocationSourceDay(memoryDir, "reitti", "2026-08-17", entry(), "2026-08-18T00:00:05.000Z");
130
+ const state = await loadLocationSyncState(memoryDir);
131
+ assert.equal(state.sources.reitti?.lastSyncedAtUtc, "2026-08-18T00:00:05.000Z");
132
+ assert.equal(state.sources.reitti?.days?.["2026-08-17"]?.observationCount, 1);
133
+ } finally {
134
+ await rm(memoryDir, { recursive: true, force: true });
135
+ }
136
+ });
137
+
138
+ test("concurrent source state updates do not lose each other", async () => {
139
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-location-concurrent-"));
140
+ try {
141
+ await Promise.all([
142
+ updateLocationSourceDay(memoryDir, "reitti", "2026-08-17", entry(), "2026-08-18T00:00:01.000Z"),
143
+ updateLocationSourceDay(memoryDir, "other", "2026-08-17", entry({ observationCount: 2 }), "2026-08-18T00:00:02.000Z"),
144
+ updateLocationSourceDay(memoryDir, "third", "2026-08-16", entry(), "2026-08-18T00:00:03.000Z"),
145
+ ]);
146
+ const state = await loadLocationSyncState(memoryDir);
147
+ assert.deepEqual(Object.keys(state.sources).sort(), ["other", "reitti", "third"]);
148
+ assert.equal(state.sources.reitti?.days?.["2026-08-17"]?.observationCount, 1);
149
+ assert.equal(state.sources.other?.days?.["2026-08-17"]?.observationCount, 2);
150
+ } finally {
151
+ await rm(memoryDir, { recursive: true, force: true });
152
+ }
153
+ });
154
+
155
+ test("day payloads prune beyond the retention cap, newest kept", async () => {
156
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-location-prune-"));
157
+ try {
158
+ for (let day = 0; day < 95; day += 1) {
159
+ const date = new Date(Date.UTC(2026, 5, 1) + day * 86_400_000).toISOString().slice(0, 10);
160
+ await updateLocationSourceDay(memoryDir, "reitti", date, entry(), "2026-09-01T00:00:00.000Z");
161
+ }
162
+ const state = await loadLocationSyncState(memoryDir);
163
+ const days = Object.keys(state.sources.reitti?.days ?? {});
164
+ assert.equal(days.length, 90);
165
+ assert.equal(days.includes("2026-06-01"), false, "oldest days pruned");
166
+ assert.equal(days.includes("2026-06-06"), true, "newest 90 days kept");
167
+ } finally {
168
+ await rm(memoryDir, { recursive: true, force: true });
169
+ }
170
+ });