@remnic/core 9.54.4 → 9.54.6

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 (648) hide show
  1. package/dist/access-admin-ops-surface.d.ts +24 -23
  2. package/dist/access-admin-ops-surface.js +55 -50
  3. package/dist/access-authorization-probe.d.ts +25 -22
  4. package/dist/access-authorization-probe.js +58 -51
  5. package/dist/access-boundary.d.ts +26 -23
  6. package/dist/access-boundary.js +59 -52
  7. package/dist/access-cli.js +115 -107
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +19 -18
  11. package/dist/access-extraction-force-flush.js +55 -50
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +22 -21
  14. package/dist/access-http-lifecycle-flush.d.ts +22 -21
  15. package/dist/access-http-offline-stream.d.ts +19 -18
  16. package/dist/access-http-query.js +55 -50
  17. package/dist/access-http.d.ts +55 -36
  18. package/dist/access-http.js +76 -65
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +55 -50
  21. package/dist/access-lcm-surface.d.ts +21 -20
  22. package/dist/access-lcm-surface.js +55 -50
  23. package/dist/access-mcp.d.ts +22 -21
  24. package/dist/access-mcp.js +63 -57
  25. package/dist/access-memory-search-fanout.d.ts +22 -2
  26. package/dist/access-memory-search-fanout.js +1 -1
  27. package/dist/{access-namespace-preflight-C7n4tgWC.d.ts → access-namespace-preflight-CCNn_6NE.d.ts} +1 -1
  28. package/dist/access-namespace-preflight.d.ts +3 -3
  29. package/dist/access-namespace-preflight.js +55 -50
  30. package/dist/access-observe-write-surface.d.ts +22 -21
  31. package/dist/access-observe-write-surface.js +55 -50
  32. package/dist/access-offline-manifest.d.ts +10 -9
  33. package/dist/access-offline-manifest.js +12 -10
  34. package/dist/access-operations-batch.js +57 -52
  35. package/dist/access-operations.d.ts +23 -22
  36. package/dist/access-operations.js +61 -55
  37. package/dist/access-recall-concurrency.d.ts +21 -20
  38. package/dist/access-recall-concurrency.js +55 -50
  39. package/dist/access-recall-response.d.ts +19 -18
  40. package/dist/access-recall-response.js +55 -50
  41. package/dist/access-recall-surface.d.ts +19 -18
  42. package/dist/access-recall-surface.js +55 -50
  43. package/dist/access-schema.d.ts +2 -2
  44. package/dist/access-schema.js +5 -4
  45. package/dist/{access-service-CXvbxLK-.d.ts → access-service-DkSdXK4S.d.ts} +1314 -83
  46. package/dist/access-service-helpers.d.ts +22 -21
  47. package/dist/access-service.d.ts +31 -30
  48. package/dist/access-service.js +55 -50
  49. package/dist/access-surface-catalog.d.ts +22 -21
  50. package/dist/access-surface-catalog.js +209 -2
  51. package/dist/access-surface-catalog.js.map +1 -1
  52. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  53. package/dist/action-confidence.d.ts +1 -1
  54. package/dist/active-memory-bridge.d.ts +14 -1
  55. package/dist/active-memory-bridge.js +6 -1
  56. package/dist/active-recall.d.ts +1 -1
  57. package/dist/active-recall.js +4 -2
  58. package/dist/active-recall.js.map +1 -1
  59. package/dist/adapters/index.js +7 -7
  60. package/dist/adapters/registry.js +3 -3
  61. package/dist/admin-console/public/what-helps-me/app.js +1594 -0
  62. package/dist/admin-console/public/what-helps-me/index.html +339 -0
  63. package/dist/admin-console/public/what-helps-me/model.js +685 -0
  64. package/dist/admin-console/public/what-helps-me/what-helps-me.css +1296 -0
  65. package/dist/ambient-provenance.d.ts +1 -1
  66. package/dist/artifact-search.d.ts +1 -1
  67. package/dist/{auto-sync-TDHLZPVP.js → auto-sync-NR3GPCOA.js} +4 -4
  68. package/dist/behavior-learner.d.ts +1 -1
  69. package/dist/behavior-signals.d.ts +1 -1
  70. package/dist/bootstrap.d.ts +9 -9
  71. package/dist/briefing.d.ts +2 -2
  72. package/dist/briefing.js +33 -12
  73. package/dist/buffer-surprise-report.d.ts +1 -1
  74. package/dist/buffer-turn-helpers.d.ts +1 -1
  75. package/dist/buffer.d.ts +2 -2
  76. package/dist/buffer.js +3 -3
  77. package/dist/bulk-import/index.d.ts +3 -3
  78. package/dist/calibration.d.ts +1 -1
  79. package/dist/calibration.js +1 -1
  80. package/dist/capabilities.d.ts +1 -1
  81. package/dist/{catalog-BOOxl-I2.d.ts → catalog-DGFf8mD9.d.ts} +1 -1
  82. package/dist/causal-behavior.d.ts +1 -1
  83. package/dist/causal-consolidation.d.ts +1 -1
  84. package/dist/causal-consolidation.js +34 -13
  85. package/dist/causal-consolidation.js.map +1 -1
  86. package/dist/causal-trajectory-graph.d.ts +1 -1
  87. package/dist/{chunk-ZYLC7NFO.js → chunk-26ZXBTYN.js} +4 -4
  88. package/dist/chunk-26ZXBTYN.js.map +1 -0
  89. package/dist/{chunk-LLJMAULE.js → chunk-2FKEIPIT.js} +5 -5
  90. package/dist/{chunk-3CSX3ETE.js → chunk-2LWQSV6A.js} +23 -11
  91. package/dist/chunk-2LWQSV6A.js.map +1 -0
  92. package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
  93. package/dist/chunk-37DZZFFM.js +502 -0
  94. package/dist/chunk-37DZZFFM.js.map +1 -0
  95. package/dist/chunk-3HSRWJTC.js +9 -0
  96. package/dist/chunk-3HSRWJTC.js.map +1 -0
  97. package/dist/{chunk-XUAMCLY4.js → chunk-3TIZPSJC.js} +17 -13
  98. package/dist/chunk-3TIZPSJC.js.map +1 -0
  99. package/dist/{chunk-PDDTI7IC.js → chunk-43SEBY2M.js} +1 -1
  100. package/dist/{chunk-HWRD23DW.js → chunk-46SZBVDS.js} +3 -3
  101. package/dist/{chunk-6HPHEFXT.js → chunk-5FD7JMTX.js} +2 -2
  102. package/dist/{chunk-7BZOBA5J.js → chunk-5NB726GV.js} +70 -16
  103. package/dist/chunk-5NB726GV.js.map +1 -0
  104. package/dist/chunk-62OKGIJA.js +39 -0
  105. package/dist/chunk-62OKGIJA.js.map +1 -0
  106. package/dist/{chunk-IBKR2TFR.js → chunk-6CYHUWCO.js} +2 -2
  107. package/dist/{chunk-YN6ASZUG.js → chunk-6QPI2ITJ.js} +5 -5
  108. package/dist/{chunk-UU3G6GUP.js → chunk-76JJSXK7.js} +1 -1
  109. package/dist/chunk-76JJSXK7.js.map +1 -0
  110. package/dist/{chunk-PX2LSHYM.js → chunk-7BD6O7ZV.js} +189 -4
  111. package/dist/chunk-7BD6O7ZV.js.map +1 -0
  112. package/dist/chunk-7TBXNYLO.js +346 -0
  113. package/dist/chunk-7TBXNYLO.js.map +1 -0
  114. package/dist/{chunk-NH5IR2WM.js → chunk-BRR5EUTF.js} +6 -2
  115. package/dist/chunk-BRR5EUTF.js.map +1 -0
  116. package/dist/{chunk-NIS256NW.js → chunk-CIAI24TQ.js} +3 -3
  117. package/dist/{chunk-T2JHZWH6.js → chunk-D3LBIUWV.js} +11703 -7573
  118. package/dist/chunk-D3LBIUWV.js.map +1 -0
  119. package/dist/{chunk-7FGL6CGA.js → chunk-DVYRD4HY.js} +2 -2
  120. package/dist/{chunk-G7Z6EENO.js → chunk-EKTP7MNS.js} +4 -4
  121. package/dist/{chunk-S75M5ZRK.js → chunk-F2JRDPON.js} +16 -1
  122. package/dist/chunk-F2JRDPON.js.map +1 -0
  123. package/dist/{chunk-RZ4XGKOJ.js → chunk-FFRTNF3L.js} +8 -8
  124. package/dist/{chunk-VKXWK44Q.js → chunk-FONWBCVY.js} +3 -3
  125. package/dist/{chunk-DZD576ZU.js → chunk-FPLM2TKD.js} +2 -2
  126. package/dist/{chunk-ZPJ4QSGP.js → chunk-FTPONF6B.js} +2 -2
  127. package/dist/{chunk-LJS7KUPV.js → chunk-FWX7W5PM.js} +2 -2
  128. package/dist/{chunk-UTRO5UJU.js → chunk-G6DFSS6A.js} +5 -1
  129. package/dist/chunk-G6DFSS6A.js.map +1 -0
  130. package/dist/{chunk-6LAPLZOS.js → chunk-GJ776BC2.js} +8 -3
  131. package/dist/chunk-GJ776BC2.js.map +1 -0
  132. package/dist/{chunk-G4T7L557.js → chunk-GN3WRFJY.js} +6 -6
  133. package/dist/{chunk-QT2FRYI4.js → chunk-H7NAZ3UB.js} +2 -2
  134. package/dist/{chunk-APPFNGX6.js → chunk-HBUFQRIM.js} +2 -2
  135. package/dist/{chunk-UU7TRFNE.js → chunk-HGJ6NJ4E.js} +2 -2
  136. package/dist/{chunk-WB7JSQCP.js → chunk-HIEWDJ2O.js} +5 -2
  137. package/dist/{chunk-WB7JSQCP.js.map → chunk-HIEWDJ2O.js.map} +1 -1
  138. package/dist/{chunk-UES4BHA2.js → chunk-I3MSGFGD.js} +17 -6
  139. package/dist/chunk-I3MSGFGD.js.map +1 -0
  140. package/dist/{chunk-C4ZNGVK3.js → chunk-IKP5OYQQ.js} +2 -2
  141. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  142. package/dist/{chunk-RDJRFDJY.js → chunk-IQ4NVWEF.js} +2 -2
  143. package/dist/{chunk-CGVHPCP2.js → chunk-JYG4OLWH.js} +2 -2
  144. package/dist/{chunk-WOPA3SGI.js → chunk-K26NWEOC.js} +8 -8
  145. package/dist/{chunk-K4QS7LXU.js → chunk-K2UGPTYY.js} +485 -44
  146. package/dist/chunk-K2UGPTYY.js.map +1 -0
  147. package/dist/{chunk-L6CQ7WIW.js → chunk-KFBOPMF6.js} +11 -11
  148. package/dist/chunk-KFBOPMF6.js.map +1 -0
  149. package/dist/{chunk-G7A5HLUS.js → chunk-KJH6W2I7.js} +2 -2
  150. package/dist/{chunk-IXEBUYO4.js → chunk-KM55SMLG.js} +11 -2
  151. package/dist/{chunk-IXEBUYO4.js.map → chunk-KM55SMLG.js.map} +1 -1
  152. package/dist/{chunk-HOIRB77A.js → chunk-L6Z5OAS4.js} +2 -2
  153. package/dist/chunk-LC2BDILZ.js +57 -0
  154. package/dist/chunk-LC2BDILZ.js.map +1 -0
  155. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  156. package/dist/{chunk-6R4G7N4S.js → chunk-M4YHAODQ.js} +388 -748
  157. package/dist/chunk-M4YHAODQ.js.map +1 -0
  158. package/dist/{chunk-3JLPGJ43.js → chunk-MIH3CJPN.js} +5 -5
  159. package/dist/{chunk-QHFV6VNU.js → chunk-MK5VEWCR.js} +2 -2
  160. package/dist/chunk-NISZQW72.js +140 -0
  161. package/dist/chunk-NISZQW72.js.map +1 -0
  162. package/dist/{chunk-GUKBW4HL.js → chunk-NQMSEB5D.js} +6 -6
  163. package/dist/{chunk-G2LUMAUF.js → chunk-OCIP6YMD.js} +12 -5
  164. package/dist/chunk-OCIP6YMD.js.map +1 -0
  165. package/dist/{chunk-47P3RYZP.js → chunk-PG4QBPVG.js} +21 -11
  166. package/dist/chunk-PG4QBPVG.js.map +1 -0
  167. package/dist/{chunk-WRJVB3C2.js → chunk-PO7NXBG2.js} +2 -2
  168. package/dist/{chunk-QWGE2PUH.js → chunk-PT54WISF.js} +190 -56
  169. package/dist/chunk-PT54WISF.js.map +1 -0
  170. package/dist/{chunk-AZM4CCAC.js → chunk-QZLDRQ4I.js} +8 -8
  171. package/dist/{chunk-THPPCISA.js → chunk-RBII7637.js} +5 -5
  172. package/dist/{chunk-JDZPNCMA.js → chunk-RUNORG2P.js} +6 -3
  173. package/dist/chunk-RUNORG2P.js.map +1 -0
  174. package/dist/{chunk-JV5YUBIZ.js → chunk-RZQ6W2W4.js} +50 -44
  175. package/dist/chunk-RZQ6W2W4.js.map +1 -0
  176. package/dist/{chunk-RUMNY5DT.js → chunk-SMNYAMJD.js} +6 -2
  177. package/dist/chunk-SMNYAMJD.js.map +1 -0
  178. package/dist/{chunk-MKQBXQKU.js → chunk-TB6CBZ5V.js} +22 -15
  179. package/dist/chunk-TB6CBZ5V.js.map +1 -0
  180. package/dist/{chunk-VMMQDEN5.js → chunk-TO7ZTWJW.js} +2 -2
  181. package/dist/{chunk-K2TDK7GG.js → chunk-TWTUGCTY.js} +2 -2
  182. package/dist/{chunk-HLYR2TQL.js → chunk-UCTS3DDH.js} +2 -2
  183. package/dist/{chunk-JYIJNNN3.js → chunk-UG6VXIOK.js} +2 -2
  184. package/dist/{chunk-WM65LU67.js → chunk-UKYS3NU5.js} +2 -2
  185. package/dist/{chunk-WDBX6VER.js → chunk-UPLZNJKN.js} +5 -5
  186. package/dist/{chunk-C23BYTJV.js → chunk-UW5NPZJ2.js} +40 -12
  187. package/dist/chunk-UW5NPZJ2.js.map +1 -0
  188. package/dist/{chunk-2GFK22OZ.js → chunk-V6ERBR7U.js} +2 -2
  189. package/dist/{chunk-CYF6WVE7.js → chunk-W5ZKPI34.js} +139 -158
  190. package/dist/chunk-W5ZKPI34.js.map +1 -0
  191. package/dist/chunk-XX4HPFFM.js +370 -0
  192. package/dist/chunk-XX4HPFFM.js.map +1 -0
  193. package/dist/{chunk-ZMYDYNI4.js → chunk-XZHE2DEN.js} +4 -4
  194. package/dist/chunk-YXQEKCZM.js +223 -0
  195. package/dist/chunk-YXQEKCZM.js.map +1 -0
  196. package/dist/{chunk-GYPQL6XN.js → chunk-Z4N7SVVF.js} +35 -47
  197. package/dist/chunk-Z4N7SVVF.js.map +1 -0
  198. package/dist/{chunk-MAXVH44E.js → chunk-Z54EM5YZ.js} +7 -7
  199. package/dist/{chunk-YGLY6QFV.js → chunk-ZRZVLB7O.js} +2 -2
  200. package/dist/{chunk-4IIFL7GC.js → chunk-ZUKIUXHQ.js} +10 -10
  201. package/dist/{cli-DVUFIa5E.d.ts → cli-Bze8S0Qs.d.ts} +5 -5
  202. package/dist/cli.d.ts +22 -21
  203. package/dist/cli.js +90 -80
  204. package/dist/coding/pre-action-gate.d.ts +1 -1
  205. package/dist/compounding/engine.d.ts +2 -2
  206. package/dist/compounding/engine.js +33 -12
  207. package/dist/compounding/preference-consolidator.d.ts +1 -1
  208. package/dist/compression-optimizer.d.ts +1 -1
  209. package/dist/config.d.ts +1 -1
  210. package/dist/config.js +4 -2
  211. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  212. package/dist/connectors/codex-materialize-runner.js +33 -12
  213. package/dist/connectors/codex-materialize.d.ts +1 -1
  214. package/dist/connectors/index.d.ts +1 -1
  215. package/dist/connectors/index.js +32 -12
  216. package/dist/consolidation-provenance-check.d.ts +2 -2
  217. package/dist/consolidation-undo.d.ts +2 -2
  218. package/dist/contradiction/index.d.ts +2 -2
  219. package/dist/contradiction/index.js +3 -1
  220. package/dist/contradiction-scan-JDTBXRGW.js +20 -0
  221. package/dist/converge-config.d.ts +1 -1
  222. package/dist/convergence-refresh.d.ts +2 -2
  223. package/dist/convergence-refresh.js +35 -14
  224. package/dist/conversation-index/backend.d.ts +1 -1
  225. package/dist/conversation-index/backend.js +2 -2
  226. package/dist/conversation-index/chunker.d.ts +1 -1
  227. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  228. package/dist/conversation-index/indexer.d.ts +1 -1
  229. package/dist/conversation-index/search.d.ts +1 -1
  230. package/dist/corpus-watermark.d.ts +1 -1
  231. package/dist/corpus-watermark.js +25 -24
  232. package/dist/day-summary.d.ts +1 -1
  233. package/dist/delinearize.d.ts +1 -1
  234. package/dist/dependency-propagation-config.d.ts +1 -1
  235. package/dist/{dependency-propagation-delivery-XzF76C9n.d.ts → dependency-propagation-delivery-CzEfB31P.d.ts} +1 -1
  236. package/dist/direct-answer-wiring.d.ts +1 -1
  237. package/dist/direct-answer.d.ts +1 -1
  238. package/dist/embedding-fallback.d.ts +1 -1
  239. package/dist/enrichment/index.d.ts +1 -1
  240. package/dist/entity-retrieval.d.ts +2 -2
  241. package/dist/entity-retrieval.js +33 -12
  242. package/dist/entity-schema.d.ts +1 -1
  243. package/dist/explicit-capture.d.ts +9 -9
  244. package/dist/explicit-capture.js +7 -4
  245. package/dist/external-wiki-access.d.ts +22 -21
  246. package/dist/external-wiki-access.js +56 -51
  247. package/dist/external-wiki-collection-registration.d.ts +1 -1
  248. package/dist/external-wiki-collection.d.ts +1 -1
  249. package/dist/external-wiki-mcp-tools.d.ts +22 -21
  250. package/dist/extraction-error-classification.d.ts +1 -1
  251. package/dist/extraction-faithfulness.d.ts +1 -1
  252. package/dist/extraction-faithfulness.js +2 -2
  253. package/dist/extraction-judge-telemetry.d.ts +1 -1
  254. package/dist/extraction-judge-training.d.ts +1 -1
  255. package/dist/extraction-judge.d.ts +1 -1
  256. package/dist/extraction-judge.js +2 -2
  257. package/dist/extraction-liveness.d.ts +1 -1
  258. package/dist/extraction-normalization.d.ts +1 -1
  259. package/dist/extraction-prompt.d.ts +1 -1
  260. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  261. package/dist/extraction-source-grounding.d.ts +1 -1
  262. package/dist/extraction.d.ts +1 -1
  263. package/dist/extraction.js +35 -19
  264. package/dist/fallback-llm.d.ts +14 -1
  265. package/dist/fallback-llm.js +1 -1
  266. package/dist/{first-start-migration-626C5554.js → first-start-migration-CRZCZAJN.js} +11 -3
  267. package/dist/first-start-migration-CRZCZAJN.js.map +1 -0
  268. package/dist/graph-dashboard-diff.d.ts +1 -1
  269. package/dist/graph-dashboard-key.d.ts +1 -1
  270. package/dist/graph-dashboard-parser.d.ts +1 -1
  271. package/dist/graph-edge-reinforcement.d.ts +1 -1
  272. package/dist/graph-path-reconstruction.d.ts +1 -1
  273. package/dist/graph-path-scoring.d.ts +1 -1
  274. package/dist/graph-snapshot.d.ts +1 -1
  275. package/dist/graph.d.ts +1 -1
  276. package/dist/harmonic-retrieval.js +1 -1
  277. package/dist/importance.d.ts +1 -1
  278. package/dist/importers/index.d.ts +1 -1
  279. package/dist/in-flight-reads.d.ts +1 -1
  280. package/dist/index.d.ts +314 -308
  281. package/dist/index.js +304 -133
  282. package/dist/inline-script.d.ts +3 -0
  283. package/dist/inline-script.js +8 -0
  284. package/dist/inline-script.js.map +1 -0
  285. package/dist/intent.d.ts +1 -1
  286. package/dist/lcm/engine.d.ts +1 -1
  287. package/dist/lcm/engine.js +3 -3
  288. package/dist/lcm/index.d.ts +1 -1
  289. package/dist/lcm/index.js +6 -6
  290. package/dist/lcm/tools.d.ts +1 -1
  291. package/dist/lifecycle.d.ts +1 -1
  292. package/dist/live-connectors-runner.d.ts +1 -1
  293. package/dist/local-llm.d.ts +2 -1
  294. package/dist/local-llm.js +2 -2
  295. package/dist/local-model-endpoint.d.ts +1 -1
  296. package/dist/maintenance/memory-governance.d.ts +1 -1
  297. package/dist/maintenance/memory-governance.js +33 -12
  298. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  299. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +33 -12
  300. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  301. package/dist/maintenance/rebuild-memory-projection.js +34 -13
  302. package/dist/{maintenance-BJzNIKBE.d.ts → maintenance-C8aRWfhq.d.ts} +3 -3
  303. package/dist/mcp-memory-inspector-app.d.ts +22 -21
  304. package/dist/mcp-read-only-tools.js +1 -1
  305. package/dist/memory-action-policy.d.ts +1 -1
  306. package/dist/memory-cache.d.ts +1 -1
  307. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  308. package/dist/memory-projection-mutations.js +2 -2
  309. package/dist/memory-projection-store.d.ts +6 -2
  310. package/dist/memory-projection-store.js +1 -1
  311. package/dist/memory-provenance.d.ts +1 -1
  312. package/dist/memory-snapshot.d.ts +1 -1
  313. package/dist/memory-worth-outcomes.d.ts +2 -2
  314. package/dist/models-json.d.ts +1 -1
  315. package/dist/namespaces/migrate.d.ts +3 -3
  316. package/dist/namespaces/migrate.js +24 -23
  317. package/dist/namespaces/principal.d.ts +1 -1
  318. package/dist/namespaces/search.d.ts +1 -1
  319. package/dist/namespaces/search.js +8 -8
  320. package/dist/namespaces/storage.d.ts +3 -3
  321. package/dist/namespaces/storage.js +33 -12
  322. package/dist/native-knowledge.d.ts +1 -1
  323. package/dist/offline-sync-file-io.d.ts +20 -0
  324. package/dist/offline-sync-file-io.js +18 -0
  325. package/dist/offline-sync-file-io.js.map +1 -0
  326. package/dist/offline-sync-impression-drain.d.ts +1 -1
  327. package/dist/offline-sync-impression-drain.js +5 -5
  328. package/dist/offline-sync.d.ts +8 -6
  329. package/dist/offline-sync.js +2 -1
  330. package/dist/operator-doctor-corpus.d.ts +1 -1
  331. package/dist/operator-doctor-corpus.js +26 -25
  332. package/dist/operator-doctor-replica.d.ts +1 -1
  333. package/dist/operator-toolkit.d.ts +3 -3
  334. package/dist/operator-toolkit.js +39 -36
  335. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  336. package/dist/orchestration/maintenance.d.ts +4 -4
  337. package/dist/orchestration/maintenance.js +38 -18
  338. package/dist/{orchestrator-BiXgBJBQ.d.ts → orchestrator-D85CzDDY.d.ts} +11 -8
  339. package/dist/orchestrator.d.ts +9 -9
  340. package/dist/orchestrator.js +117 -109
  341. package/dist/patterns-cli.d.ts +1 -1
  342. package/dist/{pipeline-sz_dcuLg.d.ts → pipeline-DPcUifCO.d.ts} +1 -1
  343. package/dist/policy-runtime.d.ts +1 -1
  344. package/dist/proactive-contention.d.ts +1 -1
  345. package/dist/provenance.d.ts +1 -1
  346. package/dist/public-http-D-SyWNj6.d.ts +25 -0
  347. package/dist/{purge-533FZLS4.js → purge-GB7PZHT2.js} +2 -2
  348. package/dist/{qmd-eAWObK4M.d.ts → qmd-dFCHp1r0.d.ts} +1 -1
  349. package/dist/qmd-preflight.d.ts +2 -2
  350. package/dist/qmd-recall-cache.d.ts +1 -1
  351. package/dist/qmd.d.ts +2 -2
  352. package/dist/recall-concurrency-config.d.ts +1 -1
  353. package/dist/recall-disclosure-escalation.d.ts +1 -1
  354. package/dist/recall-explain-renderer.d.ts +1 -1
  355. package/dist/recall-explain-renderer.js +3 -3
  356. package/dist/recall-memory-map.d.ts +1 -1
  357. package/dist/recall-planner-llm.d.ts +1 -1
  358. package/dist/recall-planner-llm.js +1 -1
  359. package/dist/recall-state.d.ts +1 -1
  360. package/dist/recall-tag-filter.d.ts +1 -1
  361. package/dist/recall-timings.d.ts +1 -1
  362. package/dist/recall-xray-cli.d.ts +1 -1
  363. package/dist/recall-xray-cli.js +4 -4
  364. package/dist/recall-xray-renderer.d.ts +1 -1
  365. package/dist/recall-xray-renderer.js +3 -3
  366. package/dist/recall-xray.d.ts +1 -1
  367. package/dist/recall-xray.js +2 -2
  368. package/dist/reconcile/cursor.d.ts +2 -1
  369. package/dist/reconcile/manifest.d.ts +2 -1
  370. package/dist/reconcile/manifest.js +6 -5
  371. package/dist/reconcile/plan.d.ts +2 -1
  372. package/dist/reconcile/plan.js +3 -2
  373. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  374. package/dist/replay/normalizers/claude.d.ts +1 -1
  375. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  376. package/dist/replay/normalizers/shared.d.ts +1 -1
  377. package/dist/replay/runner.d.ts +1 -1
  378. package/dist/replay/types.d.ts +1 -1
  379. package/dist/replica-divergence.d.ts +1 -1
  380. package/dist/replica-peers-config.d.ts +1 -1
  381. package/dist/resolve-auth-token.d.ts +1 -1
  382. package/dist/resume-bundles.js +5 -3
  383. package/dist/retrieval-agents.d.ts +2 -2
  384. package/dist/retrieval-tiers.d.ts +1 -1
  385. package/dist/routing/engine.d.ts +1 -1
  386. package/dist/routing/store.d.ts +1 -1
  387. package/dist/salvage-envelope.d.ts +1 -1
  388. package/dist/schemas.d.ts +38 -38
  389. package/dist/{scope-profiles-BBQsXuyj.d.ts → scope-profiles-DICK7GtN.d.ts} +1 -1
  390. package/dist/search/embed-helper.d.ts +1 -1
  391. package/dist/search/factory.d.ts +1 -1
  392. package/dist/search/factory.js +6 -6
  393. package/dist/search/index.d.ts +1 -1
  394. package/dist/search/index.js +12 -12
  395. package/dist/search/lancedb-backend.d.ts +1 -1
  396. package/dist/search/meilisearch-backend.d.ts +1 -1
  397. package/dist/search/noop-backend.d.ts +1 -1
  398. package/dist/search/orama-backend.d.ts +1 -1
  399. package/dist/search/port.d.ts +1 -1
  400. package/dist/search/remote-backend.d.ts +1 -1
  401. package/dist/{semantic-consolidation-HD8RkzVA.d.ts → semantic-consolidation-BP7NAX0N.d.ts} +1 -1
  402. package/dist/semantic-consolidation.d.ts +2 -2
  403. package/dist/semantic-consolidation.js +34 -13
  404. package/dist/semantic-rule-promotion.js +33 -12
  405. package/dist/semantic-rule-verifier.d.ts +1 -1
  406. package/dist/semantic-rule-verifier.js +33 -12
  407. package/dist/{service-DIrtHHEg.d.ts → service-DT-pJyda.d.ts} +660 -660
  408. package/dist/session-observer-bands.d.ts +1 -1
  409. package/dist/session-observer-state.d.ts +1 -1
  410. package/dist/shared-context/manager.d.ts +1 -1
  411. package/dist/signal.d.ts +1 -1
  412. package/dist/source-agent-qualifier.d.ts +1 -1
  413. package/dist/source-agent-qualifier.js +31 -15
  414. package/dist/{storage-DqCzXi56.d.ts → storage-qOnaJ1Lw.d.ts} +49 -13
  415. package/dist/storage.d.ts +2 -2
  416. package/dist/storage.js +33 -12
  417. package/dist/summarizer.d.ts +1 -1
  418. package/dist/summarizer.js +5 -5
  419. package/dist/summary-snapshot.d.ts +1 -1
  420. package/dist/support-passport/index.d.ts +518 -0
  421. package/dist/support-passport/index.js +357 -0
  422. package/dist/support-passport/index.js.map +1 -0
  423. package/dist/temporal-supersession.d.ts +2 -2
  424. package/dist/temporal-timeline-recall.d.ts +1 -1
  425. package/dist/temporal-validity.d.ts +1 -1
  426. package/dist/threading.d.ts +1 -1
  427. package/dist/tier-migration.d.ts +2 -2
  428. package/dist/tier-routing.d.ts +2 -2
  429. package/dist/tier-routing.js +7 -1
  430. package/dist/{tier-stats-2JCZYEF7.js → tier-stats-EXYSKSVN.js} +8 -2
  431. package/dist/{tier-stats-2JCZYEF7.js.map → tier-stats-EXYSKSVN.js.map} +1 -1
  432. package/dist/topics.d.ts +1 -1
  433. package/dist/transcript.d.ts +1 -1
  434. package/dist/transfer/autodetect.js +1 -1
  435. package/dist/transfer/backup.js +1 -1
  436. package/dist/transfer/capsule-export.js +2 -2
  437. package/dist/transfer/types.d.ts +36 -36
  438. package/dist/trust-score-stage.d.ts +1 -1
  439. package/dist/trust-score.d.ts +1 -1
  440. package/dist/{types-d_j4vC6N.d.ts → types-ed-SvS2t.d.ts} +8 -1
  441. package/dist/types.d.ts +1 -1
  442. package/dist/types.js +1 -1
  443. package/dist/utility-runtime.d.ts +2 -2
  444. package/dist/verified-recall.js +33 -12
  445. package/dist/write-envelope.d.ts +1 -1
  446. package/dist/write-rate-limiter.d.ts +11 -5
  447. package/dist/write-rate-limiter.js +1 -1
  448. package/package.json +12 -2
  449. package/scripts/build-with-heap.mjs +11 -1
  450. package/src/access-authorization-probe.ts +20 -2
  451. package/src/access-boundary.ts +32 -0
  452. package/src/access-http.test.ts +1971 -643
  453. package/src/access-http.ts +81 -74
  454. package/src/access-mcp.ts +3 -1
  455. package/src/access-memory-search-fanout.ts +24 -1
  456. package/src/access-offline-manifest.ts +5 -0
  457. package/src/access-operations.ts +2 -0
  458. package/src/access-service-namespace.test.ts +14 -0
  459. package/src/access-service-observe-scope.test.ts +2 -0
  460. package/src/access-service-offline-file-content.test.ts +211 -0
  461. package/src/access-service.ts +628 -884
  462. package/src/access-surface-catalog.test.ts +40 -10
  463. package/src/access-surface-catalog.ts +15 -0
  464. package/src/active-memory-bridge.test.ts +151 -0
  465. package/src/active-memory-bridge.ts +95 -21
  466. package/src/briefing.test.ts +38 -0
  467. package/src/briefing.ts +3 -3
  468. package/src/cli.ts +6 -1
  469. package/src/config.ts +18 -9
  470. package/src/connectors/codex-materialize-runner.ts +2 -0
  471. package/src/contradiction/contradiction-scan.ts +2 -0
  472. package/src/contradiction/contradiction.test.ts +42 -0
  473. package/src/correction/correction-access-wiring.ts +8 -2
  474. package/src/correction/correction-storage-integration.test.ts +7 -0
  475. package/src/explicit-capture.test.ts +24 -0
  476. package/src/explicit-capture.ts +2 -0
  477. package/src/fallback-llm.test.ts +54 -0
  478. package/src/fallback-llm.ts +54 -9
  479. package/src/index.ts +3 -0
  480. package/src/inline-script.ts +6 -0
  481. package/src/local-llm-headers-timeout.test.ts +42 -0
  482. package/src/local-llm.ts +8 -8
  483. package/src/maintenance/first-start-migration.ts +2 -1
  484. package/src/maintenance/memory-governance.test.ts +25 -0
  485. package/src/maintenance/memory-governance.ts +4 -1
  486. package/src/maintenance/pattern-reinforcement.test.ts +20 -0
  487. package/src/maintenance/pattern-reinforcement.ts +2 -1
  488. package/src/maintenance/rebuild-memory-projection.ts +8 -8
  489. package/src/mcp-read-only-tools.ts +3 -0
  490. package/src/memory-projection-store.ts +25 -27
  491. package/src/offline-sync-file-io.ts +68 -0
  492. package/src/offline-sync.ts +43 -57
  493. package/src/orchestration/consolidation-run.ts +5 -4
  494. package/src/orchestration/contradiction-linking-coordinator.ts +7 -0
  495. package/src/orchestration/contradiction-localization.integration.test.ts +115 -0
  496. package/src/orchestration/lifecycle-policy-coordinator.ts +2 -1
  497. package/src/orchestration/qmd-result-resolver.test.ts +215 -14
  498. package/src/orchestration/qmd-result-resolver.ts +142 -82
  499. package/src/orchestration/recall-search-pipeline.test.ts +41 -0
  500. package/src/orchestration/recall-search-pipeline.ts +12 -0
  501. package/src/orchestration/semantic-consolidation-coordinator.ts +2 -1
  502. package/src/orchestration/tier-migration-coordinator.ts +23 -56
  503. package/src/orchestration/workspace-ops.test.ts +37 -0
  504. package/src/orchestration/workspace-ops.ts +5 -29
  505. package/src/orchestrator.ts +7 -8
  506. package/src/relay/mission-access.test.ts +11 -11
  507. package/src/relay/mission-ui.test.ts +3 -1
  508. package/src/storage/supersession-audit.ts +7 -1
  509. package/src/storage/supersession-side-effects.ts +17 -5
  510. package/src/storage/tombstone-blocked-capture-sync.ts +31 -0
  511. package/src/storage-contract/failure-semantics.test.ts +102 -0
  512. package/src/storage.ts +18 -19
  513. package/src/support-passport/access-http-base.ts +143 -0
  514. package/src/support-passport/access-http.ts +172 -0
  515. package/src/support-passport/access-operations.ts +272 -0
  516. package/src/support-passport/access-service-base.test.ts +193 -0
  517. package/src/support-passport/access-service-base.ts +176 -0
  518. package/src/support-passport/access-surface.test.ts +283 -0
  519. package/src/support-passport/access-surface.ts +286 -0
  520. package/src/support-passport/card-projection.test.ts +137 -0
  521. package/src/support-passport/card-projection.ts +348 -0
  522. package/src/support-passport/card-replacement.ts +126 -0
  523. package/src/support-passport/card-service-commit-forwarding.test.ts +160 -0
  524. package/src/support-passport/card-service.test.ts +2964 -0
  525. package/src/support-passport/card-service.ts +1184 -0
  526. package/src/support-passport/card-state.ts +158 -0
  527. package/src/support-passport/config-model-routing.test.ts +37 -0
  528. package/src/support-passport/config.test.ts +34 -0
  529. package/src/support-passport/config.ts +41 -0
  530. package/src/support-passport/contracts.test.ts +156 -0
  531. package/src/support-passport/contracts.ts +149 -0
  532. package/src/support-passport/errors.ts +30 -0
  533. package/src/support-passport/generated-batch.test.ts +185 -0
  534. package/src/support-passport/generated-batch.ts +338 -0
  535. package/src/support-passport/grant-contracts.ts +183 -0
  536. package/src/support-passport/grant-generated-batch-scope.test.ts +201 -0
  537. package/src/support-passport/grant-service-cache.test.ts +148 -0
  538. package/src/support-passport/grant-service-cancellation.test.ts +83 -0
  539. package/src/support-passport/grant-service-marker-cache.test.ts +62 -0
  540. package/src/support-passport/grant-service.test.ts +3496 -0
  541. package/src/support-passport/grant-service.ts +513 -0
  542. package/src/support-passport/grant-store-commit-hook.test.ts +123 -0
  543. package/src/support-passport/grant-store-order.test.ts +60 -0
  544. package/src/support-passport/grant-store-path-safety.test.ts +30 -0
  545. package/src/support-passport/grant-store-recovery.test.ts +448 -0
  546. package/src/support-passport/grant-store.ts +906 -0
  547. package/src/support-passport/index.ts +17 -0
  548. package/src/support-passport/mcp-tools.test.ts +222 -0
  549. package/src/support-passport/mcp-tools.ts +187 -0
  550. package/src/support-passport/model-adapter.test.ts +840 -0
  551. package/src/support-passport/model-adapter.ts +602 -0
  552. package/src/support-passport/model-audit-pinned-write.test.ts +59 -0
  553. package/src/support-passport/model-audit.ts +152 -0
  554. package/src/support-passport/model-bridge.test.ts +456 -0
  555. package/src/support-passport/model-bridge.ts +439 -0
  556. package/src/support-passport/model-contracts.ts +124 -0
  557. package/src/support-passport/model-service.test.ts +1731 -0
  558. package/src/support-passport/model-service.ts +399 -0
  559. package/src/support-passport/offline-sync-guard.ts +230 -0
  560. package/src/support-passport/owner-http.test.ts +671 -0
  561. package/src/support-passport/owner-lock.ts +70 -0
  562. package/src/support-passport/private-file.test.ts +99 -0
  563. package/src/support-passport/private-file.ts +498 -0
  564. package/src/support-passport/public-http.test.ts +476 -0
  565. package/src/support-passport/public-http.ts +601 -0
  566. package/src/support-passport/ui.test.ts +181 -0
  567. package/src/tier-routing.ts +28 -17
  568. package/src/training-export/converter.test.ts +24 -0
  569. package/src/training-export/converter.ts +2 -0
  570. package/src/types.ts +8 -1
  571. package/src/wearables/service.test.ts +13 -0
  572. package/src/wearables/service.ts +2 -0
  573. package/src/write-rate-limiter.test.ts +30 -6
  574. package/src/write-rate-limiter.ts +29 -18
  575. package/dist/chunk-3CSX3ETE.js.map +0 -1
  576. package/dist/chunk-47P3RYZP.js.map +0 -1
  577. package/dist/chunk-6LAPLZOS.js.map +0 -1
  578. package/dist/chunk-6R4G7N4S.js.map +0 -1
  579. package/dist/chunk-7BZOBA5J.js.map +0 -1
  580. package/dist/chunk-C23BYTJV.js.map +0 -1
  581. package/dist/chunk-CYF6WVE7.js.map +0 -1
  582. package/dist/chunk-G2LUMAUF.js.map +0 -1
  583. package/dist/chunk-GYPQL6XN.js.map +0 -1
  584. package/dist/chunk-JDZPNCMA.js.map +0 -1
  585. package/dist/chunk-JV5YUBIZ.js.map +0 -1
  586. package/dist/chunk-K4QS7LXU.js.map +0 -1
  587. package/dist/chunk-L6CQ7WIW.js.map +0 -1
  588. package/dist/chunk-MKQBXQKU.js.map +0 -1
  589. package/dist/chunk-NH5IR2WM.js.map +0 -1
  590. package/dist/chunk-PX2LSHYM.js.map +0 -1
  591. package/dist/chunk-QWGE2PUH.js.map +0 -1
  592. package/dist/chunk-RUMNY5DT.js.map +0 -1
  593. package/dist/chunk-S75M5ZRK.js.map +0 -1
  594. package/dist/chunk-T2JHZWH6.js.map +0 -1
  595. package/dist/chunk-UES4BHA2.js.map +0 -1
  596. package/dist/chunk-UTRO5UJU.js.map +0 -1
  597. package/dist/chunk-UU3G6GUP.js.map +0 -1
  598. package/dist/chunk-XUAMCLY4.js.map +0 -1
  599. package/dist/chunk-ZYLC7NFO.js.map +0 -1
  600. package/dist/contradiction-scan-HYLIRMJ2.js +0 -14
  601. package/dist/first-start-migration-626C5554.js.map +0 -1
  602. /package/dist/{auto-sync-TDHLZPVP.js.map → auto-sync-NR3GPCOA.js.map} +0 -0
  603. /package/dist/{chunk-LLJMAULE.js.map → chunk-2FKEIPIT.js.map} +0 -0
  604. /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
  605. /package/dist/{chunk-PDDTI7IC.js.map → chunk-43SEBY2M.js.map} +0 -0
  606. /package/dist/{chunk-HWRD23DW.js.map → chunk-46SZBVDS.js.map} +0 -0
  607. /package/dist/{chunk-6HPHEFXT.js.map → chunk-5FD7JMTX.js.map} +0 -0
  608. /package/dist/{chunk-IBKR2TFR.js.map → chunk-6CYHUWCO.js.map} +0 -0
  609. /package/dist/{chunk-YN6ASZUG.js.map → chunk-6QPI2ITJ.js.map} +0 -0
  610. /package/dist/{chunk-NIS256NW.js.map → chunk-CIAI24TQ.js.map} +0 -0
  611. /package/dist/{chunk-7FGL6CGA.js.map → chunk-DVYRD4HY.js.map} +0 -0
  612. /package/dist/{chunk-G7Z6EENO.js.map → chunk-EKTP7MNS.js.map} +0 -0
  613. /package/dist/{chunk-RZ4XGKOJ.js.map → chunk-FFRTNF3L.js.map} +0 -0
  614. /package/dist/{chunk-VKXWK44Q.js.map → chunk-FONWBCVY.js.map} +0 -0
  615. /package/dist/{chunk-DZD576ZU.js.map → chunk-FPLM2TKD.js.map} +0 -0
  616. /package/dist/{chunk-ZPJ4QSGP.js.map → chunk-FTPONF6B.js.map} +0 -0
  617. /package/dist/{chunk-LJS7KUPV.js.map → chunk-FWX7W5PM.js.map} +0 -0
  618. /package/dist/{chunk-G4T7L557.js.map → chunk-GN3WRFJY.js.map} +0 -0
  619. /package/dist/{chunk-QT2FRYI4.js.map → chunk-H7NAZ3UB.js.map} +0 -0
  620. /package/dist/{chunk-APPFNGX6.js.map → chunk-HBUFQRIM.js.map} +0 -0
  621. /package/dist/{chunk-UU7TRFNE.js.map → chunk-HGJ6NJ4E.js.map} +0 -0
  622. /package/dist/{chunk-C4ZNGVK3.js.map → chunk-IKP5OYQQ.js.map} +0 -0
  623. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  624. /package/dist/{chunk-RDJRFDJY.js.map → chunk-IQ4NVWEF.js.map} +0 -0
  625. /package/dist/{chunk-CGVHPCP2.js.map → chunk-JYG4OLWH.js.map} +0 -0
  626. /package/dist/{chunk-WOPA3SGI.js.map → chunk-K26NWEOC.js.map} +0 -0
  627. /package/dist/{chunk-G7A5HLUS.js.map → chunk-KJH6W2I7.js.map} +0 -0
  628. /package/dist/{chunk-HOIRB77A.js.map → chunk-L6Z5OAS4.js.map} +0 -0
  629. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  630. /package/dist/{chunk-3JLPGJ43.js.map → chunk-MIH3CJPN.js.map} +0 -0
  631. /package/dist/{chunk-QHFV6VNU.js.map → chunk-MK5VEWCR.js.map} +0 -0
  632. /package/dist/{chunk-GUKBW4HL.js.map → chunk-NQMSEB5D.js.map} +0 -0
  633. /package/dist/{chunk-WRJVB3C2.js.map → chunk-PO7NXBG2.js.map} +0 -0
  634. /package/dist/{chunk-AZM4CCAC.js.map → chunk-QZLDRQ4I.js.map} +0 -0
  635. /package/dist/{chunk-THPPCISA.js.map → chunk-RBII7637.js.map} +0 -0
  636. /package/dist/{chunk-VMMQDEN5.js.map → chunk-TO7ZTWJW.js.map} +0 -0
  637. /package/dist/{chunk-K2TDK7GG.js.map → chunk-TWTUGCTY.js.map} +0 -0
  638. /package/dist/{chunk-HLYR2TQL.js.map → chunk-UCTS3DDH.js.map} +0 -0
  639. /package/dist/{chunk-JYIJNNN3.js.map → chunk-UG6VXIOK.js.map} +0 -0
  640. /package/dist/{chunk-WM65LU67.js.map → chunk-UKYS3NU5.js.map} +0 -0
  641. /package/dist/{chunk-WDBX6VER.js.map → chunk-UPLZNJKN.js.map} +0 -0
  642. /package/dist/{chunk-2GFK22OZ.js.map → chunk-V6ERBR7U.js.map} +0 -0
  643. /package/dist/{chunk-ZMYDYNI4.js.map → chunk-XZHE2DEN.js.map} +0 -0
  644. /package/dist/{chunk-MAXVH44E.js.map → chunk-Z54EM5YZ.js.map} +0 -0
  645. /package/dist/{chunk-YGLY6QFV.js.map → chunk-ZRZVLB7O.js.map} +0 -0
  646. /package/dist/{chunk-4IIFL7GC.js.map → chunk-ZUKIUXHQ.js.map} +0 -0
  647. /package/dist/{contradiction-scan-HYLIRMJ2.js.map → contradiction-scan-JDTBXRGW.js.map} +0 -0
  648. /package/dist/{purge-533FZLS4.js.map → purge-GB7PZHT2.js.map} +0 -0
@@ -5,9 +5,20 @@ import path from "node:path";
5
5
  import test from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
 
8
- import { EngramAccessHttpServer, type RemnicAdminControls, type RemnicAdminDashboardStatus } from "./access-http.js";
9
- import { EngramAccessInputError, type EngramAccessRecallRequest, type EngramAccessService } from "./access-service.js";
10
- import { tokenCapabilityStore, type TokenCapabilities } from "./access-token-capabilities.js";
8
+ import {
9
+ EngramAccessHttpServer,
10
+ type RemnicAdminControls,
11
+ type RemnicAdminDashboardStatus,
12
+ } from "./access-http.js";
13
+ import {
14
+ EngramAccessInputError,
15
+ type EngramAccessRecallRequest,
16
+ type EngramAccessService,
17
+ } from "./access-service.js";
18
+ import {
19
+ tokenCapabilityStore,
20
+ type TokenCapabilities,
21
+ } from "./access-token-capabilities.js";
11
22
  import { parseConfig } from "./config.js";
12
23
  import { readPair, writePair } from "./contradiction/contradiction-review.js";
13
24
  import { createChatSession } from "./chat/chat-session.js";
@@ -15,6 +26,7 @@ import { DEFAULT_CHAT_CONFIG } from "./chat/chat-config.js";
15
26
  import { projectTagProjectId } from "./coding/coding-namespace.js";
16
27
  import { OFFLINE_SYNC_MAX_MTIME_MS } from "./offline-sync.js";
17
28
  import type { StorageManager } from "./storage.js";
29
+ import { SupportPassportError } from "./support-passport/errors.js";
18
30
 
19
31
  test("HTTP server rejects invalid constructor ports", () => {
20
32
  const service = {} as EngramAccessService;
@@ -78,13 +90,18 @@ test("HTTP liveness is authenticated and does not run detailed health diagnostic
78
90
 
79
91
  const status = await server.start();
80
92
  try {
81
- const denied = await fetch(`http://127.0.0.1:${status.port}/engram/v1/live`);
93
+ const denied = await fetch(
94
+ `http://127.0.0.1:${status.port}/engram/v1/live`,
95
+ );
82
96
  assert.equal(denied.status, 401);
83
97
 
84
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/live`, {
85
- headers: { authorization: "Bearer test-token" },
86
- signal: AbortSignal.timeout(500),
87
- });
98
+ const response = await fetch(
99
+ `http://127.0.0.1:${status.port}/engram/v1/live`,
100
+ {
101
+ headers: { authorization: "Bearer test-token" },
102
+ signal: AbortSignal.timeout(500),
103
+ },
104
+ );
88
105
  assert.equal(response.status, 200);
89
106
  assert.deepEqual(await response.json(), { ok: true, ready: true });
90
107
  assert.equal(healthCalls, 0);
@@ -110,11 +127,17 @@ test("HTTP detailed health route still runs service diagnostics", async () => {
110
127
 
111
128
  const status = await server.start();
112
129
  try {
113
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`, {
114
- headers: { authorization: "Bearer test-token" },
115
- });
130
+ const response = await fetch(
131
+ `http://127.0.0.1:${status.port}/engram/v1/health`,
132
+ {
133
+ headers: { authorization: "Bearer test-token" },
134
+ },
135
+ );
116
136
  assert.equal(response.status, 200);
117
- assert.deepEqual(await response.json(), { ok: true, diagnostics: "complete" });
137
+ assert.deepEqual(await response.json(), {
138
+ ok: true,
139
+ diagnostics: "complete",
140
+ });
118
141
  assert.equal(healthCalls, 1);
119
142
  } finally {
120
143
  await server.stop();
@@ -138,13 +161,25 @@ test("HTTP memory browse rejects malformed pagination and sort query values", as
138
161
 
139
162
  const status = await server.start();
140
163
  try {
141
- for (const query of ["limit=10abc", "offset=1.5", "limit=0", "sort=udpated_desc"]) {
142
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/memories?${query}`, {
143
- headers: { authorization: "Bearer test-token" },
144
- });
164
+ for (const query of [
165
+ "limit=10abc",
166
+ "offset=1.5",
167
+ "limit=0",
168
+ "sort=udpated_desc",
169
+ ]) {
170
+ const response = await fetch(
171
+ `http://127.0.0.1:${status.port}/engram/v1/memories?${query}`,
172
+ {
173
+ headers: { authorization: "Bearer test-token" },
174
+ },
175
+ );
145
176
  assert.equal(response.status, 400, `${query} should be rejected`);
146
177
  }
147
- assert.equal(calls.length, 0, "invalid queries must fail before calling memoryBrowse");
178
+ assert.equal(
179
+ calls.length,
180
+ 0,
181
+ "invalid queries must fail before calling memoryBrowse",
182
+ );
148
183
 
149
184
  const response = await fetch(
150
185
  `http://127.0.0.1:${status.port}/engram/v1/memories?limit=10&offset=1&sort=updated_desc`,
@@ -169,7 +204,11 @@ test("HTTP memory browse rejects malformed pagination and sort query values", as
169
204
  });
170
205
 
171
206
  test("HTTP batch LCM flush isolates namespace failures and charges one write quota", async () => {
172
- const calls: Array<{ namespace?: string; sessionKey: string; authenticatedPrincipal?: string }> = [];
207
+ const calls: Array<{
208
+ namespace?: string;
209
+ sessionKey: string;
210
+ authenticatedPrincipal?: string;
211
+ }> = [];
173
212
  const service = {
174
213
  lcmCompactionFlush: async (request: {
175
214
  namespace?: string;
@@ -177,7 +216,8 @@ test("HTTP batch LCM flush isolates namespace failures and charges one write quo
177
216
  authenticatedPrincipal?: string;
178
217
  }) => {
179
218
  calls.push(request);
180
- if (request.namespace === "team-b") throw new Error("team-b flush failed");
219
+ if (request.namespace === "team-b")
220
+ throw new Error("team-b flush failed");
181
221
  return { enabled: true, flushed: true };
182
222
  },
183
223
  } as unknown as EngramAccessService;
@@ -191,17 +231,20 @@ test("HTTP batch LCM flush isolates namespace failures and charges one write quo
191
231
 
192
232
  const status = await server.start();
193
233
  try {
194
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`, {
195
- method: "POST",
196
- headers: {
197
- authorization: "Bearer test-token",
198
- "content-type": "application/json",
234
+ const response = await fetch(
235
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`,
236
+ {
237
+ method: "POST",
238
+ headers: {
239
+ authorization: "Bearer test-token",
240
+ "content-type": "application/json",
241
+ },
242
+ body: JSON.stringify({
243
+ sessionKey: "batch-session",
244
+ namespaces: ["team-a", "team-b"],
245
+ }),
199
246
  },
200
- body: JSON.stringify({
201
- sessionKey: "batch-session",
202
- namespaces: ["team-a", "team-b"],
203
- }),
204
- });
247
+ );
205
248
  assert.equal(response.status, 200);
206
249
  assert.deepEqual(await response.json(), {
207
250
  enabled: false,
@@ -230,32 +273,42 @@ test("HTTP batch LCM flush isolates namespace failures and charges one write quo
230
273
  },
231
274
  ]);
232
275
 
233
- const conflicting = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`, {
234
- method: "POST",
235
- headers: {
236
- authorization: "Bearer test-token",
237
- "content-type": "application/json",
276
+ const conflicting = await fetch(
277
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`,
278
+ {
279
+ method: "POST",
280
+ headers: {
281
+ authorization: "Bearer test-token",
282
+ "content-type": "application/json",
283
+ },
284
+ body: JSON.stringify({
285
+ sessionKey: "batch-session",
286
+ namespace: "team-a",
287
+ namespaces: ["team-b"],
288
+ }),
238
289
  },
239
- body: JSON.stringify({
240
- sessionKey: "batch-session",
241
- namespace: "team-a",
242
- namespaces: ["team-b"],
243
- }),
244
- });
290
+ );
245
291
  assert.equal(conflicting.status, 400);
246
- assert.equal(calls.length, 2, "conflicting namespace forms must fail before dispatch");
292
+ assert.equal(
293
+ calls.length,
294
+ 2,
295
+ "conflicting namespace forms must fail before dispatch",
296
+ );
247
297
 
248
- const rateLimited = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`, {
249
- method: "POST",
250
- headers: {
251
- authorization: "Bearer test-token",
252
- "content-type": "application/json",
298
+ const rateLimited = await fetch(
299
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`,
300
+ {
301
+ method: "POST",
302
+ headers: {
303
+ authorization: "Bearer test-token",
304
+ "content-type": "application/json",
305
+ },
306
+ body: JSON.stringify({
307
+ sessionKey: "batch-session",
308
+ namespaces: ["team-a"],
309
+ }),
253
310
  },
254
- body: JSON.stringify({
255
- sessionKey: "batch-session",
256
- namespaces: ["team-a"],
257
- }),
258
- });
311
+ );
259
312
  assert.equal(rateLimited.status, 429);
260
313
  assert.equal(calls.length, 2, "a batch consumes one write quota unit");
261
314
  } finally {
@@ -281,19 +334,22 @@ test("HTTP LCM compaction record uses the lifecycle route and write quota", asyn
281
334
 
282
335
  const status = await server.start();
283
336
  try {
284
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/record`, {
285
- method: "POST",
286
- headers: {
287
- authorization: "Bearer test-token",
288
- "content-type": "application/json",
337
+ const response = await fetch(
338
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/record`,
339
+ {
340
+ method: "POST",
341
+ headers: {
342
+ authorization: "Bearer test-token",
343
+ "content-type": "application/json",
344
+ },
345
+ body: JSON.stringify({
346
+ sessionKey: "record-session",
347
+ namespace: "team-a",
348
+ tokensBefore: 100,
349
+ tokensAfter: 40,
350
+ }),
289
351
  },
290
- body: JSON.stringify({
291
- sessionKey: "record-session",
292
- namespace: "team-a",
293
- tokensBefore: 100,
294
- tokensAfter: 40,
295
- }),
296
- });
352
+ );
297
353
  assert.equal(response.status, 200);
298
354
  assert.deepEqual(await response.json(), {
299
355
  enabled: true,
@@ -310,18 +366,21 @@ test("HTTP LCM compaction record uses the lifecycle route and write quota", asyn
310
366
  },
311
367
  ]);
312
368
 
313
- const rateLimited = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/record`, {
314
- method: "POST",
315
- headers: {
316
- authorization: "Bearer test-token",
317
- "content-type": "application/json",
369
+ const rateLimited = await fetch(
370
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/record`,
371
+ {
372
+ method: "POST",
373
+ headers: {
374
+ authorization: "Bearer test-token",
375
+ "content-type": "application/json",
376
+ },
377
+ body: JSON.stringify({
378
+ sessionKey: "record-session",
379
+ tokensBefore: 120,
380
+ tokensAfter: 60,
381
+ }),
318
382
  },
319
- body: JSON.stringify({
320
- sessionKey: "record-session",
321
- tokensBefore: 120,
322
- tokensAfter: 60,
323
- }),
324
- });
383
+ );
325
384
  assert.equal(rateLimited.status, 429);
326
385
  assert.equal(calls.length, 1);
327
386
  } finally {
@@ -330,9 +389,16 @@ test("HTTP LCM compaction record uses the lifecycle route and write quota", asyn
330
389
  });
331
390
 
332
391
  test("HTTP batch LCM flush deduplicates aliases after effective namespace resolution", async () => {
333
- const calls: Array<{ namespace?: string; sessionKey: string; authenticatedPrincipal?: string }> = [];
392
+ const calls: Array<{
393
+ namespace?: string;
394
+ sessionKey: string;
395
+ authenticatedPrincipal?: string;
396
+ }> = [];
334
397
  const service = {
335
- configRef: parseConfig({ namespacesEnabled: true, defaultNamespace: "default" }),
398
+ configRef: parseConfig({
399
+ namespacesEnabled: true,
400
+ defaultNamespace: "default",
401
+ }),
336
402
  lcmCompactionFlush: async (request: {
337
403
  namespace?: string;
338
404
  sessionKey: string;
@@ -351,17 +417,20 @@ test("HTTP batch LCM flush deduplicates aliases after effective namespace resolu
351
417
 
352
418
  const status = await server.start();
353
419
  try {
354
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`, {
355
- method: "POST",
356
- headers: {
357
- authorization: "Bearer test-token",
358
- "content-type": "application/json",
420
+ const response = await fetch(
421
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`,
422
+ {
423
+ method: "POST",
424
+ headers: {
425
+ authorization: "Bearer test-token",
426
+ "content-type": "application/json",
427
+ },
428
+ body: JSON.stringify({
429
+ sessionKey: "effective-namespace-session",
430
+ namespaces: ["", "default"],
431
+ }),
359
432
  },
360
- body: JSON.stringify({
361
- sessionKey: "effective-namespace-session",
362
- namespaces: ["", "default"],
363
- }),
364
- });
433
+ );
365
434
  assert.equal(response.status, 200);
366
435
  assert.deepEqual(await response.json(), {
367
436
  enabled: true,
@@ -393,7 +462,6 @@ test("HTTP batch LCM flush deduplicates aliases after effective namespace resolu
393
462
  }
394
463
  });
395
464
 
396
-
397
465
  test("HTTP batch LCM flush isolates per-namespace authorization failures", async () => {
398
466
  const calls: string[] = [];
399
467
  const service = {
@@ -421,17 +489,20 @@ test("HTTP batch LCM flush isolates per-namespace authorization failures", async
421
489
 
422
490
  const status = await server.start();
423
491
  try {
424
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`, {
425
- method: "POST",
426
- headers: {
427
- authorization: "Bearer scoped-token",
428
- "content-type": "application/json",
492
+ const response = await fetch(
493
+ `http://127.0.0.1:${status.port}/engram/v1/lcm/compaction/flush`,
494
+ {
495
+ method: "POST",
496
+ headers: {
497
+ authorization: "Bearer scoped-token",
498
+ "content-type": "application/json",
499
+ },
500
+ body: JSON.stringify({
501
+ sessionKey: "authorized-batch-session",
502
+ namespaces: ["team-a", "team-b"],
503
+ }),
429
504
  },
430
- body: JSON.stringify({
431
- sessionKey: "authorized-batch-session",
432
- namespaces: ["team-a", "team-b"],
433
- }),
434
- });
505
+ );
435
506
  assert.equal(response.status, 200);
436
507
  assert.deepEqual(await response.json(), {
437
508
  enabled: false,
@@ -473,7 +544,7 @@ test("HTTP admin console assets are public but API routes require bearer authent
473
544
  assert.match(app.headers.get("content-type") ?? "", /javascript/);
474
545
 
475
546
  const api = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
476
- const body = await api.json() as { code?: string };
547
+ const body = (await api.json()) as { code?: string };
477
548
  assert.equal(api.status, 401);
478
549
  assert.equal(body.code, "unauthorized");
479
550
  assert.equal(api.headers.get("www-authenticate"), "Bearer");
@@ -493,7 +564,9 @@ test("HTTP admin console can prefill the primary bearer token when explicitly en
493
564
 
494
565
  const status = await server.start();
495
566
  try {
496
- const unauthenticatedShell = await fetch(`http://127.0.0.1:${status.port}/engram/ui/`);
567
+ const unauthenticatedShell = await fetch(
568
+ `http://127.0.0.1:${status.port}/engram/ui/`,
569
+ );
497
570
  assert.equal(unauthenticatedShell.status, 200);
498
571
  assert.doesNotMatch(await unauthenticatedShell.text(), /test-token/);
499
572
 
@@ -504,10 +577,11 @@ test("HTTP admin console can prefill the primary bearer token when explicitly en
504
577
  assert.equal(shell.headers.get("cache-control"), "private, no-store");
505
578
  assert.equal(shell.headers.get("vary"), "authorization");
506
579
  const shellText = await shell.text();
507
- assert.match(
508
- shellText,
509
- /window\.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__="test-token"/,
510
- );
580
+ assert.match(shellText, /Object\.defineProperty\(window,key/);
581
+ assert.match(shellText, /window\.addEventListener\("pagehide",clear,\{once:true\}\)/);
582
+ assert.match(shellText, /script\.textContent="";script\.remove\(\)/);
583
+ assert.match(shellText, /\}\)\("test-token",document\.currentScript\)/);
584
+ assert.doesNotMatch(shellText, /window\.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__="test-token"/);
511
585
 
512
586
  const app = await fetch(`http://127.0.0.1:${status.port}/engram/ui/app.js`);
513
587
  assert.equal(app.status, 200);
@@ -517,6 +591,59 @@ test("HTTP admin console can prefill the primary bearer token when explicitly en
517
591
  }
518
592
  });
519
593
 
594
+ test("HTTP admin console safely serializes a prefill token into its inline bootstrap", async () => {
595
+ const token = 'safe</script><script>window.injected=true</script>';
596
+ const service = {} as EngramAccessService;
597
+ const server = new EngramAccessHttpServer({
598
+ service,
599
+ port: 0,
600
+ authToken: token,
601
+ adminConsolePrefillToken: true,
602
+ });
603
+
604
+ const status = await server.start();
605
+ try {
606
+ const shell = await fetch(`http://127.0.0.1:${status.port}/engram/ui/`, {
607
+ headers: { authorization: `Bearer ${token}` },
608
+ });
609
+ assert.equal(shell.status, 200);
610
+ const shellText = await shell.text();
611
+ assert.doesNotMatch(shellText, /<script>window\.injected=true<\/script>/);
612
+ assert.match(shellText, /safe\\u003c\/script>\\u003cscript>window\.injected=true\\u003c\/script>/);
613
+ } finally {
614
+ await server.stop();
615
+ }
616
+ });
617
+
618
+ test("HTTP admin console does not disclose the primary token to a dynamic token", async () => {
619
+ const service = {} as EngramAccessService;
620
+ const server = new EngramAccessHttpServer({
621
+ service,
622
+ port: 0,
623
+ authToken: "primary-token",
624
+ authTokenEntriesGetter: () => [
625
+ {
626
+ token: "scoped-token",
627
+ capabilities: { version: 1, ops: ["support_passport_cards_list"] },
628
+ },
629
+ ],
630
+ adminConsolePrefillToken: true,
631
+ });
632
+
633
+ const status = await server.start();
634
+ try {
635
+ const shell = await fetch(`http://127.0.0.1:${status.port}/engram/ui/`, {
636
+ headers: { authorization: "Bearer scoped-token" },
637
+ });
638
+ assert.equal(shell.status, 200);
639
+ const body = await shell.text();
640
+ assert.doesNotMatch(body, /primary-token/);
641
+ assert.doesNotMatch(body, /__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__/);
642
+ } finally {
643
+ await server.stop();
644
+ }
645
+ });
646
+
520
647
  test("HTTP admin dashboard endpoints require bearer authentication and apply config patches", async () => {
521
648
  const service = {} as EngramAccessService;
522
649
  const patches: unknown[] = [];
@@ -562,27 +689,40 @@ test("HTTP admin dashboard endpoints require bearer authentication and apply con
562
689
 
563
690
  const status = await server.start();
564
691
  try {
565
- const unauthorized = await fetch(`http://127.0.0.1:${status.port}/engram/v1/admin/dashboard`);
692
+ const unauthorized = await fetch(
693
+ `http://127.0.0.1:${status.port}/engram/v1/admin/dashboard`,
694
+ );
566
695
  assert.equal(unauthorized.status, 401);
567
696
 
568
- const dashboardResponse = await fetch(`http://127.0.0.1:${status.port}/engram/v1/admin/dashboard`, {
569
- headers: { authorization: "Bearer test-token" },
570
- });
697
+ const dashboardResponse = await fetch(
698
+ `http://127.0.0.1:${status.port}/engram/v1/admin/dashboard`,
699
+ {
700
+ headers: { authorization: "Bearer test-token" },
701
+ },
702
+ );
571
703
  assert.equal(dashboardResponse.status, 200);
572
704
  assert.deepEqual(await dashboardResponse.json(), dashboard);
573
705
 
574
- const patchResponse = await fetch(`http://127.0.0.1:${status.port}/remnic/v1/admin/config`, {
575
- method: "PATCH",
576
- headers: {
577
- authorization: "Bearer test-token",
578
- "content-type": "application/json",
706
+ const patchResponse = await fetch(
707
+ `http://127.0.0.1:${status.port}/remnic/v1/admin/config`,
708
+ {
709
+ method: "PATCH",
710
+ headers: {
711
+ authorization: "Bearer test-token",
712
+ "content-type": "application/json",
713
+ },
714
+ body: JSON.stringify({
715
+ modelSource: "gateway",
716
+ gatewayAgentId: "sage-router",
717
+ }),
579
718
  },
580
- body: JSON.stringify({ modelSource: "gateway", gatewayAgentId: "sage-router" }),
581
- });
719
+ );
582
720
  assert.equal(patchResponse.status, 200);
583
- const body = await patchResponse.json() as RemnicAdminDashboardStatus;
721
+ const body = (await patchResponse.json()) as RemnicAdminDashboardStatus;
584
722
  assert.equal(body.config.restartRequired, true);
585
- assert.deepEqual(patches, [{ modelSource: "gateway", gatewayAgentId: "sage-router" }]);
723
+ assert.deepEqual(patches, [
724
+ { modelSource: "gateway", gatewayAgentId: "sage-router" },
725
+ ]);
586
726
  } finally {
587
727
  await server.stop();
588
728
  }
@@ -604,17 +744,20 @@ test("HTTP coding-context endpoint accepts projectTag shorthand", async () => {
604
744
 
605
745
  const status = await server.start();
606
746
  try {
607
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/coding-context`, {
608
- method: "POST",
609
- headers: {
610
- authorization: "Bearer test-token",
611
- "content-type": "application/json",
747
+ const response = await fetch(
748
+ `http://127.0.0.1:${status.port}/engram/v1/coding-context`,
749
+ {
750
+ method: "POST",
751
+ headers: {
752
+ authorization: "Bearer test-token",
753
+ "content-type": "application/json",
754
+ },
755
+ body: JSON.stringify({
756
+ sessionKey: "s1",
757
+ projectTag: "Acme/Webshop",
758
+ }),
612
759
  },
613
- body: JSON.stringify({
614
- sessionKey: "s1",
615
- projectTag: "Acme/Webshop",
616
- }),
617
- });
760
+ );
618
761
 
619
762
  const projectId = projectTagProjectId("Acme/Webshop");
620
763
  assert.equal(response.status, 200);
@@ -636,7 +779,10 @@ test("HTTP coding-context endpoint accepts projectTag shorthand", async () => {
636
779
  });
637
780
 
638
781
  test("HTTP contradiction scan uses writable namespace resolver", async () => {
639
- const resolverCalls: Array<{ namespace: string | undefined; principal: string | undefined }> = [];
782
+ const resolverCalls: Array<{
783
+ namespace: string | undefined;
784
+ principal: string | undefined;
785
+ }> = [];
640
786
  const storage = {
641
787
  readAllMemories: async () => [],
642
788
  } as unknown as StorageManager;
@@ -656,9 +802,14 @@ test("HTTP contradiction scan uses writable namespace resolver", async () => {
656
802
  localLlmRef: null,
657
803
  fallbackLlmRef: null,
658
804
  getReadableStorageForNamespace: async () => {
659
- throw new Error("readable resolver must not authorize contradiction scan writes");
805
+ throw new Error(
806
+ "readable resolver must not authorize contradiction scan writes",
807
+ );
660
808
  },
661
- getWritableStorageForNamespace: async (namespace: string | undefined, principal: string | undefined) => {
809
+ getWritableStorageForNamespace: async (
810
+ namespace: string | undefined,
811
+ principal: string | undefined,
812
+ ) => {
662
813
  resolverCalls.push({ namespace, principal });
663
814
  return { namespace: namespace ?? "default", storage };
664
815
  },
@@ -673,19 +824,24 @@ test("HTTP contradiction scan uses writable namespace resolver", async () => {
673
824
 
674
825
  const status = await server.start();
675
826
  try {
676
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/contradiction-scan`, {
677
- method: "POST",
678
- headers: {
679
- authorization: "Bearer test-token",
680
- "content-type": "application/json",
827
+ const response = await fetch(
828
+ `http://127.0.0.1:${status.port}/engram/v1/contradiction-scan`,
829
+ {
830
+ method: "POST",
831
+ headers: {
832
+ authorization: "Bearer test-token",
833
+ "content-type": "application/json",
834
+ },
835
+ body: JSON.stringify({ namespace: "team" }),
681
836
  },
682
- body: JSON.stringify({ namespace: "team" }),
683
- });
684
- const body = await response.json() as { scanned?: number };
837
+ );
838
+ const body = (await response.json()) as { scanned?: number };
685
839
 
686
840
  assert.equal(response.status, 200);
687
841
  assert.equal(body.scanned, 0);
688
- assert.deepEqual(resolverCalls, [{ namespace: "team", principal: "writer" }]);
842
+ assert.deepEqual(resolverCalls, [
843
+ { namespace: "team", principal: "writer" },
844
+ ]);
689
845
  } finally {
690
846
  await server.stop();
691
847
  }
@@ -693,7 +849,10 @@ test("HTTP contradiction scan uses writable namespace resolver", async () => {
693
849
 
694
850
  test("HTTP review list uses readable namespace resolver", async () => {
695
851
  const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-http-review-list-"));
696
- const resolverCalls: Array<{ namespace: string | undefined; principal: string | undefined }> = [];
852
+ const resolverCalls: Array<{
853
+ namespace: string | undefined;
854
+ principal: string | undefined;
855
+ }> = [];
697
856
  const service = {
698
857
  configRef: parseConfig({
699
858
  memoryDir: dir,
@@ -701,9 +860,14 @@ test("HTTP review list uses readable namespace resolver", async () => {
701
860
  defaultNamespace: "default",
702
861
  }),
703
862
  memoryDir: dir,
704
- getReadableStorageForNamespace: async (namespace: string | undefined, principal: string | undefined) => {
863
+ getReadableStorageForNamespace: async (
864
+ namespace: string | undefined,
865
+ principal: string | undefined,
866
+ ) => {
705
867
  resolverCalls.push({ namespace, principal });
706
- throw new EngramAccessInputError(`namespace is not readable: ${namespace}`);
868
+ throw new EngramAccessInputError(
869
+ `namespace is not readable: ${namespace}`,
870
+ );
707
871
  },
708
872
  } as unknown as EngramAccessService;
709
873
  const server = new EngramAccessHttpServer({
@@ -716,14 +880,19 @@ test("HTTP review list uses readable namespace resolver", async () => {
716
880
 
717
881
  const status = await server.start();
718
882
  try {
719
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/review/contradictions?namespace=team`, {
720
- headers: { authorization: "Bearer test-token" },
721
- });
722
- const body = await response.json() as { error?: string };
883
+ const response = await fetch(
884
+ `http://127.0.0.1:${status.port}/engram/v1/review/contradictions?namespace=team`,
885
+ {
886
+ headers: { authorization: "Bearer test-token" },
887
+ },
888
+ );
889
+ const body = (await response.json()) as { error?: string };
723
890
 
724
891
  assert.equal(response.status, 400);
725
892
  assert.match(body.error ?? "", /namespace is not readable: team/);
726
- assert.deepEqual(resolverCalls, [{ namespace: "team", principal: "reader" }]);
893
+ assert.deepEqual(resolverCalls, [
894
+ { namespace: "team", principal: "reader" },
895
+ ]);
727
896
  } finally {
728
897
  await server.stop();
729
898
  await rm(dir, { recursive: true, force: true });
@@ -731,7 +900,9 @@ test("HTTP review list uses readable namespace resolver", async () => {
731
900
  });
732
901
 
733
902
  test("HTTP default review list includes legacy unscoped pairs without mutating storage", async () => {
734
- const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-http-review-list-default-"));
903
+ const dir = await mkdtemp(
904
+ path.join(os.tmpdir(), "remnic-http-review-list-default-"),
905
+ );
735
906
  const legacy = writePair(dir, {
736
907
  memoryIds: ["legacy-a", "legacy-b"],
737
908
  verdict: "contradicts",
@@ -739,7 +910,10 @@ test("HTTP default review list includes legacy unscoped pairs without mutating s
739
910
  confidence: 0.9,
740
911
  detectedAt: new Date().toISOString(),
741
912
  });
742
- const resolverCalls: Array<{ namespace: string | undefined; principal: string | undefined }> = [];
913
+ const resolverCalls: Array<{
914
+ namespace: string | undefined;
915
+ principal: string | undefined;
916
+ }> = [];
743
917
  const storage = {
744
918
  readAllMemories: async () => [],
745
919
  } as unknown as StorageManager;
@@ -750,7 +924,10 @@ test("HTTP default review list includes legacy unscoped pairs without mutating s
750
924
  defaultNamespace: "default",
751
925
  }),
752
926
  memoryDir: dir,
753
- getReadableStorageForNamespace: async (namespace: string | undefined, principal: string | undefined) => {
927
+ getReadableStorageForNamespace: async (
928
+ namespace: string | undefined,
929
+ principal: string | undefined,
930
+ ) => {
754
931
  resolverCalls.push({ namespace, principal });
755
932
  return { namespace: namespace ?? "default", storage };
756
933
  },
@@ -765,10 +942,13 @@ test("HTTP default review list includes legacy unscoped pairs without mutating s
765
942
 
766
943
  const status = await server.start();
767
944
  try {
768
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/review/contradictions`, {
769
- headers: { authorization: "Bearer test-token" },
770
- });
771
- const body = await response.json() as {
945
+ const response = await fetch(
946
+ `http://127.0.0.1:${status.port}/engram/v1/review/contradictions`,
947
+ {
948
+ headers: { authorization: "Bearer test-token" },
949
+ },
950
+ );
951
+ const body = (await response.json()) as {
772
952
  total?: number;
773
953
  pairs?: Array<{ pairId?: string; namespace?: string }>;
774
954
  };
@@ -777,7 +957,9 @@ test("HTTP default review list includes legacy unscoped pairs without mutating s
777
957
  assert.equal(body.pairs?.[0]?.pairId, legacy.pairId);
778
958
  assert.equal(body.pairs?.[0]?.namespace, undefined);
779
959
  assert.equal(readPair(dir, legacy.pairId)?.namespace, undefined);
780
- assert.deepEqual(resolverCalls, [{ namespace: undefined, principal: "reader" }]);
960
+ assert.deepEqual(resolverCalls, [
961
+ { namespace: undefined, principal: "reader" },
962
+ ]);
781
963
  } finally {
782
964
  await server.stop();
783
965
  await rm(dir, { recursive: true, force: true });
@@ -794,7 +976,10 @@ test("HTTP review show hides namespace denial as pair_not_found", async () => {
794
976
  confidence: 0.9,
795
977
  detectedAt: new Date().toISOString(),
796
978
  });
797
- const resolverCalls: Array<{ namespace: string | undefined; principal: string | undefined }> = [];
979
+ const resolverCalls: Array<{
980
+ namespace: string | undefined;
981
+ principal: string | undefined;
982
+ }> = [];
798
983
  const service = {
799
984
  configRef: parseConfig({
800
985
  memoryDir: dir,
@@ -802,9 +987,14 @@ test("HTTP review show hides namespace denial as pair_not_found", async () => {
802
987
  defaultNamespace: "default",
803
988
  }),
804
989
  memoryDir: dir,
805
- getReadableStorageForNamespace: async (namespace: string | undefined, principal: string | undefined) => {
990
+ getReadableStorageForNamespace: async (
991
+ namespace: string | undefined,
992
+ principal: string | undefined,
993
+ ) => {
806
994
  resolverCalls.push({ namespace, principal });
807
- throw new EngramAccessInputError(`namespace is not readable: ${namespace}`);
995
+ throw new EngramAccessInputError(
996
+ `namespace is not readable: ${namespace}`,
997
+ );
808
998
  },
809
999
  } as unknown as EngramAccessService;
810
1000
  const server = new EngramAccessHttpServer({
@@ -817,14 +1007,19 @@ test("HTTP review show hides namespace denial as pair_not_found", async () => {
817
1007
 
818
1008
  const status = await server.start();
819
1009
  try {
820
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/review/contradictions/${pair.pairId}`, {
821
- headers: { authorization: "Bearer test-token" },
822
- });
823
- const body = await response.json() as { error?: string };
1010
+ const response = await fetch(
1011
+ `http://127.0.0.1:${status.port}/engram/v1/review/contradictions/${pair.pairId}`,
1012
+ {
1013
+ headers: { authorization: "Bearer test-token" },
1014
+ },
1015
+ );
1016
+ const body = (await response.json()) as { error?: string };
824
1017
 
825
1018
  assert.equal(response.status, 404);
826
1019
  assert.equal(body.error, "pair_not_found");
827
- assert.deepEqual(resolverCalls, [{ namespace: "team", principal: "reader" }]);
1020
+ assert.deepEqual(resolverCalls, [
1021
+ { namespace: "team", principal: "reader" },
1022
+ ]);
828
1023
  } finally {
829
1024
  await server.stop();
830
1025
  await rm(dir, { recursive: true, force: true });
@@ -877,7 +1072,7 @@ test("HTTP offline snapshot forwards namespace and transfer options", async () =
877
1072
  `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/snapshot?namespace=team&include_transcripts=false&content=false`,
878
1073
  { headers: { authorization: "Bearer test-token" } },
879
1074
  );
880
- const body = await response.json() as {
1075
+ const body = (await response.json()) as {
881
1076
  namespace?: string;
882
1077
  includeTranscripts?: boolean;
883
1078
  files?: unknown[];
@@ -888,13 +1083,17 @@ test("HTTP offline snapshot forwards namespace and transfer options", async () =
888
1083
  assert.equal(body.namespace, "team");
889
1084
  assert.equal(body.includeTranscripts, false);
890
1085
  assert.deepEqual(body.files, []);
891
- assert.deepEqual(body.deletions, [{ path: "facts/deleted.md", mtimeMs: 1235 }]);
892
- assert.deepEqual(calls, [{
893
- namespace: "team",
894
- principal: "reader",
895
- includeTranscripts: false,
896
- includeContent: false,
897
- }]);
1086
+ assert.deepEqual(body.deletions, [
1087
+ { path: "facts/deleted.md", mtimeMs: 1235 },
1088
+ ]);
1089
+ assert.deepEqual(calls, [
1090
+ {
1091
+ namespace: "team",
1092
+ principal: "reader",
1093
+ includeTranscripts: false,
1094
+ includeContent: false,
1095
+ },
1096
+ ]);
898
1097
  } finally {
899
1098
  await server.stop();
900
1099
  }
@@ -947,18 +1146,22 @@ test("HTTP offline snapshot accepts gzipped fast-base bodies", async () => {
947
1146
 
948
1147
  const status = await server.start();
949
1148
  try {
950
- const body = gzipSync(JSON.stringify({
951
- namespace: "team",
952
- includeTranscripts: false,
953
- includeContent: false,
954
- baseCapturedAt: "2026-05-20T00:00:00.000Z",
955
- baseFiles: [{
956
- path: "facts/a.md",
957
- sha256: "a".repeat(64),
958
- bytes: 12,
959
- mtimeMs: 1234,
960
- }],
961
- }));
1149
+ const body = gzipSync(
1150
+ JSON.stringify({
1151
+ namespace: "team",
1152
+ includeTranscripts: false,
1153
+ includeContent: false,
1154
+ baseCapturedAt: "2026-05-20T00:00:00.000Z",
1155
+ baseFiles: [
1156
+ {
1157
+ path: "facts/a.md",
1158
+ sha256: "a".repeat(64),
1159
+ bytes: 12,
1160
+ mtimeMs: 1234,
1161
+ },
1162
+ ],
1163
+ }),
1164
+ );
962
1165
  const response = await fetch(
963
1166
  `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/snapshot`,
964
1167
  {
@@ -971,7 +1174,7 @@ test("HTTP offline snapshot accepts gzipped fast-base bodies", async () => {
971
1174
  body,
972
1175
  },
973
1176
  );
974
- const responseBody = await response.json() as { namespace?: string };
1177
+ const responseBody = (await response.json()) as { namespace?: string };
975
1178
 
976
1179
  assert.equal(response.status, 200);
977
1180
  assert.equal(responseBody.namespace, "team");
@@ -983,12 +1186,14 @@ test("HTTP offline snapshot accepts gzipped fast-base bodies", async () => {
983
1186
  principal: "reader",
984
1187
  includeTranscripts: false,
985
1188
  includeContent: false,
986
- baseFiles: [{
987
- path: "facts/a.md",
988
- sha256: "a".repeat(64),
989
- bytes: 12,
990
- mtimeMs: 1234,
991
- }],
1189
+ baseFiles: [
1190
+ {
1191
+ path: "facts/a.md",
1192
+ sha256: "a".repeat(64),
1193
+ bytes: 12,
1194
+ mtimeMs: 1234,
1195
+ },
1196
+ ],
992
1197
  baseCapturedAt: new Date("2026-05-20T00:00:00.000Z"),
993
1198
  });
994
1199
  } finally {
@@ -1055,18 +1260,25 @@ test("HTTP offline snapshot stream emits metadata records as NDJSON", async () =
1055
1260
  .map((line) => JSON.parse(line) as Record<string, unknown>);
1056
1261
 
1057
1262
  assert.equal(response.status, 200);
1058
- assert.equal(response.headers.get("content-type"), "application/x-ndjson; charset=utf-8");
1263
+ assert.equal(
1264
+ response.headers.get("content-type"),
1265
+ "application/x-ndjson; charset=utf-8",
1266
+ );
1059
1267
  assert.equal(lines[0]?.type, "snapshot");
1060
1268
  assert.equal(lines[0]?.namespace, "team");
1061
- assert.deepEqual(lines[0]?.deletions, [{ path: "facts/deleted.md", mtimeMs: 1235 }]);
1269
+ assert.deepEqual(lines[0]?.deletions, [
1270
+ { path: "facts/deleted.md", mtimeMs: 1235 },
1271
+ ]);
1062
1272
  assert.equal(lines[1]?.type, "file");
1063
1273
  assert.deepEqual((lines[1]?.file as { path?: string }).path, "facts/a.md");
1064
- assert.deepEqual(calls, [{
1065
- namespace: "team",
1066
- principal: "reader",
1067
- includeTranscripts: false,
1068
- includeContent: false,
1069
- }]);
1274
+ assert.deepEqual(calls, [
1275
+ {
1276
+ namespace: "team",
1277
+ principal: "reader",
1278
+ includeTranscripts: false,
1279
+ includeContent: false,
1280
+ },
1281
+ ]);
1070
1282
  } finally {
1071
1283
  await server.stop();
1072
1284
  }
@@ -1102,36 +1314,66 @@ test("HTTP offline manifest stream preserves snapshot-stream auth and emits body
1102
1314
  port: 0,
1103
1315
  principal: "reader",
1104
1316
  authTokenEntriesGetter: () => [
1105
- { token: "wrong-op", capabilities: { version: 1, ops: ["offline_sync_snapshot"] } },
1317
+ {
1318
+ token: "wrong-op",
1319
+ capabilities: { version: 1, ops: ["offline_sync_snapshot"] },
1320
+ },
1106
1321
  {
1107
1322
  token: "wrong-namespace",
1108
- capabilities: { version: 1, ops: ["offline_sync_snapshot_stream"], namespaces: ["other"] },
1323
+ capabilities: {
1324
+ version: 1,
1325
+ ops: ["offline_sync_snapshot_stream"],
1326
+ namespaces: ["other"],
1327
+ },
1109
1328
  },
1110
1329
  {
1111
1330
  token: "reader",
1112
- capabilities: { version: 1, ops: ["offline_sync_snapshot_stream"], namespaces: ["team"] },
1331
+ capabilities: {
1332
+ version: 1,
1333
+ ops: ["offline_sync_snapshot_stream"],
1334
+ namespaces: ["team"],
1335
+ },
1113
1336
  },
1114
1337
  ],
1115
1338
  adminConsoleEnabled: false,
1116
1339
  });
1117
1340
  const status = await server.start();
1118
- const request = (token: string, query = "namespace=team&include_transcripts=false") =>
1119
- fetch(`http://127.0.0.1:${status.port}/remnic/v1/offline-sync/manifest-stream?${query}`, {
1120
- headers: { authorization: `Bearer ${token}` },
1121
- });
1341
+ const request = (
1342
+ token: string,
1343
+ query = "namespace=team&include_transcripts=false",
1344
+ ) =>
1345
+ fetch(
1346
+ `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/manifest-stream?${query}`,
1347
+ {
1348
+ headers: { authorization: `Bearer ${token}` },
1349
+ },
1350
+ );
1122
1351
  try {
1123
1352
  assert.equal((await request("wrong-op")).status, 403);
1124
1353
  assert.equal((await request("wrong-namespace")).status, 403);
1125
1354
  assert.equal(calls.length, 0);
1126
- assert.equal((await request("reader", "namespace=team&include_transcripts=invalid")).status, 400);
1127
- assert.equal((await request("reader", "namespace=team&content=true")).status, 400);
1355
+ assert.equal(
1356
+ (await request("reader", "namespace=team&include_transcripts=invalid"))
1357
+ .status,
1358
+ 400,
1359
+ );
1360
+ assert.equal(
1361
+ (await request("reader", "namespace=team&content=true")).status,
1362
+ 400,
1363
+ );
1128
1364
  assert.equal(calls.length, 0);
1129
1365
 
1130
1366
  const response = await request("reader");
1131
1367
  const text = await response.text();
1132
- const lines = text.trim().split("\n").map((line) => JSON.parse(line) as Record<string, unknown>);
1368
+ const lines = text
1369
+ .trim()
1370
+ .split("\n")
1371
+ .map((line) => JSON.parse(line) as Record<string, unknown>);
1133
1372
  assert.equal(response.status, 200);
1134
- assert.equal(response.headers.get("content-type"), "application/x-ndjson; charset=utf-8");
1373
+ assert.equal(
1374
+ response.headers.get("content-type"),
1375
+ "application/x-ndjson; charset=utf-8",
1376
+ );
1135
1377
  assert.equal(lines[0]?.type, "manifest");
1136
1378
  assert.equal(lines[1]?.type, "file");
1137
1379
  assert.deepEqual(lines[1]?.file, {
@@ -1147,15 +1389,18 @@ test("HTTP offline manifest stream preserves snapshot-stream auth and emits body
1147
1389
  },
1148
1390
  });
1149
1391
  assert.doesNotMatch(text, /contentBase64|rawContent|memory body/);
1150
- assert.deepEqual({
1151
- namespace: calls[0]?.namespace,
1152
- principal: calls[0]?.principal,
1153
- includeTranscripts: calls[0]?.includeTranscripts,
1154
- }, {
1155
- namespace: "team",
1156
- principal: "reader",
1157
- includeTranscripts: false,
1158
- });
1392
+ assert.deepEqual(
1393
+ {
1394
+ namespace: calls[0]?.namespace,
1395
+ principal: calls[0]?.principal,
1396
+ includeTranscripts: calls[0]?.includeTranscripts,
1397
+ },
1398
+ {
1399
+ namespace: "team",
1400
+ principal: "reader",
1401
+ includeTranscripts: false,
1402
+ },
1403
+ );
1159
1404
  } finally {
1160
1405
  await server.stop();
1161
1406
  }
@@ -1185,7 +1430,6 @@ test("HTTP offline sync capabilities advertise manifest streaming", async () =>
1185
1430
  }
1186
1431
  });
1187
1432
 
1188
-
1189
1433
  test("HTTP offline files forwards namespace and requested paths", async () => {
1190
1434
  const calls: Array<{
1191
1435
  namespace: string | undefined;
@@ -1242,18 +1486,24 @@ test("HTTP offline files forwards namespace and requested paths", async () => {
1242
1486
  }),
1243
1487
  },
1244
1488
  );
1245
- const body = await response.json() as { namespace?: string; includeTranscripts?: boolean; files?: unknown[] };
1489
+ const body = (await response.json()) as {
1490
+ namespace?: string;
1491
+ includeTranscripts?: boolean;
1492
+ files?: unknown[];
1493
+ };
1246
1494
 
1247
1495
  assert.equal(response.status, 200);
1248
1496
  assert.equal(body.namespace, "team");
1249
1497
  assert.equal(body.includeTranscripts, false);
1250
1498
  assert.deepEqual(body.files, []);
1251
- assert.deepEqual(calls, [{
1252
- namespace: "team",
1253
- principal: "reader",
1254
- includeTranscripts: false,
1255
- paths: ["facts/a.md"],
1256
- }]);
1499
+ assert.deepEqual(calls, [
1500
+ {
1501
+ namespace: "team",
1502
+ principal: "reader",
1503
+ includeTranscripts: false,
1504
+ paths: ["facts/a.md"],
1505
+ },
1506
+ ]);
1257
1507
  } finally {
1258
1508
  await server.stop();
1259
1509
  }
@@ -1328,22 +1578,30 @@ test("HTTP offline file-content forwards range options and returns binary metada
1328
1578
 
1329
1579
  assert.equal(response.status, 200);
1330
1580
  assert.equal(body.toString("utf-8"), "hello");
1331
- assert.equal(response.headers.get("content-type"), "application/octet-stream");
1581
+ assert.equal(
1582
+ response.headers.get("content-type"),
1583
+ "application/octet-stream",
1584
+ );
1332
1585
  assert.equal(response.headers.get("x-remnic-namespace"), "team");
1333
- assert.equal(response.headers.get("x-remnic-file-path"), "artifacts%2Flarge.txt");
1586
+ assert.equal(
1587
+ response.headers.get("x-remnic-file-path"),
1588
+ "artifacts%2Flarge.txt",
1589
+ );
1334
1590
  assert.equal(response.headers.get("x-remnic-file-sha256"), "a".repeat(64));
1335
1591
  assert.equal(response.headers.get("x-remnic-file-bytes"), "12");
1336
1592
  assert.equal(response.headers.get("x-remnic-file-mtime-ms"), "1234");
1337
1593
  assert.equal(response.headers.get("x-remnic-chunk-offset"), "8");
1338
1594
  assert.equal(response.headers.get("x-remnic-chunk-bytes"), "5");
1339
- assert.deepEqual(calls, [{
1340
- namespace: "team",
1341
- principal: "reader",
1342
- includeTranscripts: false,
1343
- path: "artifacts/large.txt",
1344
- offset: 8,
1345
- length: 5,
1346
- }]);
1595
+ assert.deepEqual(calls, [
1596
+ {
1597
+ namespace: "team",
1598
+ principal: "reader",
1599
+ includeTranscripts: false,
1600
+ path: "artifacts/large.txt",
1601
+ offset: 8,
1602
+ length: 5,
1603
+ },
1604
+ ]);
1347
1605
  } finally {
1348
1606
  await server.stop();
1349
1607
  }
@@ -1433,25 +1691,31 @@ test("HTTP offline apply-file-content forwards binary chunks and metadata", asyn
1433
1691
  body: Buffer.from("hello"),
1434
1692
  },
1435
1693
  );
1436
- const body = await response.json() as { namespace?: string; applied?: boolean; chunkBytes?: number };
1694
+ const body = (await response.json()) as {
1695
+ namespace?: string;
1696
+ applied?: boolean;
1697
+ chunkBytes?: number;
1698
+ };
1437
1699
 
1438
1700
  assert.equal(response.status, 200);
1439
1701
  assert.equal(body.namespace, "team");
1440
1702
  assert.equal(body.applied, true);
1441
1703
  assert.equal(body.chunkBytes, 5);
1442
- assert.deepEqual(calls, [{
1443
- namespace: "team",
1444
- principal: "writer",
1445
- includeTranscripts: false,
1446
- sourceId: "laptop:test",
1447
- path: "state/lcm.sqlite",
1448
- sha256: "b".repeat(64),
1449
- bytes: 5,
1450
- mtimeMs: 1234,
1451
- offset: 8,
1452
- baseSha256: "a".repeat(64),
1453
- content: "hello",
1454
- }]);
1704
+ assert.deepEqual(calls, [
1705
+ {
1706
+ namespace: "team",
1707
+ principal: "writer",
1708
+ includeTranscripts: false,
1709
+ sourceId: "laptop:test",
1710
+ path: "state/lcm.sqlite",
1711
+ sha256: "b".repeat(64),
1712
+ bytes: 5,
1713
+ mtimeMs: 1234,
1714
+ offset: 8,
1715
+ baseSha256: "a".repeat(64),
1716
+ content: "hello",
1717
+ },
1718
+ ]);
1455
1719
  } finally {
1456
1720
  await server.stop();
1457
1721
  }
@@ -1541,7 +1805,12 @@ test("HTTP offline snapshot accepts baseline metadata for fast sync", async () =
1541
1805
  includeTranscripts?: boolean;
1542
1806
  includeContent?: boolean;
1543
1807
  baseCapturedAt?: Date;
1544
- baseFiles?: Array<{ path: string; sha256: string; bytes: number; mtimeMs: number }>;
1808
+ baseFiles?: Array<{
1809
+ path: string;
1810
+ sha256: string;
1811
+ bytes: number;
1812
+ mtimeMs: number;
1813
+ }>;
1545
1814
  }) => {
1546
1815
  calls.push({
1547
1816
  namespace: options.namespace,
@@ -1585,28 +1854,35 @@ test("HTTP offline snapshot accepts baseline metadata for fast sync", async () =
1585
1854
  includeTranscripts: false,
1586
1855
  includeContent: false,
1587
1856
  baseCapturedAt: "2026-05-31T17:30:08.350Z",
1588
- baseFiles: [{
1589
- path: "facts/a.md",
1590
- sha256: "a".repeat(64),
1591
- bytes: 12,
1592
- mtimeMs: 1234,
1593
- }],
1857
+ baseFiles: [
1858
+ {
1859
+ path: "facts/a.md",
1860
+ sha256: "a".repeat(64),
1861
+ bytes: 12,
1862
+ mtimeMs: 1234,
1863
+ },
1864
+ ],
1594
1865
  }),
1595
1866
  },
1596
1867
  );
1597
- const body = await response.json() as { namespace?: string; files?: unknown[] };
1868
+ const body = (await response.json()) as {
1869
+ namespace?: string;
1870
+ files?: unknown[];
1871
+ };
1598
1872
 
1599
1873
  assert.equal(response.status, 200);
1600
1874
  assert.equal(body.namespace, "team");
1601
1875
  assert.equal(body.files?.length, 1);
1602
- assert.deepEqual(calls, [{
1603
- namespace: "team",
1604
- principal: "reader",
1605
- includeTranscripts: false,
1606
- includeContent: false,
1607
- baseCapturedAt: "2026-05-31T17:30:08.350Z",
1608
- baseFileCount: 1,
1609
- }]);
1876
+ assert.deepEqual(calls, [
1877
+ {
1878
+ namespace: "team",
1879
+ principal: "reader",
1880
+ includeTranscripts: false,
1881
+ includeContent: false,
1882
+ baseCapturedAt: "2026-05-31T17:30:08.350Z",
1883
+ baseFileCount: 1,
1884
+ },
1885
+ ]);
1610
1886
  } finally {
1611
1887
  await server.stop();
1612
1888
  }
@@ -1639,16 +1915,21 @@ test("HTTP offline snapshot rejects unsafe baseline paths as validation errors",
1639
1915
  "content-type": "application/json",
1640
1916
  },
1641
1917
  body: JSON.stringify({
1642
- baseFiles: [{
1643
- path: "../outside.md",
1644
- sha256: "a".repeat(64),
1645
- bytes: 12,
1646
- mtimeMs: 1234,
1647
- }],
1918
+ baseFiles: [
1919
+ {
1920
+ path: "../outside.md",
1921
+ sha256: "a".repeat(64),
1922
+ bytes: 12,
1923
+ mtimeMs: 1234,
1924
+ },
1925
+ ],
1648
1926
  }),
1649
1927
  },
1650
1928
  );
1651
- const body = await response.json() as { code?: string; details?: Array<{ field?: string; message?: string }> };
1929
+ const body = (await response.json()) as {
1930
+ code?: string;
1931
+ details?: Array<{ field?: string; message?: string }>;
1932
+ };
1652
1933
 
1653
1934
  assert.equal(response.status, 400);
1654
1935
  assert.equal(body.code, "validation_error");
@@ -1687,16 +1968,21 @@ test("HTTP offline snapshot rejects out-of-range baseline mtimes as validation e
1687
1968
  "content-type": "application/json",
1688
1969
  },
1689
1970
  body: JSON.stringify({
1690
- baseFiles: [{
1691
- path: "facts/a.md",
1692
- sha256: "a".repeat(64),
1693
- bytes: 12,
1694
- mtimeMs: OFFLINE_SYNC_MAX_MTIME_MS + 1,
1695
- }],
1971
+ baseFiles: [
1972
+ {
1973
+ path: "facts/a.md",
1974
+ sha256: "a".repeat(64),
1975
+ bytes: 12,
1976
+ mtimeMs: OFFLINE_SYNC_MAX_MTIME_MS + 1,
1977
+ },
1978
+ ],
1696
1979
  }),
1697
1980
  },
1698
1981
  );
1699
- const body = await response.json() as { code?: string; details?: Array<{ field?: string; message?: string }> };
1982
+ const body = (await response.json()) as {
1983
+ code?: string;
1984
+ details?: Array<{ field?: string; message?: string }>;
1985
+ };
1700
1986
 
1701
1987
  assert.equal(response.status, 400);
1702
1988
  assert.equal(body.code, "validation_error");
@@ -1730,10 +2016,13 @@ test("HTTP offline snapshot rejects invalid boolean query values", async () => {
1730
2016
  `http://127.0.0.1:${status.port}/engram/v1/offline-sync/snapshot?include_transcripts=maybe`,
1731
2017
  { headers: { authorization: "Bearer test-token" } },
1732
2018
  );
1733
- const body = await response.json() as { error?: string; code?: string };
2019
+ const body = (await response.json()) as { error?: string; code?: string };
1734
2020
 
1735
2021
  assert.equal(response.status, 400);
1736
- assert.match(body.error ?? "", /include_transcripts must be one of: true, false/);
2022
+ assert.match(
2023
+ body.error ?? "",
2024
+ /include_transcripts must be one of: true, false/,
2025
+ );
1737
2026
  assert.equal(body.code, "input_error");
1738
2027
  assert.equal(calls, 0);
1739
2028
  } finally {
@@ -1786,24 +2075,32 @@ test("HTTP offline apply validates and forwards changesets", async () => {
1786
2075
 
1787
2076
  const status = await server.start();
1788
2077
  try {
1789
- const response = await fetch(`http://127.0.0.1:${status.port}/remnic/v1/offline-sync/apply`, {
1790
- method: "POST",
1791
- headers: {
1792
- authorization: "Bearer test-token",
1793
- "content-type": "application/json",
2078
+ const response = await fetch(
2079
+ `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/apply`,
2080
+ {
2081
+ method: "POST",
2082
+ headers: {
2083
+ authorization: "Bearer test-token",
2084
+ "content-type": "application/json",
2085
+ },
2086
+ body: JSON.stringify({ namespace: "team", changeset }),
1794
2087
  },
1795
- body: JSON.stringify({ namespace: "team", changeset }),
1796
- });
1797
- const body = await response.json() as { namespace?: string; appliedUpserts?: number };
2088
+ );
2089
+ const body = (await response.json()) as {
2090
+ namespace?: string;
2091
+ appliedUpserts?: number;
2092
+ };
1798
2093
 
1799
2094
  assert.equal(response.status, 200);
1800
2095
  assert.equal(body.namespace, "team");
1801
2096
  assert.equal(body.appliedUpserts, 0);
1802
- assert.deepEqual(calls, [{
1803
- namespace: "team",
1804
- principal: "writer",
1805
- changeset,
1806
- }]);
2097
+ assert.deepEqual(calls, [
2098
+ {
2099
+ namespace: "team",
2100
+ principal: "writer",
2101
+ changeset,
2102
+ },
2103
+ ]);
1807
2104
  } finally {
1808
2105
  await server.stop();
1809
2106
  }
@@ -1818,17 +2115,19 @@ test("HTTP offline apply accepts bulk changesets above the generic JSON body lim
1818
2115
  createdAt: new Date("2026-05-21T00:00:00Z").toISOString(),
1819
2116
  sourceId: "laptop:test",
1820
2117
  includeTranscripts: true,
1821
- changes: [{
1822
- type: "upsert",
1823
- path: "facts/large.md",
1824
- file: {
2118
+ changes: [
2119
+ {
2120
+ type: "upsert",
1825
2121
  path: "facts/large.md",
1826
- sha256: "b".repeat(64),
1827
- bytes: 256 * 1024,
1828
- mtimeMs: 1,
1829
- contentBase64: largeContent,
2122
+ file: {
2123
+ path: "facts/large.md",
2124
+ sha256: "b".repeat(64),
2125
+ bytes: 256 * 1024,
2126
+ mtimeMs: 1,
2127
+ contentBase64: largeContent,
2128
+ },
1830
2129
  },
1831
- }],
2130
+ ],
1832
2131
  };
1833
2132
  const service = {
1834
2133
  offlineSyncApply: async () => {
@@ -1854,15 +2153,18 @@ test("HTTP offline apply accepts bulk changesets above the generic JSON body lim
1854
2153
 
1855
2154
  const status = await server.start();
1856
2155
  try {
1857
- const response = await fetch(`http://127.0.0.1:${status.port}/remnic/v1/offline-sync/apply`, {
1858
- method: "POST",
1859
- headers: {
1860
- authorization: "Bearer test-token",
1861
- "content-type": "application/json",
2156
+ const response = await fetch(
2157
+ `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/apply`,
2158
+ {
2159
+ method: "POST",
2160
+ headers: {
2161
+ authorization: "Bearer test-token",
2162
+ "content-type": "application/json",
2163
+ },
2164
+ body: JSON.stringify({ namespace: "team", changeset }),
1862
2165
  },
1863
- body: JSON.stringify({ namespace: "team", changeset }),
1864
- });
1865
- const body = await response.json() as { appliedUpserts?: number };
2166
+ );
2167
+ const body = (await response.json()) as { appliedUpserts?: number };
1866
2168
 
1867
2169
  assert.equal(response.status, 200);
1868
2170
  assert.equal(body.appliedUpserts, 1);
@@ -1890,30 +2192,42 @@ test("HTTP offline apply requires a changeset", async () => {
1890
2192
 
1891
2193
  const status = await server.start();
1892
2194
  try {
1893
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/offline-sync/apply`, {
1894
- method: "POST",
1895
- headers: {
1896
- authorization: "Bearer test-token",
1897
- "content-type": "application/json",
2195
+ const response = await fetch(
2196
+ `http://127.0.0.1:${status.port}/engram/v1/offline-sync/apply`,
2197
+ {
2198
+ method: "POST",
2199
+ headers: {
2200
+ authorization: "Bearer test-token",
2201
+ "content-type": "application/json",
2202
+ },
2203
+ body: JSON.stringify({ namespace: "team" }),
1898
2204
  },
1899
- body: JSON.stringify({ namespace: "team" }),
1900
- });
1901
- const body = await response.json() as { code?: string; details?: Array<{ field?: string; message?: string }> };
2205
+ );
2206
+ const body = (await response.json()) as {
2207
+ code?: string;
2208
+ details?: Array<{ field?: string; message?: string }>;
2209
+ };
1902
2210
 
1903
2211
  assert.equal(response.status, 400);
1904
2212
  assert.equal(body.code, "validation_error");
1905
2213
  assert.equal(body.details?.[0]?.field, "changeset");
1906
2214
  assert.equal(calls, 0);
1907
2215
 
1908
- const nullResponse = await fetch(`http://127.0.0.1:${status.port}/engram/v1/offline-sync/apply`, {
1909
- method: "POST",
1910
- headers: {
1911
- authorization: "Bearer test-token",
1912
- "content-type": "application/json",
2216
+ const nullResponse = await fetch(
2217
+ `http://127.0.0.1:${status.port}/engram/v1/offline-sync/apply`,
2218
+ {
2219
+ method: "POST",
2220
+ headers: {
2221
+ authorization: "Bearer test-token",
2222
+ "content-type": "application/json",
2223
+ },
2224
+ body: JSON.stringify({ namespace: "team", changeset: null }),
1913
2225
  },
1914
- body: JSON.stringify({ namespace: "team", changeset: null }),
1915
- });
1916
- const nullBody = await nullResponse.json() as { code?: string; details?: Array<{ field?: string; message?: string }> };
2226
+ );
2227
+ const nullBody = (await nullResponse.json()) as {
2228
+ code?: string;
2229
+ details?: Array<{ field?: string; message?: string }>;
2230
+ };
1917
2231
 
1918
2232
  assert.equal(nullResponse.status, 400);
1919
2233
  assert.equal(nullBody.code, "validation_error");
@@ -1927,7 +2241,12 @@ test("HTTP offline apply requires a changeset", async () => {
1927
2241
 
1928
2242
  test("HTTP server rejects invalid resourceMetadataUrl at construction", () => {
1929
2243
  const service = {} as EngramAccessService;
1930
- for (const bad of ["not a url", "ftp://example.com/oauth", "//relative/path", ""]) {
2244
+ for (const bad of [
2245
+ "not a url",
2246
+ "ftp://example.com/oauth",
2247
+ "//relative/path",
2248
+ "",
2249
+ ]) {
1931
2250
  assert.throws(
1932
2251
  () =>
1933
2252
  new EngramAccessHttpServer({
@@ -1959,7 +2278,8 @@ test("HTTP server rejects invalid resourceMetadataUrl at construction", () => {
1959
2278
 
1960
2279
  test("HTTP 401 www-authenticate carries resource_metadata exactly when configured", async () => {
1961
2280
  const service = {} as EngramAccessService;
1962
- const metadataUrl = "https://example.test/.well-known/oauth-protected-resource";
2281
+ const metadataUrl =
2282
+ "https://example.test/.well-known/oauth-protected-resource";
1963
2283
  const server = new EngramAccessHttpServer({
1964
2284
  service,
1965
2285
  port: 0,
@@ -1969,7 +2289,9 @@ test("HTTP 401 www-authenticate carries resource_metadata exactly when configure
1969
2289
  });
1970
2290
  const status = await server.start();
1971
2291
  try {
1972
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
2292
+ const response = await fetch(
2293
+ `http://127.0.0.1:${status.port}/engram/v1/health`,
2294
+ );
1973
2295
  assert.equal(response.status, 401);
1974
2296
  assert.equal(
1975
2297
  response.headers.get("www-authenticate"),
@@ -1990,7 +2312,9 @@ test("HTTP 401 www-authenticate is the bare Bearer challenge when resourceMetada
1990
2312
  });
1991
2313
  const status = await server.start();
1992
2314
  try {
1993
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
2315
+ const response = await fetch(
2316
+ `http://127.0.0.1:${status.port}/engram/v1/health`,
2317
+ );
1994
2318
  assert.equal(response.status, 401);
1995
2319
  assert.equal(response.headers.get("www-authenticate"), "Bearer");
1996
2320
  } finally {
@@ -2014,12 +2338,18 @@ test("HTTP /mcp returns 405 with Allow: POST for GET and DELETE (authorized requ
2014
2338
  headers: { authorization: "Bearer test-token" },
2015
2339
  });
2016
2340
  assert.equal(response.status, 405, `${method} /mcp must be 405`);
2017
- assert.equal(response.headers.get("allow"), "POST", `${method} /mcp must advertise Allow: POST`);
2018
- const body = await response.json() as { code?: string };
2341
+ assert.equal(
2342
+ response.headers.get("allow"),
2343
+ "POST",
2344
+ `${method} /mcp must advertise Allow: POST`,
2345
+ );
2346
+ const body = (await response.json()) as { code?: string };
2019
2347
  assert.equal(body.code, "method_not_allowed");
2020
2348
  }
2021
2349
  // Unauthenticated GET /mcp still gets 401 first (auth gate beats method-conformance).
2022
- const unauth = await fetch(`http://127.0.0.1:${status.port}/mcp`, { method: "GET" });
2350
+ const unauth = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
2351
+ method: "GET",
2352
+ });
2023
2353
  assert.equal(unauth.status, 401);
2024
2354
  } finally {
2025
2355
  await server.stop();
@@ -2046,7 +2376,10 @@ test("HTTP /mcp rejects unknown MCP-Protocol-Version header with 400 JSON-RPC er
2046
2376
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }),
2047
2377
  });
2048
2378
  assert.equal(response.status, 400);
2049
- const body = await response.json() as { jsonrpc?: string; error?: { message?: string } };
2379
+ const body = (await response.json()) as {
2380
+ jsonrpc?: string;
2381
+ error?: { message?: string };
2382
+ };
2050
2383
  assert.equal(body.jsonrpc, "2.0");
2051
2384
  assert.match(body.error?.message ?? "", /unsupported MCP-Protocol-Version/);
2052
2385
 
@@ -2067,7 +2400,10 @@ test("HTTP /mcp rejects unknown MCP-Protocol-Version header with 400 JSON-RPC er
2067
2400
  // Absent header is also fine.
2068
2401
  const absent = await fetch(`http://127.0.0.1:${status.port}/mcp`, {
2069
2402
  method: "POST",
2070
- headers: { authorization: "Bearer test-token", "content-type": "application/json" },
2403
+ headers: {
2404
+ authorization: "Bearer test-token",
2405
+ "content-type": "application/json",
2406
+ },
2071
2407
  body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }),
2072
2408
  });
2073
2409
  assert.equal(absent.status, 200);
@@ -2136,7 +2472,10 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
2136
2472
  method: _req.method ?? "",
2137
2473
  authorized: ctx.authorized,
2138
2474
  });
2139
- if (new URL(_req.url ?? "/", "http://placeholder").pathname === "/probe/handled") {
2475
+ if (
2476
+ new URL(_req.url ?? "/", "http://placeholder").pathname ===
2477
+ "/probe/handled"
2478
+ ) {
2140
2479
  res.writeHead(200, { "content-type": "application/json" });
2141
2480
  res.end(JSON.stringify({ handled: true, authorized: ctx.authorized }));
2142
2481
  return true;
@@ -2147,11 +2486,20 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
2147
2486
  const status = await server.start();
2148
2487
  try {
2149
2488
  // Pre-auth, the handler sees authorized=false (no token sent).
2150
- const handled = await fetch(`http://127.0.0.1:${status.port}/probe/handled`);
2489
+ const handled = await fetch(
2490
+ `http://127.0.0.1:${status.port}/probe/handled`,
2491
+ );
2151
2492
  assert.equal(handled.status, 200);
2152
- const handledBody = await handled.json() as { handled?: boolean; authorized?: boolean };
2493
+ const handledBody = (await handled.json()) as {
2494
+ handled?: boolean;
2495
+ authorized?: boolean;
2496
+ };
2153
2497
  assert.equal(handledBody.handled, true);
2154
- assert.equal(handledBody.authorized, false, "handler must observe authorized=false pre-token");
2498
+ assert.equal(
2499
+ handledBody.authorized,
2500
+ false,
2501
+ "handler must observe authorized=false pre-token",
2502
+ );
2155
2503
 
2156
2504
  // Fall-through path: same handler returns false, request continues to normal
2157
2505
  // routing. Hit a real endpoint so we know the request reached it.
@@ -2159,9 +2507,20 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
2159
2507
  `http://127.0.0.1:${status.port}/engram/v1/health`,
2160
2508
  { headers: { authorization: "Bearer test-token" } },
2161
2509
  );
2162
- assert.equal(passthrough.status, 200, "fall-through should reach the normal health route");
2163
- const passthroughBody = await passthrough.json() as { ok?: boolean; memoryDir?: string };
2164
- assert.equal(passthroughBody.ok, true, "fall-through must return the stubbed health payload");
2510
+ assert.equal(
2511
+ passthrough.status,
2512
+ 200,
2513
+ "fall-through should reach the normal health route",
2514
+ );
2515
+ const passthroughBody = (await passthrough.json()) as {
2516
+ ok?: boolean;
2517
+ memoryDir?: string;
2518
+ };
2519
+ assert.equal(
2520
+ passthroughBody.ok,
2521
+ true,
2522
+ "fall-through must return the stubbed health payload",
2523
+ );
2165
2524
  assert.equal(passthroughBody.memoryDir, "/tmp/remnic-test");
2166
2525
 
2167
2526
  // Authorized request: handler sees ctx.authorized=true.
@@ -2170,8 +2529,12 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
2170
2529
  { headers: { authorization: "Bearer test-token" } },
2171
2530
  );
2172
2531
  assert.equal(authed.status, 200);
2173
- const authedBody = await authed.json() as { authorized?: boolean };
2174
- assert.equal(authedBody.authorized, true, "handler must observe authorized=true with valid token");
2532
+ const authedBody = (await authed.json()) as { authorized?: boolean };
2533
+ assert.equal(
2534
+ authedBody.authorized,
2535
+ true,
2536
+ "handler must observe authorized=true with valid token",
2537
+ );
2175
2538
 
2176
2539
  assert.deepEqual(seen, [
2177
2540
  { path: "/probe/handled", method: "GET", authorized: false },
@@ -2196,9 +2559,15 @@ test("HTTP externalRequestHandler errors flow through the existing error handler
2196
2559
  });
2197
2560
  const status = await server.start();
2198
2561
  try {
2199
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/health`);
2200
- assert.equal(response.status, 500, "thrown errors must produce a 500 via the existing error handler");
2201
- const body = await response.json() as { code?: string };
2562
+ const response = await fetch(
2563
+ `http://127.0.0.1:${status.port}/engram/v1/health`,
2564
+ );
2565
+ assert.equal(
2566
+ response.status,
2567
+ 500,
2568
+ "thrown errors must produce a 500 via the existing error handler",
2569
+ );
2570
+ const body = (await response.json()) as { code?: string };
2202
2571
  assert.equal(body.code, "internal_error");
2203
2572
  } finally {
2204
2573
  await server.stop();
@@ -2262,21 +2631,33 @@ test("HTTP authTokenEntriesGetter is authoritative: scope policy binds connector
2262
2631
  // Dangerous shape on purpose: BOTH getters configured, and the string
2263
2632
  // getter is a superset (extra "string_only_token"). The entries getter
2264
2633
  // must decide alone; nothing may leak into the string getter.
2265
- authTokensGetter: () => [...entries.map((entry) => entry.token), "string_only_token"],
2634
+ authTokensGetter: () => [
2635
+ ...entries.map((entry) => entry.token),
2636
+ "string_only_token",
2637
+ ],
2266
2638
  authTokenEntriesGetter: () => entries,
2267
- tokenPathPolicy: (connector, pathname) => connector !== "chatgpt" || pathname === "/mcp",
2639
+ tokenPathPolicy: (connector, pathname) =>
2640
+ connector !== "chatgpt" || pathname === "/mcp",
2268
2641
  adminConsoleEnabled: false,
2269
2642
  });
2270
2643
  const status = await server.start();
2271
2644
  const request = (token: string, path: string, method = "GET") =>
2272
2645
  fetch(`http://127.0.0.1:${status.port}${path}`, {
2273
2646
  method,
2274
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
2275
- ...(method === "POST" ? { body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }) } : {}),
2647
+ headers: {
2648
+ authorization: `Bearer ${token}`,
2649
+ "content-type": "application/json",
2650
+ },
2651
+ ...(method === "POST"
2652
+ ? { body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }) }
2653
+ : {}),
2276
2654
  });
2277
2655
  try {
2278
2656
  // Scoped chatgpt token: /mcp only; health denied.
2279
- assert.equal((await request("remnic_cg_scoped", "/mcp", "POST")).status, 200);
2657
+ assert.equal(
2658
+ (await request("remnic_cg_scoped", "/mcp", "POST")).status,
2659
+ 200,
2660
+ );
2280
2661
  assert.equal(
2281
2662
  (await request("remnic_cg_scoped", "/engram/v1/health")).status,
2282
2663
  401,
@@ -2291,10 +2672,19 @@ test("HTTP authTokenEntriesGetter is authoritative: scope policy binds connector
2291
2672
  assert.equal((await request("remnic_xx_anon", "/mcp", "POST")).status, 401);
2292
2673
  // A token present ONLY in the string getter is NOT honored: the entries
2293
2674
  // getter is authoritative and there is no fall-through.
2294
- assert.equal((await request("string_only_token", "/mcp", "POST")).status, 401);
2295
- assert.equal((await request("string_only_token", "/engram/v1/health")).status, 401);
2675
+ assert.equal(
2676
+ (await request("string_only_token", "/mcp", "POST")).status,
2677
+ 401,
2678
+ );
2679
+ assert.equal(
2680
+ (await request("string_only_token", "/engram/v1/health")).status,
2681
+ 401,
2682
+ );
2296
2683
  // Unknown tokens are rejected everywhere.
2297
- assert.equal((await request("remnic_zz_unknown", "/mcp", "POST")).status, 401);
2684
+ assert.equal(
2685
+ (await request("remnic_zz_unknown", "/mcp", "POST")).status,
2686
+ 401,
2687
+ );
2298
2688
  // Static operator token bypasses the policy entirely: /mcp AND health.
2299
2689
  assert.equal((await request("operator-token", "/mcp", "POST")).status, 200);
2300
2690
  const operatorHealth = await request("operator-token", "/engram/v1/health");
@@ -2336,7 +2726,10 @@ test("HTTP scoped-token namespace allow-list is enforced on every namespace rout
2336
2726
  // Entry-based tokens with a capabilities record. The scoped token carries
2337
2727
  // a namespaces allow-list; the operator token is explicit-unrestricted.
2338
2728
  authTokenEntriesGetter: () => [
2339
- { token: "scoped-ns-a", capabilities: { version: 1, namespaces: ["ns_a"] } },
2729
+ {
2730
+ token: "scoped-ns-a",
2731
+ capabilities: { version: 1, namespaces: ["ns_a"] },
2732
+ },
2340
2733
  { token: "operator", capabilities: { version: 1 } },
2341
2734
  ],
2342
2735
  adminConsoleEnabled: false,
@@ -2354,13 +2747,25 @@ test("HTTP scoped-token namespace allow-list is enforced on every namespace rout
2354
2747
  "scoped: omitting namespace must be rejected — server default is not in the allow-list",
2355
2748
  );
2356
2749
  assert.equal(
2357
- (await request("scoped-ns-a", "/engram/v1/memories?namespace=ns_b")).status,
2750
+ (await request("scoped-ns-a", "/engram/v1/memories?namespace=ns_b"))
2751
+ .status,
2358
2752
  403,
2359
2753
  "scoped: an unlisted namespace must be rejected",
2360
2754
  );
2361
- const okList = await request("scoped-ns-a", "/engram/v1/memories?namespace=ns_a");
2362
- assert.equal(okList.status, 200, "scoped: an allowed namespace must succeed");
2363
- assert.equal(browseCalls.at(-1)?.namespace, "ns_a", "scoped: the allowed namespace is forwarded to the service");
2755
+ const okList = await request(
2756
+ "scoped-ns-a",
2757
+ "/engram/v1/memories?namespace=ns_a",
2758
+ );
2759
+ assert.equal(
2760
+ okList.status,
2761
+ 200,
2762
+ "scoped: an allowed namespace must succeed",
2763
+ );
2764
+ assert.equal(
2765
+ browseCalls.at(-1)?.namespace,
2766
+ "ns_a",
2767
+ "scoped: the allowed namespace is forwarded to the service",
2768
+ );
2364
2769
 
2365
2770
  // ── memory_get: the other named bypass route (dispatches via the op registry) ──
2366
2771
  assert.equal(
@@ -2369,12 +2774,20 @@ test("HTTP scoped-token namespace allow-list is enforced on every namespace rout
2369
2774
  "scoped memory_get: omitting namespace must be rejected",
2370
2775
  );
2371
2776
  assert.equal(
2372
- (await request("scoped-ns-a", "/engram/v1/memories/m_1?namespace=ns_b")).status,
2777
+ (await request("scoped-ns-a", "/engram/v1/memories/m_1?namespace=ns_b"))
2778
+ .status,
2373
2779
  403,
2374
2780
  "scoped memory_get: an unlisted namespace must be rejected",
2375
2781
  );
2376
- const okGet = await request("scoped-ns-a", "/engram/v1/memories/m_1?namespace=ns_a");
2377
- assert.equal(okGet.status, 200, "scoped memory_get: an allowed namespace must succeed");
2782
+ const okGet = await request(
2783
+ "scoped-ns-a",
2784
+ "/engram/v1/memories/m_1?namespace=ns_a",
2785
+ );
2786
+ assert.equal(
2787
+ okGet.status,
2788
+ 200,
2789
+ "scoped memory_get: an allowed namespace must succeed",
2790
+ );
2378
2791
  assert.deepEqual(
2379
2792
  getCalls.at(-1),
2380
2793
  { memoryId: "m_1", namespace: "ns_a" },
@@ -2406,7 +2819,9 @@ test("HTTP scoped-token namespace allow-list gates id-loaded contradiction route
2406
2819
  // knows a pair id in a namespace outside its allow-list must NOT be able to
2407
2820
  // read (GET) or mutate (resolve) it. Fail closed (403) everywhere; the pair
2408
2821
  // in the allowed namespace is reachable.
2409
- const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-http-scoped-contradiction-"));
2822
+ const dir = await mkdtemp(
2823
+ path.join(os.tmpdir(), "remnic-http-scoped-contradiction-"),
2824
+ );
2410
2825
  const allowedPair = writePair(dir, {
2411
2826
  namespace: "ns_a",
2412
2827
  memoryIds: ["a-1", "a-2"],
@@ -2445,20 +2860,29 @@ test("HTTP scoped-token namespace allow-list gates id-loaded contradiction route
2445
2860
  service,
2446
2861
  port: 0,
2447
2862
  authTokenEntriesGetter: () => [
2448
- { token: "scoped-ns-a", capabilities: { version: 1, namespaces: ["ns_a"] } },
2863
+ {
2864
+ token: "scoped-ns-a",
2865
+ capabilities: { version: 1, namespaces: ["ns_a"] },
2866
+ },
2449
2867
  { token: "operator", capabilities: { version: 1 } },
2450
2868
  ],
2451
2869
  adminConsoleEnabled: false,
2452
2870
  });
2453
2871
  const status = await server.start();
2454
2872
  const getDetail = (token: string, pairId: string) =>
2455
- fetch(`http://127.0.0.1:${status.port}/engram/v1/review/contradictions/${pairId}`, {
2456
- headers: { authorization: `Bearer ${token}` },
2457
- });
2873
+ fetch(
2874
+ `http://127.0.0.1:${status.port}/engram/v1/review/contradictions/${pairId}`,
2875
+ {
2876
+ headers: { authorization: `Bearer ${token}` },
2877
+ },
2878
+ );
2458
2879
  const resolve = (token: string, pairId: string, verb: string) =>
2459
2880
  fetch(`http://127.0.0.1:${status.port}/engram/v1/review/resolve`, {
2460
2881
  method: "POST",
2461
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
2882
+ headers: {
2883
+ authorization: `Bearer ${token}`,
2884
+ "content-type": "application/json",
2885
+ },
2462
2886
  body: JSON.stringify({ pairId, verb }),
2463
2887
  });
2464
2888
  try {
@@ -2476,7 +2900,11 @@ test("HTTP scoped-token namespace allow-list gates id-loaded contradiction route
2476
2900
  );
2477
2901
 
2478
2902
  // ── review/resolve: scoped token, denied namespace → 403 before any mutation ──
2479
- const deniedResolve = await resolve("scoped-ns-a", deniedPair.pairId, "both-valid");
2903
+ const deniedResolve = await resolve(
2904
+ "scoped-ns-a",
2905
+ deniedPair.pairId,
2906
+ "both-valid",
2907
+ );
2480
2908
  assert.equal(
2481
2909
  deniedResolve.status,
2482
2910
  403,
@@ -2490,7 +2918,11 @@ test("HTTP scoped-token namespace allow-list gates id-loaded contradiction route
2490
2918
  );
2491
2919
 
2492
2920
  // ── review/resolve: scoped token, allowed namespace → succeeds (not 403) ──
2493
- const allowedResolve = await resolve("scoped-ns-a", allowedPair.pairId, "both-valid");
2921
+ const allowedResolve = await resolve(
2922
+ "scoped-ns-a",
2923
+ allowedPair.pairId,
2924
+ "both-valid",
2925
+ );
2494
2926
  assert.equal(
2495
2927
  allowedResolve.status,
2496
2928
  200,
@@ -2529,7 +2961,11 @@ test("HTTP-MCP tools/call enforces the token namespace allow-list (issue #1850 f
2529
2961
  // reaches the service.
2530
2962
  const capsuleCalls: Array<Record<string, unknown>> = [];
2531
2963
  const service = {
2532
- configRef: parseConfig({ memoryDir: "/tmp/remnic-mcp-ns", namespacesEnabled: true, defaultNamespace: "default" }),
2964
+ configRef: parseConfig({
2965
+ memoryDir: "/tmp/remnic-mcp-ns",
2966
+ namespacesEnabled: true,
2967
+ defaultNamespace: "default",
2968
+ }),
2533
2969
  capsuleList: async (args: Record<string, unknown>) => {
2534
2970
  capsuleCalls.push(args);
2535
2971
  return { capsules: [] };
@@ -2539,7 +2975,10 @@ test("HTTP-MCP tools/call enforces the token namespace allow-list (issue #1850 f
2539
2975
  service,
2540
2976
  port: 0,
2541
2977
  authTokenEntriesGetter: () => [
2542
- { token: "scoped-ns-a", capabilities: { version: 1, namespaces: ["ns_a"] } },
2978
+ {
2979
+ token: "scoped-ns-a",
2980
+ capabilities: { version: 1, namespaces: ["ns_a"] },
2981
+ },
2543
2982
  { token: "operator", capabilities: { version: 1 } },
2544
2983
  ],
2545
2984
  adminConsoleEnabled: false,
@@ -2548,7 +2987,10 @@ test("HTTP-MCP tools/call enforces the token namespace allow-list (issue #1850 f
2548
2987
  const mcpCall = (token: string, namespace: string) =>
2549
2988
  fetch(`http://127.0.0.1:${status.port}/mcp`, {
2550
2989
  method: "POST",
2551
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
2990
+ headers: {
2991
+ authorization: `Bearer ${token}`,
2992
+ "content-type": "application/json",
2993
+ },
2552
2994
  body: JSON.stringify({
2553
2995
  jsonrpc: "2.0",
2554
2996
  id: 1,
@@ -2563,14 +3005,30 @@ test("HTTP-MCP tools/call enforces the token namespace allow-list (issue #1850 f
2563
3005
  403,
2564
3006
  "scoped MCP: an unlisted namespace must be denied (403) before op.run",
2565
3007
  );
2566
- assert.equal(capsuleCalls.length, 0, "scoped MCP: the service must NOT be reached for a denied namespace");
3008
+ assert.equal(
3009
+ capsuleCalls.length,
3010
+ 0,
3011
+ "scoped MCP: the service must NOT be reached for a denied namespace",
3012
+ );
2567
3013
  // scoped: the allowed namespace proceeds past the gate (200, not 403).
2568
3014
  const allowed = await mcpCall("scoped-ns-a", "ns_a");
2569
- assert.notEqual(allowed.status, 403, "scoped MCP: the allowed namespace must proceed past the gate");
2570
- assert.equal(capsuleCalls.at(-1)?.namespace, "ns_a", "scoped MCP: the allowed namespace reaches the service");
3015
+ assert.notEqual(
3016
+ allowed.status,
3017
+ 403,
3018
+ "scoped MCP: the allowed namespace must proceed past the gate",
3019
+ );
3020
+ assert.equal(
3021
+ capsuleCalls.at(-1)?.namespace,
3022
+ "ns_a",
3023
+ "scoped MCP: the allowed namespace reaches the service",
3024
+ );
2571
3025
  // unrestricted: an unlisted namespace is a no-op (proceeds, not 403).
2572
3026
  const unrestricted = await mcpCall("operator", "ns_b");
2573
- assert.notEqual(unrestricted.status, 403, "unrestricted MCP: namespace scoping must be a no-op");
3027
+ assert.notEqual(
3028
+ unrestricted.status,
3029
+ 403,
3030
+ "unrestricted MCP: namespace scoping must be a no-op",
3031
+ );
2574
3032
  } finally {
2575
3033
  await server.stop();
2576
3034
  }
@@ -2583,7 +3041,11 @@ test("HTTP coding POST routes gate the body namespace through the allow-list (is
2583
3041
  // routes. 403 before op.run; the allowed namespace reaches the service.
2584
3042
  const decisionCalls: Array<Record<string, unknown>> = [];
2585
3043
  const service = {
2586
- configRef: parseConfig({ memoryDir: "/tmp/remnic-coding-ns", namespacesEnabled: true, defaultNamespace: "default" }),
3044
+ configRef: parseConfig({
3045
+ memoryDir: "/tmp/remnic-coding-ns",
3046
+ namespacesEnabled: true,
3047
+ defaultNamespace: "default",
3048
+ }),
2587
3049
  codingDecision: async (input: Record<string, unknown>) => {
2588
3050
  decisionCalls.push(input);
2589
3051
  return { subcommand: "list", records: [], count: 0 };
@@ -2593,7 +3055,10 @@ test("HTTP coding POST routes gate the body namespace through the allow-list (is
2593
3055
  service,
2594
3056
  port: 0,
2595
3057
  authTokenEntriesGetter: () => [
2596
- { token: "scoped-ns-a", capabilities: { version: 1, namespaces: ["ns_a"] } },
3058
+ {
3059
+ token: "scoped-ns-a",
3060
+ capabilities: { version: 1, namespaces: ["ns_a"] },
3061
+ },
2597
3062
  { token: "operator", capabilities: { version: 1 } },
2598
3063
  ],
2599
3064
  adminConsoleEnabled: false,
@@ -2602,8 +3067,14 @@ test("HTTP coding POST routes gate the body namespace through the allow-list (is
2602
3067
  const postDecision = (token: string, namespace: string | undefined) =>
2603
3068
  fetch(`http://127.0.0.1:${status.port}/engram/v1/coding/decisions`, {
2604
3069
  method: "POST",
2605
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
2606
- body: JSON.stringify({ subcommand: "list", ...(namespace ? { namespace } : {}) }),
3070
+ headers: {
3071
+ authorization: `Bearer ${token}`,
3072
+ "content-type": "application/json",
3073
+ },
3074
+ body: JSON.stringify({
3075
+ subcommand: "list",
3076
+ ...(namespace ? { namespace } : {}),
3077
+ }),
2607
3078
  });
2608
3079
  try {
2609
3080
  // scoped: body.namespace to an unlisted tenant → 403 before op.run.
@@ -2612,11 +3083,23 @@ test("HTTP coding POST routes gate the body namespace through the allow-list (is
2612
3083
  403,
2613
3084
  "scoped coding: body.namespace to an unlisted namespace must be denied (403)",
2614
3085
  );
2615
- assert.equal(decisionCalls.length, 0, "scoped coding: the service must NOT be reached for a denied namespace");
3086
+ assert.equal(
3087
+ decisionCalls.length,
3088
+ 0,
3089
+ "scoped coding: the service must NOT be reached for a denied namespace",
3090
+ );
2616
3091
  // scoped: the allowed body.namespace reaches the service.
2617
3092
  const allowed = await postDecision("scoped-ns-a", "ns_a");
2618
- assert.notEqual(allowed.status, 403, "scoped coding: the allowed namespace must proceed past the gate");
2619
- assert.equal(decisionCalls.at(-1)?.namespace, "ns_a", "scoped coding: the allowed namespace reaches the service");
3093
+ assert.notEqual(
3094
+ allowed.status,
3095
+ 403,
3096
+ "scoped coding: the allowed namespace must proceed past the gate",
3097
+ );
3098
+ assert.equal(
3099
+ decisionCalls.at(-1)?.namespace,
3100
+ "ns_a",
3101
+ "scoped coding: the allowed namespace reaches the service",
3102
+ );
2620
3103
  // unrestricted: any body.namespace is a no-op.
2621
3104
  assert.notEqual(
2622
3105
  (await postDecision("operator", "ns_b")).status,
@@ -2635,7 +3118,9 @@ test("Legacy undefined-namespace contradiction pair: default-allow-list token re
2635
3118
  // effective-namespace chokepoint maps undefined → default, so:
2636
3119
  // - namespaces:['default'] (== server default) ⇒ ALLOWED read + resolve;
2637
3120
  // - namespaces:['ns_a'] (not default) ⇒ DENIED.
2638
- const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-http-scoped-legacy-pair-"));
3121
+ const dir = await mkdtemp(
3122
+ path.join(os.tmpdir(), "remnic-http-scoped-legacy-pair-"),
3123
+ );
2639
3124
  const legacyPair = writePair(dir, {
2640
3125
  memoryIds: ["legacy-x", "legacy-y"],
2641
3126
  verdict: "contradicts",
@@ -2647,7 +3132,11 @@ test("Legacy undefined-namespace contradiction pair: default-allow-list token re
2647
3132
  assert.equal(readPair(dir, legacyPair.pairId)?.namespace, undefined);
2648
3133
  const storage = { dir } as unknown as StorageManager;
2649
3134
  const baseService = {
2650
- configRef: parseConfig({ memoryDir: dir, namespacesEnabled: true, defaultNamespace: "default" }),
3135
+ configRef: parseConfig({
3136
+ memoryDir: dir,
3137
+ namespacesEnabled: true,
3138
+ defaultNamespace: "default",
3139
+ }),
2651
3140
  memoryDir: dir,
2652
3141
  storageRef: storage,
2653
3142
  getReadableStorageForNamespace: async (namespace: string | undefined) => ({
@@ -2664,7 +3153,12 @@ test("Legacy undefined-namespace contradiction pair: default-allow-list token re
2664
3153
  return new EngramAccessHttpServer({
2665
3154
  service,
2666
3155
  port: 0,
2667
- authTokenEntriesGetter: () => [{ token: scopedToken, capabilities: { version: 1, namespaces: allowList } }],
3156
+ authTokenEntriesGetter: () => [
3157
+ {
3158
+ token: scopedToken,
3159
+ capabilities: { version: 1, namespaces: allowList },
3160
+ },
3161
+ ],
2668
3162
  adminConsoleEnabled: false,
2669
3163
  });
2670
3164
  };
@@ -2677,13 +3171,27 @@ test("Legacy undefined-namespace contradiction pair: default-allow-list token re
2677
3171
  `http://127.0.0.1:${defaultStatus.port}/engram/v1/review/contradictions/${legacyPair.pairId}`,
2678
3172
  { headers: { authorization: "Bearer allow-default" } },
2679
3173
  );
2680
- assert.equal(detail.status, 200, "default-allow token: legacy undefined pair must be readable (undefined→default)");
2681
- const resolve = await fetch(`http://127.0.0.1:${defaultStatus.port}/engram/v1/review/resolve`, {
2682
- method: "POST",
2683
- headers: { authorization: "Bearer allow-default", "content-type": "application/json" },
2684
- body: JSON.stringify({ pairId: legacyPair.pairId, verb: "both-valid" }),
2685
- });
2686
- assert.equal(resolve.status, 200, "default-allow token: legacy undefined pair must be resolvable");
3174
+ assert.equal(
3175
+ detail.status,
3176
+ 200,
3177
+ "default-allow token: legacy undefined pair must be readable (undefined→default)",
3178
+ );
3179
+ const resolve = await fetch(
3180
+ `http://127.0.0.1:${defaultStatus.port}/engram/v1/review/resolve`,
3181
+ {
3182
+ method: "POST",
3183
+ headers: {
3184
+ authorization: "Bearer allow-default",
3185
+ "content-type": "application/json",
3186
+ },
3187
+ body: JSON.stringify({ pairId: legacyPair.pairId, verb: "both-valid" }),
3188
+ },
3189
+ );
3190
+ assert.equal(
3191
+ resolve.status,
3192
+ 200,
3193
+ "default-allow token: legacy undefined pair must be resolvable",
3194
+ );
2687
3195
  assert.equal(
2688
3196
  readPair(dir, legacyPair.pairId)?.resolution,
2689
3197
  "both-valid",
@@ -2718,12 +3226,25 @@ test("Legacy undefined-namespace contradiction pair: default-allow-list token re
2718
3226
  403,
2719
3227
  "non-default token: legacy undefined pair must be denied (undefined→default not in allow-list)",
2720
3228
  );
2721
- const resolve = await fetch(`http://127.0.0.1:${deniedStatus.port}/engram/v1/review/resolve`, {
2722
- method: "POST",
2723
- headers: { authorization: "Bearer allow-ns-a", "content-type": "application/json" },
2724
- body: JSON.stringify({ pairId: deniedLegacyPair.pairId, verb: "both-valid" }),
2725
- });
2726
- assert.equal(resolve.status, 403, "non-default token: legacy undefined pair must not be resolvable");
3229
+ const resolve = await fetch(
3230
+ `http://127.0.0.1:${deniedStatus.port}/engram/v1/review/resolve`,
3231
+ {
3232
+ method: "POST",
3233
+ headers: {
3234
+ authorization: "Bearer allow-ns-a",
3235
+ "content-type": "application/json",
3236
+ },
3237
+ body: JSON.stringify({
3238
+ pairId: deniedLegacyPair.pairId,
3239
+ verb: "both-valid",
3240
+ }),
3241
+ },
3242
+ );
3243
+ assert.equal(
3244
+ resolve.status,
3245
+ 403,
3246
+ "non-default token: legacy undefined pair must not be resolvable",
3247
+ );
2727
3248
  assert.notEqual(
2728
3249
  readPair(dir, deniedLegacyPair.pairId)?.resolution,
2729
3250
  "both-valid",
@@ -2744,7 +3265,10 @@ test("HTTP adapters route is op-gated: deny-all → 403, unrestricted → 200 (i
2744
3265
  // NO enforceTokenOp, so a deny-all / narrow-ops token reached it. It now
2745
3266
  // dispatches through the adapters_status op; a scoped token is rejected.
2746
3267
  const service = {
2747
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-adapters-opgate", defaultNamespace: "default" }),
3268
+ configRef: parseConfig({
3269
+ memoryDir: "/tmp/remnic-http-adapters-opgate",
3270
+ defaultNamespace: "default",
3271
+ }),
2748
3272
  } as unknown as EngramAccessService;
2749
3273
  const server = new EngramAccessHttpServer({
2750
3274
  service,
@@ -2773,9 +3297,17 @@ test("HTTP adapters route is op-gated: deny-all → 403, unrestricted → 200 (i
2773
3297
  "narrow-ops token without adapters_status must be rejected",
2774
3298
  );
2775
3299
  const ok = await request("operator", "/engram/v1/adapters");
2776
- assert.equal(ok.status, 200, "unrestricted token must reach adapter metadata");
3300
+ assert.equal(
3301
+ ok.status,
3302
+ 200,
3303
+ "unrestricted token must reach adapter metadata",
3304
+ );
2777
3305
  const body = (await ok.json()) as { adaptersEnabled?: boolean };
2778
- assert.equal(typeof body.adaptersEnabled, "boolean", "adapter metadata body shape preserved");
3306
+ assert.equal(
3307
+ typeof body.adaptersEnabled,
3308
+ "boolean",
3309
+ "adapter metadata body shape preserved",
3310
+ );
2779
3311
  } finally {
2780
3312
  await server.stop();
2781
3313
  }
@@ -2791,53 +3323,93 @@ test("HTTP chat routes gate the resumed session's STORED namespace (issue #1850
2791
3323
  // sessionBelongsToPrincipal, so the namespace gate is the ONLY gate in play).
2792
3324
  const session = await createChatSession(dir, { namespace: "ns_a" });
2793
3325
  const service = {
2794
- configRef: parseConfig({ memoryDir: dir, namespacesEnabled: true, defaultNamespace: "default" }),
3326
+ configRef: parseConfig({
3327
+ memoryDir: dir,
3328
+ namespacesEnabled: true,
3329
+ defaultNamespace: "default",
3330
+ }),
2795
3331
  memoryDir: dir,
2796
3332
  } as unknown as EngramAccessService;
2797
3333
  const server = new EngramAccessHttpServer({
2798
3334
  service,
2799
3335
  port: 0,
2800
3336
  authTokenEntriesGetter: () => [
2801
- { token: "scoped-ns-b", capabilities: { version: 1, namespaces: ["ns_b"] } },
2802
- { token: "scoped-ns-a", capabilities: { version: 1, namespaces: ["ns_a"] } },
3337
+ {
3338
+ token: "scoped-ns-b",
3339
+ capabilities: { version: 1, namespaces: ["ns_b"] },
3340
+ },
3341
+ {
3342
+ token: "scoped-ns-a",
3343
+ capabilities: { version: 1, namespaces: ["ns_a"] },
3344
+ },
2803
3345
  ],
2804
3346
  adminConsoleEnabled: false,
2805
3347
  });
2806
3348
  const status = await server.start();
2807
- const authed = (token: string) => ({ headers: { authorization: `Bearer ${token}` } });
3349
+ const authed = (token: string) => ({
3350
+ headers: { authorization: `Bearer ${token}` },
3351
+ });
2808
3352
  try {
2809
3353
  // ── POST chat/message resuming a CROSS-namespace session → 403 ──
2810
- const cross = await fetch(`http://127.0.0.1:${status.port}/engram/v1/chat/message`, {
2811
- method: "POST",
2812
- ...authed("scoped-ns-b"),
2813
- headers: { ...authed("scoped-ns-b").headers, "content-type": "application/json" },
2814
- body: JSON.stringify({ message: "hi", chatSessionId: session.id }),
2815
- });
2816
- assert.equal(cross.status, 403, "scoped-ns-b must NOT post to a session bound to ns_a");
3354
+ const cross = await fetch(
3355
+ `http://127.0.0.1:${status.port}/engram/v1/chat/message`,
3356
+ {
3357
+ method: "POST",
3358
+ ...authed("scoped-ns-b"),
3359
+ headers: {
3360
+ ...authed("scoped-ns-b").headers,
3361
+ "content-type": "application/json",
3362
+ },
3363
+ body: JSON.stringify({ message: "hi", chatSessionId: session.id }),
3364
+ },
3365
+ );
3366
+ assert.equal(
3367
+ cross.status,
3368
+ 403,
3369
+ "scoped-ns-b must NOT post to a session bound to ns_a",
3370
+ );
2817
3371
 
2818
3372
  // ── POST chat/message resuming a SAME-namespace session → passes the gate ──
2819
3373
  // (Chat is unconfigured here so the handler returns chat_disabled 404; the
2820
3374
  // point is it is NOT 403 — the namespace gate let it through.)
2821
- const same = await fetch(`http://127.0.0.1:${status.port}/engram/v1/chat/message`, {
2822
- method: "POST",
2823
- headers: { authorization: "Bearer scoped-ns-a", "content-type": "application/json" },
2824
- body: JSON.stringify({ message: "hi", chatSessionId: session.id }),
2825
- });
2826
- assert.notEqual(same.status, 403, "scoped-ns-a must pass the namespace gate for its own session");
3375
+ const same = await fetch(
3376
+ `http://127.0.0.1:${status.port}/engram/v1/chat/message`,
3377
+ {
3378
+ method: "POST",
3379
+ headers: {
3380
+ authorization: "Bearer scoped-ns-a",
3381
+ "content-type": "application/json",
3382
+ },
3383
+ body: JSON.stringify({ message: "hi", chatSessionId: session.id }),
3384
+ },
3385
+ );
3386
+ assert.notEqual(
3387
+ same.status,
3388
+ 403,
3389
+ "scoped-ns-a must pass the namespace gate for its own session",
3390
+ );
2827
3391
 
2828
3392
  // ── GET chat/events for a CROSS-namespace session → 403 ──
2829
3393
  const crossEvents = await fetch(
2830
3394
  `http://127.0.0.1:${status.port}/engram/v1/chat/events/${session.id}`,
2831
3395
  authed("scoped-ns-b"),
2832
3396
  );
2833
- assert.equal(crossEvents.status, 403, "scoped-ns-b must NOT stream a session bound to ns_a");
3397
+ assert.equal(
3398
+ crossEvents.status,
3399
+ 403,
3400
+ "scoped-ns-b must NOT stream a session bound to ns_a",
3401
+ );
2834
3402
 
2835
3403
  // ── GET chat/events for a SAME-namespace session → passes the gate ──
2836
3404
  const sameEvents = await fetch(
2837
3405
  `http://127.0.0.1:${status.port}/engram/v1/chat/events/${session.id}`,
2838
3406
  authed("scoped-ns-a"),
2839
3407
  );
2840
- assert.notEqual(sameEvents.status, 403, "scoped-ns-a must pass the namespace gate for its own session");
3408
+ assert.notEqual(
3409
+ sameEvents.status,
3410
+ 403,
3411
+ "scoped-ns-a must pass the namespace gate for its own session",
3412
+ );
2841
3413
  } finally {
2842
3414
  await server.stop();
2843
3415
  await rm(dir, { recursive: true, force: true });
@@ -2876,16 +3448,33 @@ test("HTTP per-request token capabilities never bleed across concurrent async re
2876
3448
  port: 0,
2877
3449
  authTokenEntriesGetter: () => [
2878
3450
  { token: "deny-all", capabilities: { version: 1, ops: [] } },
2879
- { token: "scoped-ns-a", capabilities: { version: 1, ops: ["memory_list"], namespaces: ["ns_a"] } },
2880
- { token: "scoped-ns-b", capabilities: { version: 1, ops: ["memory_list"], namespaces: ["ns_b"] } },
3451
+ {
3452
+ token: "scoped-ns-a",
3453
+ capabilities: {
3454
+ version: 1,
3455
+ ops: ["memory_list"],
3456
+ namespaces: ["ns_a"],
3457
+ },
3458
+ },
3459
+ {
3460
+ token: "scoped-ns-b",
3461
+ capabilities: {
3462
+ version: 1,
3463
+ ops: ["memory_list"],
3464
+ namespaces: ["ns_b"],
3465
+ },
3466
+ },
2881
3467
  ],
2882
3468
  adminConsoleEnabled: false,
2883
3469
  });
2884
3470
  const status = await server.start();
2885
3471
  const get = (token: string, namespace: string) =>
2886
- fetch(`http://127.0.0.1:${status.port}/engram/v1/memories?namespace=${namespace}`, {
2887
- headers: { authorization: `Bearer ${token}` },
2888
- });
3472
+ fetch(
3473
+ `http://127.0.0.1:${status.port}/engram/v1/memories?namespace=${namespace}`,
3474
+ {
3475
+ headers: { authorization: `Bearer ${token}` },
3476
+ },
3477
+ );
2889
3478
  try {
2890
3479
  const REPEATS = 15;
2891
3480
  const batch: Promise<Response>[] = [];
@@ -2898,26 +3487,54 @@ test("HTTP per-request token capabilities never bleed across concurrent async re
2898
3487
 
2899
3488
  // (1) Op-gate holds under concurrency: every deny-all request is 403, never
2900
3489
  // fail-open 200 (which would mean its caps bled to unrestricted).
2901
- const denyAllStatuses = responses.filter((_, i) => i % 3 === 0).map((r) => r.status);
3490
+ const denyAllStatuses = responses
3491
+ .filter((_, i) => i % 3 === 0)
3492
+ .map((r) => r.status);
2902
3493
  assert.ok(
2903
3494
  denyAllStatuses.every((s) => s === 403),
2904
3495
  `deny-all token must always be 403, got: ${JSON.stringify(denyAllStatuses)}`,
2905
3496
  );
2906
3497
 
2907
3498
  // scoped tokens reach the service (200) — op + namespace gates both pass.
2908
- const nsAStatuses = responses.filter((_, i) => i % 3 === 1).map((r) => r.status);
2909
- const nsBStatuses = responses.filter((_, i) => i % 3 === 2).map((r) => r.status);
2910
- assert.ok(nsAStatuses.every((s) => s === 200), `scoped-ns-a should all be 200: ${JSON.stringify(nsAStatuses)}`);
2911
- assert.ok(nsBStatuses.every((s) => s === 200), `scoped-ns-b should all be 200: ${JSON.stringify(nsBStatuses)}`);
3499
+ const nsAStatuses = responses
3500
+ .filter((_, i) => i % 3 === 1)
3501
+ .map((r) => r.status);
3502
+ const nsBStatuses = responses
3503
+ .filter((_, i) => i % 3 === 2)
3504
+ .map((r) => r.status);
3505
+ assert.ok(
3506
+ nsAStatuses.every((s) => s === 200),
3507
+ `scoped-ns-a should all be 200: ${JSON.stringify(nsAStatuses)}`,
3508
+ );
3509
+ assert.ok(
3510
+ nsBStatuses.every((s) => s === 200),
3511
+ `scoped-ns-b should all be 200: ${JSON.stringify(nsBStatuses)}`,
3512
+ );
2912
3513
 
2913
3514
  // (2) No cross-request bleed: the store read AFTER the async yield carried
2914
3515
  // the REQUESTING token's namespace on every service call. memoryBrowse
2915
3516
  // ran once per scoped request (2 * REPEATS).
2916
- assert.equal(observed.length, 2 * REPEATS, "memoryBrowse called once per scoped request");
2917
- const nsAObserved = observed.filter((c) => c?.namespaces?.includes("ns_a")).length;
2918
- const nsBObserved = observed.filter((c) => c?.namespaces?.includes("ns_b")).length;
2919
- assert.equal(nsAObserved, REPEATS, "every ns_a request observed its own scope — no bleed");
2920
- assert.equal(nsBObserved, REPEATS, "every ns_b request observed its own scope — no bleed");
3517
+ assert.equal(
3518
+ observed.length,
3519
+ 2 * REPEATS,
3520
+ "memoryBrowse called once per scoped request",
3521
+ );
3522
+ const nsAObserved = observed.filter((c) =>
3523
+ c?.namespaces?.includes("ns_a"),
3524
+ ).length;
3525
+ const nsBObserved = observed.filter((c) =>
3526
+ c?.namespaces?.includes("ns_b"),
3527
+ ).length;
3528
+ assert.equal(
3529
+ nsAObserved,
3530
+ REPEATS,
3531
+ "every ns_a request observed its own scope — no bleed",
3532
+ );
3533
+ assert.equal(
3534
+ nsBObserved,
3535
+ REPEATS,
3536
+ "every ns_b request observed its own scope — no bleed",
3537
+ );
2921
3538
  } finally {
2922
3539
  await server.stop();
2923
3540
  }
@@ -2955,22 +3572,39 @@ test("HTTP allow-capability is never inherited by a concurrent deny token across
2955
3572
  port: 0,
2956
3573
  authTokenEntriesGetter: () => [
2957
3574
  // ALLOW: op + namespace both permitted → 200.
2958
- { token: "allow", capabilities: { version: 1, ops: ["memory_list"], namespaces: ["ns_allow"] } },
3575
+ {
3576
+ token: "allow",
3577
+ capabilities: {
3578
+ version: 1,
3579
+ ops: ["memory_list"],
3580
+ namespaces: ["ns_allow"],
3581
+ },
3582
+ },
2959
3583
  // DENY (op-level): empty ops → 403 at the op-gate.
2960
3584
  { token: "deny-all", capabilities: { version: 1, ops: [] } },
2961
3585
  // DENY (namespace-level): op permitted but scoped to a DIFFERENT
2962
3586
  // namespace ("ns_other") than the request ("ns_allow") → 403 at the
2963
3587
  // namespace-gate. The ONLY way this becomes 200 is by inheriting the
2964
3588
  // allow token's ns_allow cap — exactly the bleed run() prevents.
2965
- { token: "deny-ns", capabilities: { version: 1, ops: ["memory_list"], namespaces: ["ns_other"] } },
3589
+ {
3590
+ token: "deny-ns",
3591
+ capabilities: {
3592
+ version: 1,
3593
+ ops: ["memory_list"],
3594
+ namespaces: ["ns_other"],
3595
+ },
3596
+ },
2966
3597
  ],
2967
3598
  adminConsoleEnabled: false,
2968
3599
  });
2969
3600
  const status = await server.start();
2970
3601
  const get = (token: string, namespace: string) =>
2971
- fetch(`http://127.0.0.1:${status.port}/engram/v1/memories?namespace=${namespace}`, {
2972
- headers: { authorization: `Bearer ${token}` },
2973
- });
3602
+ fetch(
3603
+ `http://127.0.0.1:${status.port}/engram/v1/memories?namespace=${namespace}`,
3604
+ {
3605
+ headers: { authorization: `Bearer ${token}` },
3606
+ },
3607
+ );
2974
3608
  try {
2975
3609
  const REPEATS = 15;
2976
3610
  const batch: Promise<Response>[] = [];
@@ -2980,13 +3614,22 @@ test("HTTP allow-capability is never inherited by a concurrent deny token across
2980
3614
  batch.push(get("deny-ns", "ns_allow")); // 3i+2 → 403 (namespace-gate)
2981
3615
  }
2982
3616
  const responses = await Promise.all(batch);
2983
- const allowStatuses = responses.filter((_, i) => i % 3 === 0).map((r) => r.status);
2984
- const denyAllStatuses = responses.filter((_, i) => i % 3 === 1).map((r) => r.status);
2985
- const denyNsStatuses = responses.filter((_, i) => i % 3 === 2).map((r) => r.status);
3617
+ const allowStatuses = responses
3618
+ .filter((_, i) => i % 3 === 0)
3619
+ .map((r) => r.status);
3620
+ const denyAllStatuses = responses
3621
+ .filter((_, i) => i % 3 === 1)
3622
+ .map((r) => r.status);
3623
+ const denyNsStatuses = responses
3624
+ .filter((_, i) => i % 3 === 2)
3625
+ .map((r) => r.status);
2986
3626
 
2987
3627
  // ALLOW requests succeed (200) — the caps DID resolve correctly and the
2988
3628
  // service read happened under the allow scope.
2989
- assert.ok(allowStatuses.every((s) => s === 200), `allow token should all be 200: ${JSON.stringify(allowStatuses)}`);
3629
+ assert.ok(
3630
+ allowStatuses.every((s) => s === 200),
3631
+ `allow token should all be 200: ${JSON.stringify(allowStatuses)}`,
3632
+ );
2990
3633
 
2991
3634
  // NON-INHERITANCE: in the SAME interleaved batch where allow got 200, every
2992
3635
  // deny request stayed 403 — neither deny token ever inherited the allow
@@ -3006,7 +3649,11 @@ test("HTTP allow-capability is never inherited by a concurrent deny token across
3006
3649
 
3007
3650
  // Only allow requests reached the service; the store read after the async
3008
3651
  // yield carried the allow token's namespace every time — zero deny bleed.
3009
- assert.equal(observed.length, REPEATS, "only allow requests reached the service");
3652
+ assert.equal(
3653
+ observed.length,
3654
+ REPEATS,
3655
+ "only allow requests reached the service",
3656
+ );
3010
3657
  assert.ok(
3011
3658
  observed.every((c) => c?.namespaces?.includes("ns_allow")),
3012
3659
  "every service read carried the allow token's namespace — no deny bleed",
@@ -3033,7 +3680,9 @@ test("HTTP chat/message gates the NEW session's namespace — scoped token canno
3033
3680
  chat: { ...DEFAULT_CHAT_CONFIG, enabled: true },
3034
3681
  }),
3035
3682
  memoryDir: dir,
3036
- fallbackLlmRef: { chatCompletion: async () => ({ content: "stub reply" }) },
3683
+ fallbackLlmRef: {
3684
+ chatCompletion: async () => ({ content: "stub reply" }),
3685
+ },
3037
3686
  localLlmRef: null,
3038
3687
  memoryGet: () => Promise.resolve(null),
3039
3688
  memoryTimeline: () => Promise.resolve([]),
@@ -3050,9 +3699,23 @@ test("HTTP chat/message gates the NEW session's namespace — scoped token canno
3050
3699
  port: 0,
3051
3700
  authTokenEntriesGetter: () => [
3052
3701
  // scoped to ns_a (NOT the server default) — must be denied a new chat.
3053
- { token: "scoped-ns-a", capabilities: { version: 1, ops: ["chat_message"], namespaces: ["ns_a"] } },
3702
+ {
3703
+ token: "scoped-ns-a",
3704
+ capabilities: {
3705
+ version: 1,
3706
+ ops: ["chat_message"],
3707
+ namespaces: ["ns_a"],
3708
+ },
3709
+ },
3054
3710
  // scoped to the server default — may start a new chat there.
3055
- { token: "scoped-default", capabilities: { version: 1, ops: ["chat_message"], namespaces: ["default"] } },
3711
+ {
3712
+ token: "scoped-default",
3713
+ capabilities: {
3714
+ version: 1,
3715
+ ops: ["chat_message"],
3716
+ namespaces: ["default"],
3717
+ },
3718
+ },
3056
3719
  ],
3057
3720
  adminConsoleEnabled: false,
3058
3721
  });
@@ -3060,21 +3723,35 @@ test("HTTP chat/message gates the NEW session's namespace — scoped token canno
3060
3723
  const post = (token: string) =>
3061
3724
  fetch(`http://127.0.0.1:${status.port}/engram/v1/chat/message`, {
3062
3725
  method: "POST",
3063
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
3726
+ headers: {
3727
+ authorization: `Bearer ${token}`,
3728
+ "content-type": "application/json",
3729
+ },
3064
3730
  body: JSON.stringify({ message: "hi" }), // no chatSessionId → NEW session
3065
3731
  });
3066
3732
  try {
3067
3733
  // DISALLOWED: token scoped to ns_a; the new session would land in the
3068
3734
  // server default ("default"), which is not in ["ns_a"] → 403.
3069
3735
  const denied = await post("scoped-ns-a");
3070
- assert.equal(denied.status, 403, "scoped-to-ns_a must not start a new chat in the default namespace");
3736
+ assert.equal(
3737
+ denied.status,
3738
+ 403,
3739
+ "scoped-to-ns_a must not start a new chat in the default namespace",
3740
+ );
3071
3741
 
3072
3742
  // ALLOWED: token scoped to the server default → gate passes → chat runs
3073
3743
  // and returns 200 with a reply.
3074
3744
  const allowed = await post("scoped-default");
3075
- assert.equal(allowed.status, 200, "scoped-to-default may start a new chat in the default namespace");
3745
+ assert.equal(
3746
+ allowed.status,
3747
+ 200,
3748
+ "scoped-to-default may start a new chat in the default namespace",
3749
+ );
3076
3750
  const allowedBody = (await allowed.json()) as { reply?: string };
3077
- assert.ok(allowedBody.reply && allowedBody.reply.length > 0, "allowed new chat returns a reply");
3751
+ assert.ok(
3752
+ allowedBody.reply && allowedBody.reply.length > 0,
3753
+ "allowed new chat returns a reply",
3754
+ );
3078
3755
  } finally {
3079
3756
  await server.stop();
3080
3757
  }
@@ -3088,7 +3765,12 @@ test("HTTP recall forwards the connector from the authorized token entry", async
3088
3765
  const service = {
3089
3766
  recall: (request: EngramAccessRecallRequest) => {
3090
3767
  captured = request;
3091
- return Promise.resolve({ context: "", count: 0, memoryIds: [], results: [] });
3768
+ return Promise.resolve({
3769
+ context: "",
3770
+ count: 0,
3771
+ memoryIds: [],
3772
+ results: [],
3773
+ });
3092
3774
  },
3093
3775
  } as unknown as EngramAccessService;
3094
3776
  const server = new EngramAccessHttpServer({
@@ -3166,19 +3848,26 @@ test("PR #1852: token rotation between authorization and write does not stamp th
3166
3848
  });
3167
3849
  const status = await server.start();
3168
3850
  try {
3169
- const res = await fetch(`http://127.0.0.1:${status.port}/engram/v1/memories`, {
3170
- method: "POST",
3171
- headers: {
3172
- authorization: "Bearer race_token",
3173
- "content-type": "application/json",
3851
+ const res = await fetch(
3852
+ `http://127.0.0.1:${status.port}/engram/v1/memories`,
3853
+ {
3854
+ method: "POST",
3855
+ headers: {
3856
+ authorization: "Bearer race_token",
3857
+ "content-type": "application/json",
3858
+ },
3859
+ body: JSON.stringify({
3860
+ content: "race-test memory",
3861
+ schemaVersion: 1,
3862
+ idempotencyKey: "race-key-1852",
3863
+ }),
3174
3864
  },
3175
- body: JSON.stringify({
3176
- content: "race-test memory",
3177
- schemaVersion: 1,
3178
- idempotencyKey: "race-key-1852",
3179
- }),
3180
- });
3181
- assert.equal(res.status, 201, "memory_store must succeed with a valid entry token");
3865
+ );
3866
+ assert.equal(
3867
+ res.status,
3868
+ 201,
3869
+ "memory_store must succeed with a valid entry token",
3870
+ );
3182
3871
 
3183
3872
  // The provenance must come from the entry that AUTHORIZED the request
3184
3873
  // ("chatgpt"), not from the rotated store ("codex-cli").
@@ -3189,7 +3878,11 @@ test("PR #1852: token rotation between authorization and write does not stamp th
3189
3878
  );
3190
3879
  // The getter must have been called only once — the matched entry is
3191
3880
  // cached per-request.
3192
- assert.equal(getterCalls, 1, "authTokenEntriesGetter must be called once (cached per-request)");
3881
+ assert.equal(
3882
+ getterCalls,
3883
+ 1,
3884
+ "authTokenEntriesGetter must be called once (cached per-request)",
3885
+ );
3193
3886
  } finally {
3194
3887
  await server.stop();
3195
3888
  }
@@ -3220,23 +3913,28 @@ test("PR #1852: static operator token has no connector provenance under rotation
3220
3913
  service,
3221
3914
  port: 0,
3222
3915
  authToken: "operator-static",
3223
- authTokenEntriesGetter: () => [{ token: "operator-static", connector: "chatgpt" }],
3916
+ authTokenEntriesGetter: () => [
3917
+ { token: "operator-static", connector: "chatgpt" },
3918
+ ],
3224
3919
  adminConsoleEnabled: false,
3225
3920
  });
3226
3921
  const status = await server.start();
3227
3922
  try {
3228
- const res = await fetch(`http://127.0.0.1:${status.port}/engram/v1/memories`, {
3229
- method: "POST",
3230
- headers: {
3231
- authorization: "Bearer operator-static",
3232
- "content-type": "application/json",
3923
+ const res = await fetch(
3924
+ `http://127.0.0.1:${status.port}/engram/v1/memories`,
3925
+ {
3926
+ method: "POST",
3927
+ headers: {
3928
+ authorization: "Bearer operator-static",
3929
+ "content-type": "application/json",
3930
+ },
3931
+ body: JSON.stringify({
3932
+ content: "operator-token memory",
3933
+ schemaVersion: 1,
3934
+ idempotencyKey: "static-key-1852",
3935
+ }),
3233
3936
  },
3234
- body: JSON.stringify({
3235
- content: "operator-token memory",
3236
- schemaVersion: 1,
3237
- idempotencyKey: "static-key-1852",
3238
- }),
3239
- });
3937
+ );
3240
3938
  assert.equal(res.status, 201);
3241
3939
  assert.equal(
3242
3940
  capturedSourceConnector,
@@ -3253,8 +3951,15 @@ test("HTTP namespace/writable preflight returns the structured writability resul
3253
3951
  // for any principal; a foreign namespace with no policy is not. A rejection
3254
3952
  // is a valid 200 answer (read via `ok`), not an HTTP error.
3255
3953
  const service = {
3256
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-preflight-test", defaultNamespace: "default" }),
3257
- namespaceWritablePreflight: async ({ namespace }: { namespace?: string }) =>
3954
+ configRef: parseConfig({
3955
+ memoryDir: "/tmp/remnic-http-preflight-test",
3956
+ defaultNamespace: "default",
3957
+ }),
3958
+ namespaceWritablePreflight: async ({
3959
+ namespace,
3960
+ }: {
3961
+ namespace?: string;
3962
+ }) =>
3258
3963
  namespace && namespace !== "default"
3259
3964
  ? { ok: false as const, reason: "not_writable" as const, namespace }
3260
3965
  : { ok: true as const, namespace: "default" },
@@ -3298,7 +4003,10 @@ test("HTTP namespace/writable forwards project context to the scoped write prefl
3298
4003
  }
3299
4004
  | undefined;
3300
4005
  const service = {
3301
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-preflight-context", defaultNamespace: "default" }),
4006
+ configRef: parseConfig({
4007
+ memoryDir: "/tmp/remnic-http-preflight-context",
4008
+ defaultNamespace: "default",
4009
+ }),
3302
4010
  namespaceWritablePreflight: async (request: {
3303
4011
  namespace?: string;
3304
4012
  sessionKey?: string;
@@ -3322,7 +4030,10 @@ test("HTTP namespace/writable forwards project context to the scoped write prefl
3322
4030
  `http://127.0.0.1:${status.port}/engram/v1/namespace/writable?session=s-1&projectTag=Acme%2FWebshop&cwd=%2Ftmp%2Frepo`,
3323
4031
  { headers: { authorization: "Bearer test-token" } },
3324
4032
  );
3325
- assert.deepEqual(await response.json(), { ok: true, namespace: "derived-project-namespace" });
4033
+ assert.deepEqual(await response.json(), {
4034
+ ok: true,
4035
+ namespace: "derived-project-namespace",
4036
+ });
3326
4037
  assert.deepEqual(received, {
3327
4038
  namespace: undefined,
3328
4039
  sessionKey: "s-1",
@@ -3337,7 +4048,10 @@ test("HTTP namespace/writable forwards project context to the scoped write prefl
3337
4048
 
3338
4049
  test("HTTP namespace/writable checks token scope against the effective scoped namespace", async () => {
3339
4050
  const service = {
3340
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-preflight-effective", defaultNamespace: "default" }),
4051
+ configRef: parseConfig({
4052
+ memoryDir: "/tmp/remnic-http-preflight-effective",
4053
+ defaultNamespace: "default",
4054
+ }),
3341
4055
  namespaceWritablePreflight: async () => ({
3342
4056
  ok: true as const,
3343
4057
  namespace: "principal-project-derived",
@@ -3347,7 +4061,10 @@ test("HTTP namespace/writable checks token scope against the effective scoped na
3347
4061
  service,
3348
4062
  port: 0,
3349
4063
  authTokenEntriesGetter: () => [
3350
- { token: "default-only", capabilities: { version: 1, ops: ["observe"], namespaces: ["default"] } },
4064
+ {
4065
+ token: "default-only",
4066
+ capabilities: { version: 1, ops: ["observe"], namespaces: ["default"] },
4067
+ },
3351
4068
  ],
3352
4069
  adminConsoleEnabled: false,
3353
4070
  });
@@ -3370,16 +4087,31 @@ test("HTTP namespace/writable checks token scope against the effective scoped na
3370
4087
 
3371
4088
  test("HTTP namespace/writable preflight admits write-scoped tokens and rejects read-only ops", async () => {
3372
4089
  const service = {
3373
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-preflight-ops-test", defaultNamespace: "default" }),
3374
- namespaceWritablePreflight: async () => ({ ok: true as const, namespace: "default" }),
4090
+ configRef: parseConfig({
4091
+ memoryDir: "/tmp/remnic-http-preflight-ops-test",
4092
+ defaultNamespace: "default",
4093
+ }),
4094
+ namespaceWritablePreflight: async () => ({
4095
+ ok: true as const,
4096
+ namespace: "default",
4097
+ }),
3375
4098
  } as unknown as EngramAccessService;
3376
4099
  const server = new EngramAccessHttpServer({
3377
4100
  service,
3378
4101
  port: 0,
3379
4102
  authTokenEntriesGetter: () => [
3380
- { token: "write-scoped", capabilities: { version: 1, ops: ["observe", "memory_store"] } },
3381
- { token: "store-only", capabilities: { version: 1, ops: ["memory_store"] } },
3382
- { token: "diagnostic-only", capabilities: { version: 1, ops: ["namespace_writable"] } },
4103
+ {
4104
+ token: "write-scoped",
4105
+ capabilities: { version: 1, ops: ["observe", "memory_store"] },
4106
+ },
4107
+ {
4108
+ token: "store-only",
4109
+ capabilities: { version: 1, ops: ["memory_store"] },
4110
+ },
4111
+ {
4112
+ token: "diagnostic-only",
4113
+ capabilities: { version: 1, ops: ["namespace_writable"] },
4114
+ },
3383
4115
  { token: "read-only", capabilities: { version: 1, ops: ["recall"] } },
3384
4116
  ],
3385
4117
  adminConsoleEnabled: false,
@@ -3443,16 +4175,31 @@ test("HTTP meetings build enforces the per-principal write rate limit (issue #19
3443
4175
  const build = () =>
3444
4176
  fetch(`http://127.0.0.1:${status.port}/engram/v1/meetings/build`, {
3445
4177
  method: "POST",
3446
- headers: { authorization: "Bearer test-token", "content-type": "application/json" },
4178
+ headers: {
4179
+ authorization: "Bearer test-token",
4180
+ "content-type": "application/json",
4181
+ },
3447
4182
  body: JSON.stringify({ date: "2026-03-10" }),
3448
4183
  });
3449
4184
  const first = await build();
3450
- assert.equal(first.status, 200, "the first build is within the write quota");
4185
+ assert.equal(
4186
+ first.status,
4187
+ 200,
4188
+ "the first build is within the write quota",
4189
+ );
3451
4190
  const second = await build();
3452
- assert.equal(second.status, 429, "the second build exceeds the per-principal write quota");
3453
- const rateLimited = await second.json() as { code?: string };
4191
+ assert.equal(
4192
+ second.status,
4193
+ 429,
4194
+ "the second build exceeds the per-principal write quota",
4195
+ );
4196
+ const rateLimited = (await second.json()) as { code?: string };
3454
4197
  assert.equal(rateLimited.code, "write_rate_limited");
3455
- assert.equal(builds, 1, "the rate-limited build never reached the service (no persist/reindex)");
4198
+ assert.equal(
4199
+ builds,
4200
+ 1,
4201
+ "the rate-limited build never reached the service (no persist/reindex)",
4202
+ );
3456
4203
  } finally {
3457
4204
  await server.stop();
3458
4205
  }
@@ -3480,7 +4227,10 @@ test("HTTP-MCP extraction force-flush aliases consume the write quota", async ()
3480
4227
  const status = await server.start();
3481
4228
  try {
3482
4229
  const responses: Response[] = [];
3483
- for (const name of ["remnic.extraction_force_flush", "engram.extraction_force_flush"]) {
4230
+ for (const name of [
4231
+ "remnic.extraction_force_flush",
4232
+ "engram.extraction_force_flush",
4233
+ ]) {
3484
4234
  responses.push(
3485
4235
  await fetch(`http://127.0.0.1:${status.port}/mcp`, {
3486
4236
  method: "POST",
@@ -3510,9 +4260,214 @@ test("HTTP-MCP extraction force-flush aliases consume the write quota", async ()
3510
4260
  assert.equal(first.status, 200);
3511
4261
  await first.text();
3512
4262
  assert.equal(second.status, 429);
3513
- const rateLimited = await second.json() as { code?: string };
4263
+ const rateLimited = (await second.json()) as { code?: string };
3514
4264
  assert.equal(rateLimited.code, "write_rate_limited");
3515
- assert.equal(flushes, 1, "the rate-limited alias must not reach the service");
4265
+ assert.equal(
4266
+ flushes,
4267
+ 1,
4268
+ "the rate-limited alias must not reach the service",
4269
+ );
4270
+ } finally {
4271
+ await server.stop();
4272
+ }
4273
+ });
4274
+
4275
+ test("concurrent support-passport MCP writes reserve quota before dispatch", async () => {
4276
+ const firstStarted = Promise.withResolvers<void>();
4277
+ const secondStarted = Promise.withResolvers<void>();
4278
+ const releaseWrites = Promise.withResolvers<void>();
4279
+ let writes = 0;
4280
+ let requestId = 0;
4281
+ const service = {
4282
+ supportPassportGenerateDrafts: async () => {
4283
+ writes += 1;
4284
+ if (writes === 1) firstStarted.resolve();
4285
+ if (writes === 2) secondStarted.resolve();
4286
+ await releaseWrites.promise;
4287
+ return [];
4288
+ },
4289
+ } as unknown as EngramAccessService;
4290
+ const server = new EngramAccessHttpServer({
4291
+ service,
4292
+ port: 0,
4293
+ authToken: "test-token",
4294
+ adminConsoleEnabled: false,
4295
+ writeRateLimitMaxRequests: 1,
4296
+ });
4297
+ const status = await server.start();
4298
+ const generate = () =>
4299
+ fetch(`http://127.0.0.1:${status.port}/mcp`, {
4300
+ method: "POST",
4301
+ headers: {
4302
+ authorization: "Bearer test-token",
4303
+ "content-type": "application/json",
4304
+ },
4305
+ body: JSON.stringify({
4306
+ jsonrpc: "2.0",
4307
+ id: ++requestId,
4308
+ method: "tools/call",
4309
+ params: {
4310
+ name: "remnic.support_passport_drafts_generate",
4311
+ arguments: {
4312
+ sourceMemoryIds: ["memory-1"],
4313
+ sourceMemoryRevisions: [
4314
+ { memoryId: "memory-1", revision: "a".repeat(64) },
4315
+ ],
4316
+ consent: true,
4317
+ },
4318
+ },
4319
+ }),
4320
+ });
4321
+ try {
4322
+ const firstRequest = generate();
4323
+ await firstStarted.promise;
4324
+ const secondRequest = generate();
4325
+ const secondOutcome = await Promise.race([
4326
+ secondRequest.then(() => "rate-limited" as const),
4327
+ secondStarted.promise.then(() => "dispatched" as const),
4328
+ ]);
4329
+ assert.equal(secondOutcome, "rate-limited");
4330
+ releaseWrites.resolve();
4331
+ const [first, second] = await Promise.all([firstRequest, secondRequest]);
4332
+ assert.equal(first.status, 200);
4333
+ assert.equal(second.status, 429);
4334
+ assert.equal(writes, 1);
4335
+ } finally {
4336
+ releaseWrites.resolve();
4337
+ await server.stop();
4338
+ }
4339
+ });
4340
+
4341
+ test("support-passport MCP keeps reserved quota when generated drafts commit before failure", async () => {
4342
+ let writes = 0;
4343
+ let requestId = 0;
4344
+ const service = {
4345
+ supportPassportGenerateDrafts: async (
4346
+ _principal: string,
4347
+ input: { onCommitted?: () => void },
4348
+ ) => {
4349
+ writes += 1;
4350
+ input.onCommitted?.();
4351
+ throw new SupportPassportError(
4352
+ "state_conflict",
4353
+ "Draft cleanup failed.",
4354
+ 409,
4355
+ );
4356
+ },
4357
+ } as unknown as EngramAccessService;
4358
+ const server = new EngramAccessHttpServer({
4359
+ service,
4360
+ port: 0,
4361
+ authToken: "test-token",
4362
+ adminConsoleEnabled: false,
4363
+ writeRateLimitMaxRequests: 1,
4364
+ });
4365
+ const status = await server.start();
4366
+ const generate = () =>
4367
+ fetch(`http://127.0.0.1:${status.port}/mcp`, {
4368
+ method: "POST",
4369
+ headers: {
4370
+ authorization: "Bearer test-token",
4371
+ "content-type": "application/json",
4372
+ },
4373
+ body: JSON.stringify({
4374
+ jsonrpc: "2.0",
4375
+ id: ++requestId,
4376
+ method: "tools/call",
4377
+ params: {
4378
+ name: "remnic.support_passport_drafts_generate",
4379
+ arguments: {
4380
+ sourceMemoryIds: ["memory-1"],
4381
+ sourceMemoryRevisions: [
4382
+ { memoryId: "memory-1", revision: "a".repeat(64) },
4383
+ ],
4384
+ consent: true,
4385
+ },
4386
+ },
4387
+ }),
4388
+ });
4389
+ try {
4390
+ const first = await generate();
4391
+ assert.equal(first.status, 200);
4392
+ assert.equal(
4393
+ ((await first.json()) as { result?: { isError?: boolean } }).result
4394
+ ?.isError,
4395
+ true,
4396
+ );
4397
+ assert.equal(
4398
+ (await generate()).status,
4399
+ 429,
4400
+ "the committed draft keeps its reserved MCP quota slot",
4401
+ );
4402
+ assert.equal(writes, 1);
4403
+ } finally {
4404
+ await server.stop();
4405
+ }
4406
+ });
4407
+
4408
+ test("support-passport MCP applies the write quota to share-link revocation", async () => {
4409
+ let drafts = 0;
4410
+ let revocations = 0;
4411
+ const service = {
4412
+ supportPassportEnabled: true,
4413
+ supportPassportCreateManualDraft: async () => {
4414
+ drafts += 1;
4415
+ return { cardId: "card-one" };
4416
+ },
4417
+ supportPassportRevokeGrant: async () => {
4418
+ revocations += 1;
4419
+ return {
4420
+ grantId: "grant-one",
4421
+ revokedAt: "2026-08-11T12:05:00.000Z",
4422
+ version: 2,
4423
+ };
4424
+ },
4425
+ } as unknown as EngramAccessService;
4426
+ const server = new EngramAccessHttpServer({
4427
+ service,
4428
+ port: 0,
4429
+ authToken: "test-token",
4430
+ adminConsoleEnabled: false,
4431
+ writeRateLimitMaxRequests: 1,
4432
+ });
4433
+ const status = await server.start();
4434
+ const call = (name: string, args: Record<string, unknown>, id: number) =>
4435
+ fetch(`http://127.0.0.1:${status.port}/mcp`, {
4436
+ method: "POST",
4437
+ headers: {
4438
+ authorization: "Bearer test-token",
4439
+ "content-type": "application/json",
4440
+ },
4441
+ body: JSON.stringify({
4442
+ jsonrpc: "2.0",
4443
+ id,
4444
+ method: "tools/call",
4445
+ params: { name, arguments: args },
4446
+ }),
4447
+ });
4448
+ try {
4449
+ const draft = await call(
4450
+ "remnic.support_passport_draft_create",
4451
+ {
4452
+ title: "Plan changes",
4453
+ statement: "Tell me before plans change.",
4454
+ category: "transitions",
4455
+ reviewBy: "2027-02-07T12:00:00.000Z",
4456
+ },
4457
+ 1,
4458
+ );
4459
+ assert.equal(draft.status, 200);
4460
+ const revoked = await call(
4461
+ "remnic.support_passport_grant_revoke",
4462
+ {
4463
+ grantId: "grant-one",
4464
+ expectedVersion: 1,
4465
+ },
4466
+ 2,
4467
+ );
4468
+ assert.equal(revoked.status, 429);
4469
+ assert.equal(drafts, 1);
4470
+ assert.equal(revocations, 0);
3516
4471
  } finally {
3517
4472
  await server.stop();
3518
4473
  }
@@ -3522,9 +4477,7 @@ test("HTTP extraction force-flush records quota after commit when cleanup fails"
3522
4477
  let flushes = 0;
3523
4478
  let committed = 0;
3524
4479
  const service = {
3525
- extractionForceFlush: async (request: {
3526
- onCommitted?: () => void;
3527
- }) => {
4480
+ extractionForceFlush: async (request: { onCommitted?: () => void }) => {
3528
4481
  flushes += 1;
3529
4482
  request.onCommitted?.();
3530
4483
  committed += 1;
@@ -3554,7 +4507,7 @@ test("HTTP extraction force-flush records quota after commit when cleanup fails"
3554
4507
  await first.text();
3555
4508
  const second = await flush();
3556
4509
  assert.equal(second.status, 429);
3557
- const rateLimited = await second.json() as { code?: string };
4510
+ const rateLimited = (await second.json()) as { code?: string };
3558
4511
  assert.equal(rateLimited.code, "write_rate_limited");
3559
4512
  assert.equal(flushes, 1);
3560
4513
  assert.equal(committed, 1);
@@ -3612,7 +4565,11 @@ test("HTTP-MCP extraction force-flush records quota at the commit boundary", asy
3612
4565
  assert.equal(second.status, 429);
3613
4566
  const rateLimited = (await second.json()) as { code?: string };
3614
4567
  assert.equal(rateLimited.code, "write_rate_limited");
3615
- assert.equal(flushes, 1, "the rate-limited MCP flush must not reach the service");
4568
+ assert.equal(
4569
+ flushes,
4570
+ 1,
4571
+ "the rate-limited MCP flush must not reach the service",
4572
+ );
3616
4573
  assert.equal(committed, 1);
3617
4574
  } finally {
3618
4575
  await server.stop();
@@ -3635,11 +4592,18 @@ test("HTTP meetings get returns 400 (not 500) for a malformed percent-encoded id
3635
4592
  });
3636
4593
  const status = await server.start();
3637
4594
  try {
3638
- const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/meetings/%E0%A4%A`, {
3639
- headers: { authorization: "Bearer test-token" },
3640
- });
3641
- assert.equal(response.status, 400, "a malformed id is a client error, not an internal 500");
3642
- const errorBody = await response.json() as { code?: string };
4595
+ const response = await fetch(
4596
+ `http://127.0.0.1:${status.port}/engram/v1/meetings/%E0%A4%A`,
4597
+ {
4598
+ headers: { authorization: "Bearer test-token" },
4599
+ },
4600
+ );
4601
+ assert.equal(
4602
+ response.status,
4603
+ 400,
4604
+ "a malformed id is a client error, not an internal 500",
4605
+ );
4606
+ const errorBody = (await response.json()) as { code?: string };
3643
4607
  assert.equal(errorBody.code, "invalid_request");
3644
4608
  assert.equal(called, 0, "the malformed id never reached the service");
3645
4609
  } finally {
@@ -3649,7 +4613,10 @@ test("HTTP meetings get returns 400 (not 500) for a malformed percent-encoded id
3649
4613
 
3650
4614
  test("HTTP authorization probe verifies requested operation grants without invoking operations (issue #2129)", async () => {
3651
4615
  const service = {
3652
- configRef: parseConfig({ memoryDir: "/tmp/remnic-http-authorization-probe", defaultNamespace: "default" }),
4616
+ configRef: parseConfig({
4617
+ memoryDir: "/tmp/remnic-http-authorization-probe",
4618
+ defaultNamespace: "default",
4619
+ }),
3653
4620
  } as unknown as EngramAccessService;
3654
4621
  const implicitNamespaceOperations = [
3655
4622
  "offline_sync_snapshot",
@@ -3668,10 +4635,19 @@ test("HTTP authorization probe verifies requested operation grants without invok
3668
4635
  authTokenEntriesGetter: () => [
3669
4636
  {
3670
4637
  token: "delegate-token",
3671
- capabilities: { version: 1, ops: ["recall", "observe", "lcm_compaction_flush"] },
4638
+ capabilities: {
4639
+ version: 1,
4640
+ ops: ["recall", "observe", "lcm_compaction_flush"],
4641
+ },
4642
+ },
4643
+ {
4644
+ token: "recall-only-token",
4645
+ capabilities: { version: 1, ops: ["recall"] },
4646
+ },
4647
+ {
4648
+ token: "observe-only-token",
4649
+ capabilities: { version: 1, ops: ["observe"] },
3672
4650
  },
3673
- { token: "recall-only-token", capabilities: { version: 1, ops: ["recall"] } },
3674
- { token: "observe-only-token", capabilities: { version: 1, ops: ["observe"] } },
3675
4651
  {
3676
4652
  token: "namespace-scoped-delegate-token",
3677
4653
  capabilities: {
@@ -3720,38 +4696,71 @@ test("HTTP authorization probe verifies requested operation grants without invok
3720
4696
  const query = new URLSearchParams();
3721
4697
  for (const operation of operations) query.append("op", operation);
3722
4698
  if (namespace !== undefined) query.set("namespace", namespace);
3723
- return fetch(`http://127.0.0.1:${status.port}/engram/v1/authorization?${query}`, {
3724
- headers: { authorization: `Bearer ${token}` },
3725
- });
4699
+ return fetch(
4700
+ `http://127.0.0.1:${status.port}/engram/v1/authorization?${query}`,
4701
+ {
4702
+ headers: { authorization: `Bearer ${token}` },
4703
+ },
4704
+ );
3726
4705
  };
3727
4706
  const delegateOperations = ["recall", "observe", "lcm_compaction_flush"];
3728
4707
  try {
3729
- const authorized = await probe("delegate-token", [...delegateOperations, "recall"]);
4708
+ const authorized = await probe("delegate-token", [
4709
+ ...delegateOperations,
4710
+ "recall",
4711
+ ]);
3730
4712
  assert.equal(authorized.status, 200);
3731
4713
  assert.equal(authorized.headers.get("cache-control"), "no-store");
3732
- assert.deepEqual(await authorized.json(), { authorized: true, operations: delegateOperations });
4714
+ assert.deepEqual(await authorized.json(), {
4715
+ authorized: true,
4716
+ operations: delegateOperations,
4717
+ });
3733
4718
 
3734
- const alternateGrant = await probe("observe-only-token", ["namespace_writable"]);
4719
+ const alternateGrant = await probe("observe-only-token", [
4720
+ "namespace_writable",
4721
+ ]);
3735
4722
  assert.equal(alternateGrant.status, 200);
3736
4723
  await alternateGrant.text();
3737
4724
 
3738
- const namespaceDiagnostic = await probe(
3739
- "namespace-scoped-delegate-token",
3740
- ["namespace_writable"],
4725
+ const namespaceDiagnostic = await probe("namespace-scoped-delegate-token", [
4726
+ "namespace_writable",
4727
+ ]);
4728
+ assert.equal(
4729
+ namespaceDiagnostic.status,
4730
+ 200,
4731
+ "writability diagnostics report scope in their response",
3741
4732
  );
3742
- assert.equal(namespaceDiagnostic.status, 200, "writability diagnostics report scope in their response");
3743
4733
  await namespaceDiagnostic.text();
3744
4734
 
3745
- const namespaceAgnostic = await probe("namespace-scoped-adapters-token", ["adapters_status"]);
3746
- assert.equal(namespaceAgnostic.status, 200, "namespace-free operations must not use the daemon default");
4735
+ const namespaceAgnostic = await probe("namespace-scoped-adapters-token", [
4736
+ "adapters_status",
4737
+ ]);
4738
+ assert.equal(
4739
+ namespaceAgnostic.status,
4740
+ 200,
4741
+ "namespace-free operations must not use the daemon default",
4742
+ );
3747
4743
  await namespaceAgnostic.text();
3748
4744
 
3749
- const namespaceDenied = await probe("namespace-scoped-delegate-token", delegateOperations);
3750
- assert.equal(namespaceDenied.status, 403, "a scoped token must cover the daemon default namespace");
4745
+ const namespaceDenied = await probe(
4746
+ "namespace-scoped-delegate-token",
4747
+ delegateOperations,
4748
+ );
4749
+ assert.equal(
4750
+ namespaceDenied.status,
4751
+ 403,
4752
+ "a scoped token must cover the daemon default namespace",
4753
+ );
3751
4754
 
3752
4755
  for (const operation of implicitNamespaceOperations) {
3753
- const implicitNamespace = await probe("implicit-namespace-token", [operation]);
3754
- assert.equal(implicitNamespace.status, 403, `${operation} must authorize the effective namespace`);
4756
+ const implicitNamespace = await probe("implicit-namespace-token", [
4757
+ operation,
4758
+ ]);
4759
+ assert.equal(
4760
+ implicitNamespace.status,
4761
+ 403,
4762
+ `${operation} must authorize the effective namespace`,
4763
+ );
3755
4764
  await implicitNamespace.text();
3756
4765
  }
3757
4766
  await namespaceDenied.text();
@@ -3776,7 +4785,9 @@ test("HTTP authorization probe verifies requested operation grants without invok
3776
4785
  );
3777
4786
  await resourceNamespaceProbe.text();
3778
4787
 
3779
- const fleetWide = await probe("fleet-scoped-token", ["continuity_audit_generate"]);
4788
+ const fleetWide = await probe("fleet-scoped-token", [
4789
+ "continuity_audit_generate",
4790
+ ]);
3780
4791
  assert.equal(fleetWide.status, 403);
3781
4792
  assert.equal(fleetWide.headers.get("cache-control"), "no-store");
3782
4793
  await fleetWide.text();
@@ -3803,6 +4814,112 @@ test("HTTP authorization probe verifies requested operation grants without invok
3803
4814
  assert.equal(unknown.status, 400);
3804
4815
  assert.equal(unknown.headers.get("cache-control"), "no-store");
3805
4816
  await unknown.text();
4817
+
4818
+ for (const helperOperation of [
4819
+ "support_passport_grant_read",
4820
+ "support_passport_grant_ask",
4821
+ ]) {
4822
+ const helperOnly = await probe("delegate-token", [helperOperation]);
4823
+ assert.equal(
4824
+ helperOnly.status,
4825
+ 400,
4826
+ `${helperOperation} cannot use bearer authorization probes`,
4827
+ );
4828
+ assert.equal(helperOnly.headers.get("cache-control"), "no-store");
4829
+ await helperOnly.text();
4830
+ }
4831
+ } finally {
4832
+ await server.stop();
4833
+ }
4834
+ });
4835
+
4836
+ test("HTTP authorization probe checks the principal-derived support passport namespace", async () => {
4837
+ let namespaceResolutions = 0;
4838
+ const service = {
4839
+ configRef: parseConfig({
4840
+ memoryDir: "/tmp/remnic-http-passport-authorization-probe",
4841
+ defaultNamespace: "default",
4842
+ namespacesEnabled: true,
4843
+ namespacePolicies: [
4844
+ {
4845
+ name: "owner:alice",
4846
+ readPrincipals: ["owner:alice"],
4847
+ writePrincipals: ["owner:alice"],
4848
+ },
4849
+ ],
4850
+ }),
4851
+ getWritableStorageForNamespace: async (
4852
+ namespace: string | undefined,
4853
+ principal: string | undefined,
4854
+ ) => {
4855
+ namespaceResolutions += 1;
4856
+ assert.equal(namespace, undefined);
4857
+ assert.equal(principal, "owner:alice");
4858
+ return { namespace: "owner:alice", storage: {} as StorageManager };
4859
+ },
4860
+ } as unknown as EngramAccessService;
4861
+ const server = new EngramAccessHttpServer({
4862
+ service,
4863
+ port: 0,
4864
+ principal: "owner:alice",
4865
+ authTokenEntriesGetter: () => [
4866
+ {
4867
+ token: "passport-token",
4868
+ capabilities: {
4869
+ version: 1,
4870
+ ops: ["support_passport_cards_list"],
4871
+ namespaces: ["other"],
4872
+ },
4873
+ },
4874
+ ],
4875
+ adminConsoleEnabled: false,
4876
+ });
4877
+ const status = await server.start();
4878
+ try {
4879
+ const response = await fetch(
4880
+ `http://127.0.0.1:${status.port}/engram/v1/authorization?op=support_passport_cards_list&namespace=other`,
4881
+ { headers: { authorization: "Bearer passport-token" } },
4882
+ );
4883
+
4884
+ assert.equal(response.status, 403);
4885
+ assert.equal(namespaceResolutions, 1);
4886
+ } finally {
4887
+ await server.stop();
4888
+ }
4889
+ });
4890
+
4891
+ test("HTTP authorization probe rejects support passport access without a trusted principal", async () => {
4892
+ let namespaceResolutions = 0;
4893
+ const service = {
4894
+ configRef: parseConfig({
4895
+ memoryDir: "/tmp/remnic-http-passport-principal-probe",
4896
+ defaultNamespace: "default",
4897
+ }),
4898
+ getWritableStorageForNamespace: async () => {
4899
+ namespaceResolutions += 1;
4900
+ return { namespace: "default", storage: {} as StorageManager };
4901
+ },
4902
+ } as unknown as EngramAccessService;
4903
+ const server = new EngramAccessHttpServer({
4904
+ service,
4905
+ port: 0,
4906
+ authTokenEntriesGetter: () => [
4907
+ {
4908
+ token: "passport-token",
4909
+ capabilities: { version: 1, ops: ["support_passport_cards_list"] },
4910
+ },
4911
+ ],
4912
+ adminConsoleEnabled: false,
4913
+ });
4914
+ const status = await server.start();
4915
+ try {
4916
+ const response = await fetch(
4917
+ `http://127.0.0.1:${status.port}/engram/v1/authorization?op=support_passport_cards_list`,
4918
+ { headers: { authorization: "Bearer passport-token" } },
4919
+ );
4920
+
4921
+ assert.equal(response.status, 403);
4922
+ assert.equal(namespaceResolutions, 0);
3806
4923
  } finally {
3807
4924
  await server.stop();
3808
4925
  }
@@ -3838,7 +4955,11 @@ test("HTTP capabilities explicitly advertise receiver convergence finalization",
3838
4955
  });
3839
4956
 
3840
4957
  test("HTTP convergence completion forwards one authenticated namespace batch refresh", async () => {
3841
- const calls: Array<{ namespaces: string[]; principal?: string; sourceId: string }> = [];
4958
+ const calls: Array<{
4959
+ namespaces: string[];
4960
+ principal?: string;
4961
+ sourceId: string;
4962
+ }> = [];
3842
4963
  const service = {
3843
4964
  offlineSyncFinalizeConvergence: async (options: {
3844
4965
  namespaces: string[];
@@ -3856,7 +4977,8 @@ test("HTTP convergence completion forwards one authenticated namespace batch ref
3856
4977
  principal: "writer",
3857
4978
  adminConsoleEnabled: false,
3858
4979
  });
3859
- const route = "/remnic/v1/offline-sync/convergence-complete?namespace=team&namespace=shared";
4980
+ const route =
4981
+ "/remnic/v1/offline-sync/convergence-complete?namespace=team&namespace=shared";
3860
4982
 
3861
4983
  const status = await server.start();
3862
4984
  try {
@@ -3879,11 +5001,13 @@ test("HTTP convergence completion forwards one authenticated namespace batch ref
3879
5001
  namespaces: ["team", "shared"],
3880
5002
  refreshed: true,
3881
5003
  });
3882
- assert.deepEqual(calls, [{
3883
- namespaces: ["team", "shared"],
3884
- principal: "writer",
3885
- sourceId: "remnic-converge",
3886
- }]);
5004
+ assert.deepEqual(calls, [
5005
+ {
5006
+ namespaces: ["team", "shared"],
5007
+ principal: "writer",
5008
+ sourceId: "remnic-converge",
5009
+ },
5010
+ ]);
3887
5011
  } finally {
3888
5012
  await server.stop();
3889
5013
  }
@@ -3901,21 +5025,31 @@ test("HTTP convergence completion accepts either receiver mutation grant", async
3901
5025
  service,
3902
5026
  port: 0,
3903
5027
  authTokenEntriesGetter: () => [
3904
- { token: "file-token", capabilities: { version: 1, ops: ["offline_sync_apply_file_content"] } },
3905
- { token: "delete-token", capabilities: { version: 1, ops: ["offline_sync_apply"] } },
3906
- { token: "read-token", capabilities: { version: 1, ops: ["offline_sync_snapshot"] } },
5028
+ {
5029
+ token: "file-token",
5030
+ capabilities: { version: 1, ops: ["offline_sync_apply_file_content"] },
5031
+ },
5032
+ {
5033
+ token: "delete-token",
5034
+ capabilities: { version: 1, ops: ["offline_sync_apply"] },
5035
+ },
5036
+ {
5037
+ token: "read-token",
5038
+ capabilities: { version: 1, ops: ["offline_sync_snapshot"] },
5039
+ },
3907
5040
  ],
3908
5041
  adminConsoleEnabled: false,
3909
5042
  });
3910
5043
  const status = await server.start();
3911
5044
  const route = `http://127.0.0.1:${status.port}/remnic/v1/offline-sync/convergence-complete?namespace=team`;
3912
- const complete = (token: string) => fetch(route, {
3913
- method: "POST",
3914
- headers: {
3915
- authorization: `Bearer ${token}`,
3916
- "x-remnic-source-id": encodeURIComponent("remnic-converge"),
3917
- },
3918
- });
5045
+ const complete = (token: string) =>
5046
+ fetch(route, {
5047
+ method: "POST",
5048
+ headers: {
5049
+ authorization: `Bearer ${token}`,
5050
+ "x-remnic-source-id": encodeURIComponent("remnic-converge"),
5051
+ },
5052
+ });
3919
5053
 
3920
5054
  try {
3921
5055
  const fileResponse = await complete("file-token");
@@ -3966,9 +5100,15 @@ test("HTTP convergence completion enforces the per-principal write rate limit",
3966
5100
  const second = await fetch(route, requestOptions);
3967
5101
  assert.equal(second.status, 429);
3968
5102
  const rateLimited: unknown = await second.json();
3969
- assert.ok(rateLimited && typeof rateLimited === "object" && "code" in rateLimited);
5103
+ assert.ok(
5104
+ rateLimited && typeof rateLimited === "object" && "code" in rateLimited,
5105
+ );
3970
5106
  assert.equal(rateLimited.code, "write_rate_limited");
3971
- assert.equal(completions, 1, "the rate-limited completion must not reach the service");
5107
+ assert.equal(
5108
+ completions,
5109
+ 1,
5110
+ "the rate-limited completion must not reach the service",
5111
+ );
3972
5112
  } finally {
3973
5113
  await server.stop();
3974
5114
  }
@@ -3991,7 +5131,10 @@ test("HTTP convergence completion denies scoped tokens that omit the namespace",
3991
5131
  service,
3992
5132
  port: 0,
3993
5133
  authTokenEntriesGetter: () => [
3994
- { token: "scoped-token", capabilities: { version: 1, namespaces: ["team"] } },
5134
+ {
5135
+ token: "scoped-token",
5136
+ capabilities: { version: 1, namespaces: ["team"] },
5137
+ },
3995
5138
  ],
3996
5139
  adminConsoleEnabled: false,
3997
5140
  });
@@ -4011,14 +5154,20 @@ test("HTTP convergence completion denies scoped tokens that omit the namespace",
4011
5154
 
4012
5155
  assert.equal(response.status, 403);
4013
5156
  await response.text();
4014
- assert.equal(completions, 0, "the denied completion must not reach the service");
5157
+ assert.equal(
5158
+ completions,
5159
+ 0,
5160
+ "the denied completion must not reach the service",
5161
+ );
4015
5162
  } finally {
4016
5163
  await server.stop();
4017
5164
  }
4018
5165
  });
4019
5166
 
4020
5167
  test("HTTP external wiki search aliases return the same cited result", async () => {
4021
- const rootDir = await mkdtemp(path.join(os.tmpdir(), "remnic-http-external-wiki-"));
5168
+ const rootDir = await mkdtemp(
5169
+ path.join(os.tmpdir(), "remnic-http-external-wiki-"),
5170
+ );
4022
5171
  await mkdir(path.join(rootDir, "wiki"), { recursive: true });
4023
5172
  await writeFile(
4024
5173
  path.join(rootDir, "INDEX.md"),
@@ -4032,15 +5181,17 @@ test("HTTP external wiki search aliases return the same cited result", async ()
4032
5181
  );
4033
5182
  const service = {
4034
5183
  configRef: {
4035
- externalWikis: [{
4036
- id: "planning",
4037
- rootDir,
4038
- enabled: true,
4039
- pagesDir: "wiki",
4040
- indexFile: "INDEX.md",
4041
- indexInQmd: false,
4042
- includeInDefaultRecall: false,
4043
- }],
5184
+ externalWikis: [
5185
+ {
5186
+ id: "planning",
5187
+ rootDir,
5188
+ enabled: true,
5189
+ pagesDir: "wiki",
5190
+ indexFile: "INDEX.md",
5191
+ indexInQmd: false,
5192
+ includeInDefaultRecall: false,
5193
+ },
5194
+ ],
4044
5195
  },
4045
5196
  } as unknown as EngramAccessService;
4046
5197
  const server = new EngramAccessHttpServer({
@@ -4054,38 +5205,48 @@ test("HTTP external wiki search aliases return the same cited result", async ()
4054
5205
  try {
4055
5206
  let canonicalBody: unknown;
4056
5207
  for (const prefix of ["remnic", "engram"]) {
4057
- const response = await fetch(`http://127.0.0.1:${status.port}/${prefix}/v1/external-wikis/search`, {
4058
- method: "POST",
4059
- headers: {
4060
- authorization: "Bearer test-token",
4061
- "content-type": "application/json",
5208
+ const response = await fetch(
5209
+ `http://127.0.0.1:${status.port}/${prefix}/v1/external-wikis/search`,
5210
+ {
5211
+ method: "POST",
5212
+ headers: {
5213
+ authorization: "Bearer test-token",
5214
+ "content-type": "application/json",
5215
+ },
5216
+ body: JSON.stringify({
5217
+ query: "deterministic planner",
5218
+ wikiId: "planning",
5219
+ limit: 2,
5220
+ maxCharsPerHit: 200,
5221
+ }),
4062
5222
  },
4063
- body: JSON.stringify({
4064
- query: "deterministic planner",
4065
- wikiId: "planning",
4066
- limit: 2,
4067
- maxCharsPerHit: 200,
4068
- }),
4069
- });
5223
+ );
4070
5224
  assert.equal(response.status, 200);
4071
5225
  const body: unknown = await response.json();
4072
5226
  if (canonicalBody === undefined) canonicalBody = body;
4073
5227
  else assert.deepEqual(body, canonicalBody);
4074
5228
  }
4075
- assert.ok(canonicalBody && typeof canonicalBody === "object" && "hits" in canonicalBody);
5229
+ assert.ok(
5230
+ canonicalBody &&
5231
+ typeof canonicalBody === "object" &&
5232
+ "hits" in canonicalBody,
5233
+ );
4076
5234
  assert.ok(Array.isArray(canonicalBody.hits));
4077
5235
  const firstHit = canonicalBody.hits[0];
4078
5236
  assert.ok(firstHit && typeof firstHit === "object" && "path" in firstHit);
4079
5237
  assert.equal(firstHit.path, "wiki/planning.md");
4080
5238
 
4081
- const invalid = await fetch(`http://127.0.0.1:${status.port}/remnic/v1/external-wikis/search`, {
4082
- method: "POST",
4083
- headers: {
4084
- authorization: "Bearer test-token",
4085
- "content-type": "application/json",
5239
+ const invalid = await fetch(
5240
+ `http://127.0.0.1:${status.port}/remnic/v1/external-wikis/search`,
5241
+ {
5242
+ method: "POST",
5243
+ headers: {
5244
+ authorization: "Bearer test-token",
5245
+ "content-type": "application/json",
5246
+ },
5247
+ body: JSON.stringify({ query: " " }),
4086
5248
  },
4087
- body: JSON.stringify({ query: " " }),
4088
- });
5249
+ );
4089
5250
  assert.equal(invalid.status, 400);
4090
5251
  } finally {
4091
5252
  await server.stop();
@@ -4100,7 +5261,13 @@ test("HTTP memory search aliases dispatch the boundary operation identically", a
4100
5261
  calls.push(request);
4101
5262
  return {
4102
5263
  query: request.query,
4103
- results: [{ path: "facts/alice.md", score: 0.71, snippet: "alice prefers dark mode" }],
5264
+ results: [
5265
+ {
5266
+ path: "facts/alice.md",
5267
+ score: 0.71,
5268
+ snippet: "alice prefers dark mode",
5269
+ },
5270
+ ],
4104
5271
  count: 1,
4105
5272
  };
4106
5273
  },
@@ -4116,22 +5283,36 @@ test("HTTP memory search aliases dispatch the boundary operation identically", a
4116
5283
  try {
4117
5284
  let canonicalBody: unknown;
4118
5285
  for (const prefix of ["engram", "remnic"]) {
4119
- const response = await fetch(`http://127.0.0.1:${status.port}/${prefix}/v1/memories/search`, {
4120
- method: "POST",
4121
- headers: {
4122
- authorization: "Bearer test-token",
4123
- "content-type": "application/json",
5286
+ const response = await fetch(
5287
+ `http://127.0.0.1:${status.port}/${prefix}/v1/memories/search`,
5288
+ {
5289
+ method: "POST",
5290
+ headers: {
5291
+ authorization: "Bearer test-token",
5292
+ "content-type": "application/json",
5293
+ },
5294
+ body: JSON.stringify({ query: "dark mode", maxResults: 3 }),
4124
5295
  },
4125
- body: JSON.stringify({ query: "dark mode", maxResults: 3 }),
4126
- });
5296
+ );
4127
5297
  assert.equal(response.status, 200);
4128
5298
  const body: unknown = await response.json();
4129
5299
  if (canonicalBody === undefined) canonicalBody = body;
4130
- else assert.deepEqual(body, canonicalBody, "both prefixes must return one shape");
5300
+ else
5301
+ assert.deepEqual(
5302
+ body,
5303
+ canonicalBody,
5304
+ "both prefixes must return one shape",
5305
+ );
4131
5306
  }
4132
5307
  assert.deepEqual(canonicalBody, {
4133
5308
  query: "dark mode",
4134
- results: [{ path: "facts/alice.md", score: 0.71, snippet: "alice prefers dark mode" }],
5309
+ results: [
5310
+ {
5311
+ path: "facts/alice.md",
5312
+ score: 0.71,
5313
+ snippet: "alice prefers dark mode",
5314
+ },
5315
+ ],
4135
5316
  count: 1,
4136
5317
  });
4137
5318
  assert.equal(calls.length, 2);
@@ -4174,9 +5355,17 @@ test("HTTP memory search rejects an invalid body before service dispatch", async
4174
5355
  body: JSON.stringify(body),
4175
5356
  });
4176
5357
  try {
4177
- assert.equal((await post({ query: " " })).status, 400, "blank query is rejected");
5358
+ assert.equal(
5359
+ (await post({ query: " " })).status,
5360
+ 400,
5361
+ "blank query is rejected",
5362
+ );
4178
5363
  assert.equal((await post({})).status, 400, "a missing query is rejected");
4179
- assert.equal((await post({ query: "ok", maxResults: 0 })).status, 400, "maxResults must be >= 1");
5364
+ assert.equal(
5365
+ (await post({ query: "ok", maxResults: 0 })).status,
5366
+ 400,
5367
+ "maxResults must be >= 1",
5368
+ );
4180
5369
  assert.equal(
4181
5370
  (await post({ query: "ok", namespace: 123 })).status,
4182
5371
  400,
@@ -4212,11 +5401,19 @@ test("HTTP memory search enforces token op and namespace allow-lists", async ()
4212
5401
  { token: "wrong-op", capabilities: { version: 1, ops: ["recall"] } },
4213
5402
  {
4214
5403
  token: "wrong-namespace",
4215
- capabilities: { version: 1, ops: ["memory_search"], namespaces: ["other"] },
5404
+ capabilities: {
5405
+ version: 1,
5406
+ ops: ["memory_search"],
5407
+ namespaces: ["other"],
5408
+ },
4216
5409
  },
4217
5410
  {
4218
5411
  token: "reader",
4219
- capabilities: { version: 1, ops: ["memory_search"], namespaces: ["team"] },
5412
+ capabilities: {
5413
+ version: 1,
5414
+ ops: ["memory_search"],
5415
+ namespaces: ["team"],
5416
+ },
4220
5417
  },
4221
5418
  ],
4222
5419
  adminConsoleEnabled: false,
@@ -4232,14 +5429,20 @@ test("HTTP memory search enforces token op and namespace allow-lists", async ()
4232
5429
  body: JSON.stringify(body),
4233
5430
  });
4234
5431
  try {
4235
- assert.equal((await post("wrong-op", { query: "q", namespace: "team" })).status, 403);
5432
+ assert.equal(
5433
+ (await post("wrong-op", { query: "q", namespace: "team" })).status,
5434
+ 403,
5435
+ );
4236
5436
  assert.equal(
4237
5437
  (await post("wrong-namespace", { query: "q", namespace: "team" })).status,
4238
5438
  403,
4239
5439
  "a body namespace outside the token allow-list must fail closed",
4240
5440
  );
4241
5441
  assert.equal(calls.length, 0, "no denied request may reach the service");
4242
- assert.equal((await post("reader", { query: "q", namespace: "team" })).status, 200);
5442
+ assert.equal(
5443
+ (await post("reader", { query: "q", namespace: "team" })).status,
5444
+ 200,
5445
+ );
4243
5446
  assert.equal(calls.length, 1);
4244
5447
  assert.equal(
4245
5448
  (calls[0] as { namespace?: unknown }).namespace,
@@ -4267,9 +5470,16 @@ test("HTTP memory search binds a scoped token to one namespace instead of fannin
4267
5470
  authTokenEntriesGetter: () => [
4268
5471
  {
4269
5472
  token: "scoped",
4270
- capabilities: { version: 1, ops: ["memory_search"], namespaces: ["generalist"] },
5473
+ capabilities: {
5474
+ version: 1,
5475
+ ops: ["memory_search"],
5476
+ namespaces: ["generalist"],
5477
+ },
5478
+ },
5479
+ {
5480
+ token: "unrestricted",
5481
+ capabilities: { version: 1, ops: ["memory_search"] },
4271
5482
  },
4272
- { token: "unrestricted", capabilities: { version: 1, ops: ["memory_search"] } },
4273
5483
  ],
4274
5484
  adminConsoleEnabled: false,
4275
5485
  });
@@ -4277,7 +5487,10 @@ test("HTTP memory search binds a scoped token to one namespace instead of fannin
4277
5487
  const post = (token: string) =>
4278
5488
  fetch(`http://127.0.0.1:${status.port}/engram/v1/memories/search`, {
4279
5489
  method: "POST",
4280
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
5490
+ headers: {
5491
+ authorization: `Bearer ${token}`,
5492
+ "content-type": "application/json",
5493
+ },
4281
5494
  body: JSON.stringify({ query: "q" }),
4282
5495
  });
4283
5496
  try {
@@ -4312,7 +5525,14 @@ test("HTTP memory search forwards a validated ranking mode and trims the namespa
4312
5525
  port: 0,
4313
5526
  principal: "reader",
4314
5527
  authTokenEntriesGetter: () => [
4315
- { token: "scoped", capabilities: { version: 1, ops: ["memory_search"], namespaces: ["team"] } },
5528
+ {
5529
+ token: "scoped",
5530
+ capabilities: {
5531
+ version: 1,
5532
+ ops: ["memory_search"],
5533
+ namespaces: ["team"],
5534
+ },
5535
+ },
4316
5536
  ],
4317
5537
  adminConsoleEnabled: false,
4318
5538
  });
@@ -4320,17 +5540,25 @@ test("HTTP memory search forwards a validated ranking mode and trims the namespa
4320
5540
  const post = (body: unknown) =>
4321
5541
  fetch(`http://127.0.0.1:${status.port}/engram/v1/memories/search`, {
4322
5542
  method: "POST",
4323
- headers: { authorization: "Bearer scoped", "content-type": "application/json" },
5543
+ headers: {
5544
+ authorization: "Bearer scoped",
5545
+ "content-type": "application/json",
5546
+ },
4324
5547
  body: JSON.stringify(body),
4325
5548
  });
4326
5549
  try {
4327
5550
  assert.equal(
4328
- (await post({ query: "q", namespace: " team ", mode: "vector" })).status,
5551
+ (await post({ query: "q", namespace: " team ", mode: "vector" }))
5552
+ .status,
4329
5553
  200,
4330
5554
  "surrounding whitespace must not 403 a namespace the token allows",
4331
5555
  );
4332
5556
  assert.equal(calls[0]?.namespace, "team");
4333
- assert.equal(calls[0]?.mode, "vector", "the ranking mode reaches the service");
5557
+ assert.equal(
5558
+ calls[0]?.mode,
5559
+ "vector",
5560
+ "the ranking mode reaches the service",
5561
+ );
4334
5562
  assert.equal(
4335
5563
  (await post({ query: "q", namespace: "team", mode: "vsearch" })).status,
4336
5564
  400,
@@ -4353,7 +5581,8 @@ test("HTTP memory search excludes artifacts and tops up to a full page", async (
4353
5581
  { path: "facts/one.md", score: 0.5, snippet: "one" },
4354
5582
  { path: "facts/two.md", score: 0.4, snippet: "two" },
4355
5583
  ];
4356
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5584
+ const { runScopedMemorySearch } =
5585
+ await import("./access-memory-search-fanout.js");
4357
5586
  const results = await runScopedMemorySearch({
4358
5587
  query: "q",
4359
5588
  budget: 2,
@@ -4361,6 +5590,7 @@ test("HTTP memory search excludes artifacts and tops up to a full page", async (
4361
5590
  authorizeScope: () => {},
4362
5591
  namespacesEnabled: false,
4363
5592
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
5593
+ filterPrivate: async (hits) => hits,
4364
5594
  flatCorpus: async (limit) => {
4365
5595
  seen.push(limit ?? -1);
4366
5596
  return corpus.slice(0, limit);
@@ -4372,12 +5602,17 @@ test("HTTP memory search excludes artifacts and tops up to a full page", async (
4372
5602
  ["facts/one.md", "facts/two.md"],
4373
5603
  "a full page of real memories",
4374
5604
  );
4375
- assert.deepEqual(seen, [2, 4, 8], "doubles the candidate request until the budget is met");
5605
+ assert.deepEqual(
5606
+ seen,
5607
+ [2, 4, 8],
5608
+ "doubles the candidate request until the budget is met",
5609
+ );
4376
5610
  });
4377
5611
 
4378
5612
  test("HTTP memory search stops topping up when the corpus is exhausted", async () => {
4379
5613
  const seen: number[] = [];
4380
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5614
+ const { runScopedMemorySearch } =
5615
+ await import("./access-memory-search-fanout.js");
4381
5616
  const results = await runScopedMemorySearch({
4382
5617
  query: "q",
4383
5618
  budget: 5,
@@ -4385,6 +5620,7 @@ test("HTTP memory search stops topping up when the corpus is exhausted", async (
4385
5620
  authorizeScope: () => {},
4386
5621
  namespacesEnabled: false,
4387
5622
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
5623
+ filterPrivate: async (hits) => hits,
4388
5624
  flatCorpus: async (limit) => {
4389
5625
  seen.push(limit ?? -1);
4390
5626
  return [{ path: "facts/only.md", score: 0.5, snippet: "only" }];
@@ -4392,14 +5628,19 @@ test("HTTP memory search stops topping up when the corpus is exhausted", async (
4392
5628
  namespaced: async () => [],
4393
5629
  });
4394
5630
  assert.equal(results.length, 1);
4395
- assert.deepEqual(seen, [5], "a short page means there is nothing left to fetch");
5631
+ assert.deepEqual(
5632
+ seen,
5633
+ [5],
5634
+ "a short page means there is nothing left to fetch",
5635
+ );
4396
5636
  });
4397
5637
 
4398
5638
  test("HTTP memory search omits maxResults when the caller named no budget", async () => {
4399
5639
  // The wire stays exactly as it was for the default request: the backend's
4400
5640
  // own page size, not a resolved number. Top-up rounds are explicit.
4401
5641
  const seen: Array<number | undefined> = [];
4402
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5642
+ const { runScopedMemorySearch } =
5643
+ await import("./access-memory-search-fanout.js");
4403
5644
  const results = await runScopedMemorySearch({
4404
5645
  query: "q",
4405
5646
  budget: 3,
@@ -4407,6 +5648,7 @@ test("HTTP memory search omits maxResults when the caller named no budget", asyn
4407
5648
  authorizeScope: () => {},
4408
5649
  namespacesEnabled: false,
4409
5650
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
5651
+ filterPrivate: async (hits) => hits,
4410
5652
  flatCorpus: async (limit) => {
4411
5653
  seen.push(limit);
4412
5654
  const corpus = [
@@ -4419,12 +5661,15 @@ test("HTTP memory search omits maxResults when the caller named no budget", asyn
4419
5661
  },
4420
5662
  namespaced: async () => [],
4421
5663
  });
4422
- assert.deepEqual(seen, [undefined, 6], "first request carries no cap, the top-up does");
4423
- assert.deepEqual(results.map((hit) => hit.path), [
4424
- "facts/one.md",
4425
- "facts/two.md",
4426
- "facts/three.md",
4427
- ]);
5664
+ assert.deepEqual(
5665
+ seen,
5666
+ [undefined, 6],
5667
+ "first request carries no cap, the top-up does",
5668
+ );
5669
+ assert.deepEqual(
5670
+ results.map((hit) => hit.path),
5671
+ ["facts/one.md", "facts/two.md", "facts/three.md"],
5672
+ );
4428
5673
  });
4429
5674
 
4430
5675
  test("HTTP memory search keeps topping up past four excluded pages", async () => {
@@ -4440,7 +5685,8 @@ test("HTTP memory search keeps topping up past four excluded pages", async () =>
4440
5685
  { path: "facts/two.md", score: 0.09, snippet: "two" },
4441
5686
  ];
4442
5687
  const seen: Array<number | undefined> = [];
4443
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5688
+ const { runScopedMemorySearch } =
5689
+ await import("./access-memory-search-fanout.js");
4444
5690
  const results = await runScopedMemorySearch({
4445
5691
  query: "q",
4446
5692
  budget: 2,
@@ -4448,21 +5694,30 @@ test("HTTP memory search keeps topping up past four excluded pages", async () =>
4448
5694
  authorizeScope: () => {},
4449
5695
  namespacesEnabled: false,
4450
5696
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
5697
+ filterPrivate: async (hits) => hits,
4451
5698
  flatCorpus: async (limit) => {
4452
5699
  seen.push(limit);
4453
5700
  return corpus.slice(0, limit);
4454
5701
  },
4455
5702
  namespaced: async () => [],
4456
5703
  });
4457
- assert.deepEqual(seen, [2, 4, 8, 16, 32, 64], "doubles past four rounds until the budget is met");
4458
- assert.deepEqual(results.map((hit) => hit.path), ["facts/one.md", "facts/two.md"]);
5704
+ assert.deepEqual(
5705
+ seen,
5706
+ [2, 4, 8, 16, 32, 64],
5707
+ "doubles past four rounds until the budget is met",
5708
+ );
5709
+ assert.deepEqual(
5710
+ results.map((hit) => hit.path),
5711
+ ["facts/one.md", "facts/two.md"],
5712
+ );
4459
5713
  });
4460
5714
 
4461
5715
  test("HTTP memory search stops at the candidate ceiling", async () => {
4462
5716
  // A backend that always returns a full page of excluded hits must not be
4463
5717
  // asked for an unbounded number of candidates.
4464
5718
  const seen: Array<number | undefined> = [];
4465
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5719
+ const { runScopedMemorySearch } =
5720
+ await import("./access-memory-search-fanout.js");
4466
5721
  const results = await runScopedMemorySearch({
4467
5722
  query: "q",
4468
5723
  budget: 1,
@@ -4470,6 +5725,7 @@ test("HTTP memory search stops at the candidate ceiling", async () => {
4470
5725
  authorizeScope: () => {},
4471
5726
  namespacesEnabled: false,
4472
5727
  isExcluded: () => true,
5728
+ filterPrivate: async (hits) => hits,
4473
5729
  flatCorpus: async (limit) => {
4474
5730
  seen.push(limit);
4475
5731
  return Array.from({ length: limit ?? 0 }, (_, index) => ({
@@ -4484,7 +5740,11 @@ test("HTTP memory search stops at the candidate ceiling", async () => {
4484
5740
  // The cap is ABSOLUTE, not a multiple of the budget: a small request whose
4485
5741
  // hits are all excluded still walks far enough to prove the corpus holds
4486
5742
  // nothing for it, then stops on the backend-safety bound.
4487
- assert.equal(seen.at(-1), 25_000, "the last request lands exactly on the cap");
5743
+ assert.equal(
5744
+ seen.at(-1),
5745
+ 25_000,
5746
+ "the last request lands exactly on the cap",
5747
+ );
4488
5748
  assert.ok(seen.length < 20, "and the loop terminates");
4489
5749
  });
4490
5750
 
@@ -4492,7 +5752,8 @@ test("HTTP memory search scales its ceiling above a large requested budget", asy
4492
5752
  // A fixed 1000 ceiling would stop at or below a 2000-result request, so one
4493
5753
  // excluded hit in the first page could never be replaced.
4494
5754
  const seen: Array<number | undefined> = [];
4495
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5755
+ const { runScopedMemorySearch } =
5756
+ await import("./access-memory-search-fanout.js");
4496
5757
  const results = await runScopedMemorySearch({
4497
5758
  query: "q",
4498
5759
  budget: 2_000,
@@ -4500,6 +5761,7 @@ test("HTTP memory search scales its ceiling above a large requested budget", asy
4500
5761
  authorizeScope: () => {},
4501
5762
  namespacesEnabled: false,
4502
5763
  isExcluded: (memoryPath) => memoryPath === "artifacts/a.md",
5764
+ filterPrivate: async (hits) => hits,
4503
5765
  flatCorpus: async (limit) => {
4504
5766
  seen.push(limit);
4505
5767
  const size = limit ?? 0;
@@ -4515,7 +5777,11 @@ test("HTTP memory search scales its ceiling above a large requested budget", asy
4515
5777
  namespaced: async () => [],
4516
5778
  });
4517
5779
  assert.deepEqual(seen, [2_000, 4_000], "tops up past the requested budget");
4518
- assert.equal(results.length, 2_000, "the requested page is delivered in full");
5780
+ assert.equal(
5781
+ results.length,
5782
+ 2_000,
5783
+ "the requested page is delivered in full",
5784
+ );
4519
5785
  });
4520
5786
 
4521
5787
  test("HTTP memory search keeps the backend page size when no limit is named", async () => {
@@ -4523,7 +5789,8 @@ test("HTTP memory search keeps the backend page size when no limit is named", as
4523
5789
  // Treating the cap as the target made a FULL page look short and reissued
4524
5790
  // the query, returning 8 where the same request used to return 6.
4525
5791
  const seen: Array<number | undefined> = [];
4526
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5792
+ const { runScopedMemorySearch } =
5793
+ await import("./access-memory-search-fanout.js");
4527
5794
  const results = await runScopedMemorySearch({
4528
5795
  query: "q",
4529
5796
  budget: 8,
@@ -4531,6 +5798,7 @@ test("HTTP memory search keeps the backend page size when no limit is named", as
4531
5798
  authorizeScope: () => {},
4532
5799
  namespacesEnabled: false,
4533
5800
  isExcluded: () => false,
5801
+ filterPrivate: async (hits) => hits,
4534
5802
  flatCorpus: async (limit) => {
4535
5803
  seen.push(limit);
4536
5804
  const size = limit ?? 6;
@@ -4550,7 +5818,8 @@ test("HTTP memory search still tops up a thinned default page", async () => {
4550
5818
  // Same shape, but an excluded hit really does shorten the page: the target
4551
5819
  // is the backend page size, so it tops up to restore it.
4552
5820
  const seen: Array<number | undefined> = [];
4553
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5821
+ const { runScopedMemorySearch } =
5822
+ await import("./access-memory-search-fanout.js");
4554
5823
  const results = await runScopedMemorySearch({
4555
5824
  query: "q",
4556
5825
  budget: 8,
@@ -4558,6 +5827,7 @@ test("HTTP memory search still tops up a thinned default page", async () => {
4558
5827
  authorizeScope: () => {},
4559
5828
  namespacesEnabled: false,
4560
5829
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
5830
+ filterPrivate: async (hits) => hits,
4561
5831
  flatCorpus: async (limit) => {
4562
5832
  seen.push(limit);
4563
5833
  const size = limit ?? 6;
@@ -4572,7 +5842,11 @@ test("HTTP memory search still tops up a thinned default page", async () => {
4572
5842
  },
4573
5843
  namespaced: async () => [],
4574
5844
  });
4575
- assert.deepEqual(seen, [undefined, 12], "tops up against the backend page size");
5845
+ assert.deepEqual(
5846
+ seen,
5847
+ [undefined, 12],
5848
+ "tops up against the backend page size",
5849
+ );
4576
5850
  assert.equal(results.length, 6);
4577
5851
  });
4578
5852
 
@@ -4580,9 +5854,8 @@ test("search keeps archived memories but still excludes the dedicated surfaces",
4580
5854
  // The lifecycle reserves archived memories for explicit read/search
4581
5855
  // surfaces, so a ranked search must return them - while artifacts, activity
4582
5856
  // digests, and meeting records stay on their own paths.
4583
- const { isSearchExcludedPath, isGenericRecallExcludedPath } = await import(
4584
- "./orchestration/generic-recall-paths.js"
4585
- );
5857
+ const { isSearchExcludedPath, isGenericRecallExcludedPath } =
5858
+ await import("./orchestration/generic-recall-paths.js");
4586
5859
  assert.equal(
4587
5860
  isSearchExcludedPath("archive/2026-01/fact-1.md"),
4588
5861
  false,
@@ -4597,7 +5870,8 @@ test("search keeps archived memories but still excludes the dedicated surfaces",
4597
5870
  assert.equal(isSearchExcludedPath("artifacts/report.md"), true);
4598
5871
  assert.equal(isGenericRecallExcludedPath("artifacts/report.md"), true);
4599
5872
  // And the wiring uses the search predicate, so an archived hit survives.
4600
- const { runScopedMemorySearch } = await import("./access-memory-search-fanout.js");
5873
+ const { runScopedMemorySearch } =
5874
+ await import("./access-memory-search-fanout.js");
4601
5875
  const results = await runScopedMemorySearch({
4602
5876
  query: "q",
4603
5877
  budget: 5,
@@ -4605,17 +5879,22 @@ test("search keeps archived memories but still excludes the dedicated surfaces",
4605
5879
  authorizeScope: () => {},
4606
5880
  namespacesEnabled: false,
4607
5881
  isExcluded: (memoryPath) => isSearchExcludedPath(memoryPath),
5882
+ filterPrivate: async (hits) => hits,
4608
5883
  flatCorpus: async () => [
4609
5884
  { path: "archive/2026-01/fact-1.md", score: 0.9, snippet: "archived" },
4610
5885
  { path: "artifacts/report.md", score: 0.8, snippet: "artifact" },
4611
5886
  ],
4612
5887
  namespaced: async () => [],
4613
5888
  });
4614
- assert.deepEqual(results.map((hit) => hit.path), ["archive/2026-01/fact-1.md"]);
5889
+ assert.deepEqual(
5890
+ results.map((hit) => hit.path),
5891
+ ["archive/2026-01/fact-1.md"],
5892
+ );
4615
5893
  });
4616
5894
 
4617
5895
  test("a collection-qualified QMD path still hits the dedicated-surface exclusions", async () => {
4618
- const { isSearchExcludedPath } = await import("./orchestration/generic-recall-paths.js");
5896
+ const { isSearchExcludedPath } =
5897
+ await import("./orchestration/generic-recall-paths.js");
4619
5898
  // A flat-corpus QMD transport returns `qmd://<collection>/<path>` or
4620
5899
  // `<collection>/<path>`. The activity predicate is root-aware, so an
4621
5900
  // un-stripped prefix reads as a NESTED path and the digest leaks into
@@ -4633,12 +5912,20 @@ test("a collection-qualified QMD path still hits the dedicated-surface exclusion
4633
5912
  // A namespaced search rewrites hits to absolute paths beneath the
4634
5913
  // namespace's own storage root; the digest there must be excluded too.
4635
5914
  assert.equal(
4636
- isSearchExcludedPath("/memory/namespaces/team/activity/2026-08-02.md", policy, "qmd"),
5915
+ isSearchExcludedPath(
5916
+ "/memory/namespaces/team/activity/2026-08-02.md",
5917
+ policy,
5918
+ "qmd",
5919
+ ),
4637
5920
  true,
4638
5921
  "a digest under a namespace root is still a digest",
4639
5922
  );
4640
5923
  assert.equal(
4641
- isSearchExcludedPath("/memory/namespaces/team/facts/proj/activity/2026-08-02.md", policy, "qmd"),
5924
+ isSearchExcludedPath(
5925
+ "/memory/namespaces/team/facts/proj/activity/2026-08-02.md",
5926
+ policy,
5927
+ "qmd",
5928
+ ),
4642
5929
  false,
4643
5930
  "and an ordinary nested memory under that root stays searchable",
4644
5931
  );
@@ -4696,10 +5983,15 @@ test("search keeps paging while the backend page is full of excluded hits", asyn
4696
5983
  // indistinguishable from "there is nothing else": a 1,000-row request whose
4697
5984
  // first 4,000 hits are artifacts stopped at 4,000 and answered empty while
4698
5985
  // valid memories sat at rank 4,001.
4699
- const { searchWithGenericExclusion } = await import("./access-memory-search-fanout.js");
5986
+ const { searchWithGenericExclusion } =
5987
+ await import("./access-memory-search-fanout.js");
4700
5988
  const corpus = [
4701
- ...Array.from({ length: 4_000 }, (_, index) => ({ path: `artifacts/a-${index}.md` })),
4702
- ...Array.from({ length: 50, }, (_, index) => ({ path: `facts/f-${index}.md` })),
5989
+ ...Array.from({ length: 4_000 }, (_, index) => ({
5990
+ path: `artifacts/a-${index}.md`,
5991
+ })),
5992
+ ...Array.from({ length: 50 }, (_, index) => ({
5993
+ path: `facts/f-${index}.md`,
5994
+ })),
4703
5995
  ];
4704
5996
  const limits: Array<number | undefined> = [];
4705
5997
  const results = await searchWithGenericExclusion({
@@ -4709,30 +6001,66 @@ test("search keeps paging while the backend page is full of excluded hits", asyn
4709
6001
  limits.push(limit);
4710
6002
  return corpus.slice(0, limit ?? corpus.length);
4711
6003
  },
6004
+ filterPrivate: async (hits) => hits,
4712
6005
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
4713
6006
  });
4714
- assert.equal(results.length, 50, "the memories behind the excluded block are returned");
4715
- assert.ok(limits.length > 1, "and it took more than the first page to reach them");
6007
+ assert.equal(
6008
+ results.length,
6009
+ 50,
6010
+ "the memories behind the excluded block are returned",
6011
+ );
6012
+ assert.ok(
6013
+ limits.length > 1,
6014
+ "and it took more than the first page to reach them",
6015
+ );
4716
6016
  assert.ok(
4717
6017
  limits.every((limit) => (limit ?? 0) <= 25_000),
4718
6018
  "while still respecting the absolute backend cap",
4719
6019
  );
4720
6020
  });
4721
6021
 
6022
+ test("search removes excluded paths before private-memory resolution", async () => {
6023
+ const { searchWithGenericExclusion } = await import("./access-memory-search-fanout.js");
6024
+ const resolved: string[] = [];
6025
+ const results = await searchWithGenericExclusion({
6026
+ budget: 1,
6027
+ sendInitialLimit: true,
6028
+ search: async () => [
6029
+ { path: "artifacts/private.md" },
6030
+ { path: "facts/visible.md" },
6031
+ ],
6032
+ filterPrivate: async (hits) => {
6033
+ resolved.push(...hits.map((hit) => hit.path));
6034
+ return hits;
6035
+ },
6036
+ isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
6037
+ });
6038
+ assert.deepEqual(resolved, ["facts/visible.md"]);
6039
+ assert.deepEqual(results, [{ path: "facts/visible.md" }]);
6040
+ });
6041
+
4722
6042
  test("a budget above the backend cap still gets the rows it asked for", async () => {
4723
6043
  // The cap protects the backend from an unbounded walk; it must never sit
4724
6044
  // BELOW an explicit request, or a large search returns a short page for a
4725
6045
  // count the operation deliberately supports.
4726
- const { searchWithGenericExclusion } = await import("./access-memory-search-fanout.js");
6046
+ const { searchWithGenericExclusion } =
6047
+ await import("./access-memory-search-fanout.js");
4727
6048
  const corpus = [
4728
6049
  { path: "artifacts/excluded.md" },
4729
- ...Array.from({ length: 30_000 }, (_, index) => ({ path: `facts/f-${index}.md` })),
6050
+ ...Array.from({ length: 30_000 }, (_, index) => ({
6051
+ path: `facts/f-${index}.md`,
6052
+ })),
4730
6053
  ];
4731
6054
  const results = await searchWithGenericExclusion({
4732
6055
  budget: 30_000,
4733
6056
  sendInitialLimit: true,
4734
6057
  search: async (limit) => corpus.slice(0, limit ?? corpus.length),
6058
+ filterPrivate: async (hits) => hits,
4735
6059
  isExcluded: (memoryPath) => memoryPath.startsWith("artifacts/"),
4736
6060
  });
4737
- assert.equal(results.length, 30_000, "the excluded hit was replaced, not subtracted");
6061
+ assert.equal(
6062
+ results.length,
6063
+ 30_000,
6064
+ "the excluded hit was replaced, not subtracted",
6065
+ );
4738
6066
  });