@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
@@ -0,0 +1,1731 @@
1
+ import assert from "node:assert/strict";
2
+ import { link, lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { test } from "node:test";
6
+
7
+ import { StorageManager } from "../storage.js";
8
+ import type { HeldFileLockController } from "../utils/serialize-mutations.js";
9
+ import { SupportPassportCardService } from "./card-service.js";
10
+ import { SupportPassportError } from "./errors.js";
11
+ import { SupportPassportGrantService } from "./grant-service.js";
12
+ import { SupportPassportGrantStore } from "./grant-store.js";
13
+ import { SupportPassportModelAdapter, type SupportPassportModelRoute } from "./model-adapter.js";
14
+ import {
15
+ type SupportPassportModelAuditRecord,
16
+ SupportPassportModelAuditRecordSchema,
17
+ SupportPassportModelAuditStore,
18
+ hashSupportPassportAuditValues,
19
+ } from "./model-audit.js";
20
+ import {
21
+ SupportPassportDraftService,
22
+ SupportPassportQuestionService,
23
+ computeSupportPassportSourceRevision,
24
+ } from "./model-service.js";
25
+
26
+ async function flushAudit(): Promise<void> {
27
+ await new Promise<void>((resolve) => setImmediate(resolve));
28
+ }
29
+
30
+ function makeAuditRecord(overrides: Partial<SupportPassportModelAuditRecord> = {}): SupportPassportModelAuditRecord {
31
+ return SupportPassportModelAuditRecordSchema.parse({
32
+ schemaVersion: 1,
33
+ operation: "draft_cards",
34
+ actorHash: "a".repeat(64),
35
+ subjectIdsHash: "b".repeat(64),
36
+ modelUsed: "gateway/test-model",
37
+ route: "gateway",
38
+ outputSchemaVersion: 1,
39
+ outcome: "success",
40
+ occurredAt: "2026-08-11T12:00:00.000Z",
41
+ latencyMs: 25,
42
+ ...overrides,
43
+ });
44
+ }
45
+
46
+ function sourceRevision(memoryId: string, content: string) {
47
+ return [{ memoryId, revision: computeSupportPassportSourceRevision(content) }];
48
+ }
49
+
50
+ async function makeSubject() {
51
+ StorageManager.clearAllStaticCaches();
52
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-service-"));
53
+ const aliceStorage = new StorageManager(path.join(root, "alice"));
54
+ await aliceStorage.ensureDirectories();
55
+ const now = () => new Date("2026-08-11T12:00:00.000Z");
56
+ const resolveOwner = async (principal: string) => {
57
+ if (principal !== "owner:alice") throw new Error("unknown test principal");
58
+ return { principal, namespace: "alice", storage: aliceStorage };
59
+ };
60
+ const cardService = new SupportPassportCardService({ resolveOwner, now });
61
+ const grantStore = new SupportPassportGrantStore({ memoryDir: path.join(root, "shared"), now });
62
+ const grantService = new SupportPassportGrantService({
63
+ grantStore,
64
+ resolveOwner,
65
+ resolveNamespace: async () => aliceStorage,
66
+ now,
67
+ });
68
+ return {
69
+ root,
70
+ now,
71
+ aliceStorage,
72
+ resolveOwner,
73
+ cardService,
74
+ grantService,
75
+ cleanup: async () => {
76
+ StorageManager.clearAllStaticCaches();
77
+ await rm(root, { recursive: true, force: true });
78
+ },
79
+ };
80
+ }
81
+
82
+ test("drafting reads only selected memories and persists pending cards with a content-free audit", async () => {
83
+ const subject = await makeSubject();
84
+ try {
85
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
86
+ source: "test",
87
+ });
88
+ const unselected = await subject.aliceStorage.writeMemory("preference", "Do not send this note.", {
89
+ source: "test",
90
+ });
91
+ const exactReads: string[] = [];
92
+ const getMemoryById = subject.aliceStorage.getMemoryById.bind(subject.aliceStorage);
93
+ subject.aliceStorage.getMemoryById = async (memoryId: string) => {
94
+ exactReads.push(memoryId);
95
+ return await getMemoryById(memoryId);
96
+ };
97
+ const calls: Array<Array<{ role: string; content: string }>> = [];
98
+ const route: SupportPassportModelRoute = {
99
+ kind: "gateway",
100
+ invoke: async (messages) => {
101
+ assert.deepEqual(exactReads, [selected.id]);
102
+ calls.push(messages);
103
+ return {
104
+ modelUsed: "gateway/test-model",
105
+ content: JSON.stringify({
106
+ cards: [
107
+ {
108
+ title: "Plan changes",
109
+ statement: "Tell me before plans change.",
110
+ category: "transitions",
111
+ sourceMemoryIds: [selected.id],
112
+ },
113
+ ],
114
+ }),
115
+ };
116
+ },
117
+ };
118
+ const records: SupportPassportModelAuditRecord[] = [];
119
+ const service = new SupportPassportDraftService({
120
+ cardService: subject.cardService,
121
+ modelAdapter: new SupportPassportModelAdapter({ routes: [route] }),
122
+ resolveOwner: subject.resolveOwner,
123
+ audit: {
124
+ record: async (record) => {
125
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
126
+ },
127
+ },
128
+ now: subject.now,
129
+ });
130
+
131
+ const cards = await service.draftCards({
132
+ principal: "owner:alice",
133
+ sourceMemoryIds: [selected.id],
134
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
135
+ consent: true,
136
+ });
137
+ await flushAudit();
138
+
139
+ assert.equal(cards.length, 1);
140
+ assert.equal(cards[0]?.status, "pending_review");
141
+ assert.equal(calls.length, 1);
142
+ assert.equal(calls[0]?.[1]?.content.includes(selected.id), true);
143
+ assert.equal(calls[0]?.[1]?.content.includes(unselected.id), false);
144
+ const firstCard = cards[0];
145
+ assert.ok(firstCard);
146
+ const stored = await subject.aliceStorage.getMemoryById(firstCard.cardId);
147
+ assert.deepEqual(stored?.frontmatter.lineage, [selected.id]);
148
+ assert.equal(records.length, 1);
149
+ assert.equal(records[0]?.outputSchemaVersion, 1);
150
+ assert.equal(records[0]?.outcome, "success");
151
+ const serializedAudit = JSON.stringify(records[0]);
152
+ assert.equal(serializedAudit.includes("owner:alice"), false);
153
+ assert.equal(serializedAudit.includes(selected.id), false);
154
+ assert.equal(serializedAudit.includes("Tell me before"), false);
155
+ } finally {
156
+ await subject.cleanup();
157
+ }
158
+ });
159
+
160
+ test("drafting persists into the owner scope used for source reads", async () => {
161
+ StorageManager.clearAllStaticCaches();
162
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-owner-scope-"));
163
+ const aliceStorage = new StorageManager(path.join(root, "alice"));
164
+ const bobStorage = new StorageManager(path.join(root, "bob"));
165
+ try {
166
+ await Promise.all([aliceStorage.ensureDirectories(), bobStorage.ensureDirectories()]);
167
+ const selected = await aliceStorage.writeMemory("preference", "Tell me before plans change.", {
168
+ source: "test",
169
+ });
170
+ let ownerResolutions = 0;
171
+ const resolveOwner = async (principal: string) => {
172
+ ownerResolutions += 1;
173
+ return ownerResolutions === 1
174
+ ? { principal, namespace: "alice", storage: aliceStorage }
175
+ : { principal, namespace: "bob", storage: bobStorage };
176
+ };
177
+ const cardService = new SupportPassportCardService({ resolveOwner });
178
+ const service = new SupportPassportDraftService({
179
+ cardService,
180
+ modelAdapter: new SupportPassportModelAdapter({
181
+ routes: [
182
+ {
183
+ kind: "local",
184
+ invoke: async () => ({
185
+ modelUsed: "local/test-model",
186
+ content: JSON.stringify({
187
+ cards: [
188
+ {
189
+ title: "Plan changes",
190
+ statement: "Tell me before plans change.",
191
+ category: "transitions",
192
+ sourceMemoryIds: [selected.id],
193
+ },
194
+ ],
195
+ }),
196
+ }),
197
+ },
198
+ ],
199
+ }),
200
+ resolveOwner,
201
+ audit: { record: async () => undefined },
202
+ });
203
+
204
+ const cards = await service.draftCards({
205
+ principal: "owner:alice",
206
+ sourceMemoryIds: [selected.id],
207
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
208
+ consent: true,
209
+ });
210
+
211
+ assert.equal(ownerResolutions, 1);
212
+ assert.equal(cards.length, 1);
213
+ assert.ok(await aliceStorage.getMemoryById(cards[0]?.cardId ?? ""));
214
+ assert.deepEqual(await bobStorage.readAllMemories(), []);
215
+ } finally {
216
+ StorageManager.clearAllStaticCaches();
217
+ await rm(root, { recursive: true, force: true });
218
+ }
219
+ });
220
+
221
+ test("drafting never sends another owner's support card to a model", async () => {
222
+ StorageManager.clearAllStaticCaches();
223
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-private-source-"));
224
+ const storage = new StorageManager(path.join(root, "shared"));
225
+ try {
226
+ await storage.ensureDirectories();
227
+ const resolveOwner = async (principal: string) => ({ principal, namespace: "team", storage });
228
+ const cardService = new SupportPassportCardService({ resolveOwner });
229
+ const bobDraft = await cardService.createManualDraft({
230
+ principal: "owner:bob",
231
+ title: "Private support",
232
+ statement: "Do not disclose this private support statement.",
233
+ category: "other",
234
+ reviewBy: "2026-09-01T12:00:00.000Z",
235
+ });
236
+ const bobCard = await cardService.approveCard({
237
+ principal: "owner:bob",
238
+ cardId: bobDraft.cardId,
239
+ expectedRevision: bobDraft.revision,
240
+ });
241
+ const stored = await storage.getMemoryById(bobCard.cardId);
242
+ assert.ok(stored);
243
+ let modelCalls = 0;
244
+ const service = new SupportPassportDraftService({
245
+ cardService,
246
+ modelAdapter: new SupportPassportModelAdapter({
247
+ routes: [
248
+ {
249
+ kind: "local",
250
+ invoke: async () => {
251
+ modelCalls += 1;
252
+ return null;
253
+ },
254
+ },
255
+ ],
256
+ }),
257
+ resolveOwner,
258
+ audit: { record: async () => undefined },
259
+ });
260
+
261
+ await assert.rejects(
262
+ service.draftCards({
263
+ principal: "owner:alice",
264
+ sourceMemoryIds: [bobCard.cardId],
265
+ sourceMemoryRevisions: [
266
+ {
267
+ memoryId: bobCard.cardId,
268
+ revision: computeSupportPassportSourceRevision(
269
+ stored.content,
270
+ stored.frontmatter.structuredAttributes,
271
+ ),
272
+ },
273
+ ],
274
+ consent: true,
275
+ }),
276
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input",
277
+ );
278
+ assert.equal(modelCalls, 0);
279
+ assert.deepEqual(await cardService.listCards({ principal: "owner:alice" }), []);
280
+ } finally {
281
+ StorageManager.clearAllStaticCaches();
282
+ await rm(root, { recursive: true, force: true });
283
+ }
284
+ });
285
+
286
+ test("drafting rejects a resolved principal that differs from the authenticated principal", async () => {
287
+ const subject = await makeSubject();
288
+ try {
289
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
290
+ source: "test",
291
+ });
292
+ let modelCalls = 0;
293
+ const resolveOwner = async () => ({
294
+ principal: "authenticated:alice",
295
+ namespace: "alice",
296
+ storage: subject.aliceStorage,
297
+ });
298
+ const service = new SupportPassportDraftService({
299
+ cardService: new SupportPassportCardService({ resolveOwner }),
300
+ modelAdapter: new SupportPassportModelAdapter({
301
+ routes: [
302
+ {
303
+ kind: "local",
304
+ invoke: async () => {
305
+ modelCalls += 1;
306
+ return null;
307
+ },
308
+ },
309
+ ],
310
+ }),
311
+ resolveOwner,
312
+ audit: { record: async () => undefined },
313
+ now: subject.now,
314
+ });
315
+
316
+ await assert.rejects(
317
+ service.draftCards({
318
+ principal: "request:alice",
319
+ sourceMemoryIds: [selected.id],
320
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
321
+ consent: true,
322
+ }),
323
+ (error: unknown) => error instanceof SupportPassportError && error.code === "card_data_invalid"
324
+ );
325
+ assert.equal(modelCalls, 0);
326
+ assert.deepEqual(
327
+ (await subject.aliceStorage.readAllMemories()).map((memory) => memory.frontmatter.id),
328
+ [selected.id]
329
+ );
330
+ } finally {
331
+ await subject.cleanup();
332
+ }
333
+ });
334
+
335
+ test("drafting rejects a non-canonical authenticated owner scope before model disclosure", async () => {
336
+ const subject = await makeSubject();
337
+ try {
338
+ let modelCalls = 0;
339
+ const service = new SupportPassportDraftService({
340
+ cardService: subject.cardService,
341
+ modelAdapter: new SupportPassportModelAdapter({
342
+ routes: [
343
+ {
344
+ kind: "local",
345
+ invoke: async () => {
346
+ modelCalls += 1;
347
+ return null;
348
+ },
349
+ },
350
+ ],
351
+ }),
352
+ resolveOwner: async (principal) => ({
353
+ principal,
354
+ namespace: " alice ",
355
+ storage: subject.aliceStorage,
356
+ }),
357
+ audit: { record: async () => undefined },
358
+ now: subject.now,
359
+ });
360
+
361
+ await assert.rejects(
362
+ service.draftCards({
363
+ principal: "owner:alice",
364
+ sourceMemoryIds: ["selected-memory"],
365
+ sourceMemoryRevisions: [{ memoryId: "selected-memory", revision: "a".repeat(64) }],
366
+ consent: true,
367
+ }),
368
+ (error: unknown) => error instanceof SupportPassportError && error.code === "card_data_invalid"
369
+ );
370
+ assert.equal(modelCalls, 0);
371
+ } finally {
372
+ await subject.cleanup();
373
+ }
374
+ });
375
+
376
+ test("model failures produce a content-free audit record with an error class", async () => {
377
+ const subject = await makeSubject();
378
+ try {
379
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
380
+ source: "test",
381
+ });
382
+ const records: SupportPassportModelAuditRecord[] = [];
383
+ const service = new SupportPassportDraftService({
384
+ cardService: subject.cardService,
385
+ modelAdapter: new SupportPassportModelAdapter({
386
+ routes: [{ kind: "direct", invoke: async () => ({ content: "not-json", modelUsed: "openai/gpt-test" }) }],
387
+ }),
388
+ resolveOwner: subject.resolveOwner,
389
+ audit: {
390
+ record: async (record) => {
391
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
392
+ },
393
+ },
394
+ now: subject.now,
395
+ });
396
+
397
+ await assert.rejects(
398
+ service.draftCards({
399
+ principal: "owner:alice",
400
+ sourceMemoryIds: [selected.id],
401
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
402
+ consent: true,
403
+ }),
404
+ (error: unknown) => error instanceof SupportPassportError && error.code === "model_output_invalid"
405
+ );
406
+ await flushAudit();
407
+ assert.equal(records.length, 1);
408
+ assert.equal(records[0]?.outcome, "error");
409
+ assert.equal(records[0]?.errorClass, "model_output_invalid");
410
+ assert.equal(records[0]?.outputSchemaVersion, 1);
411
+ const serializedAudit = JSON.stringify(records[0]);
412
+ assert.equal(serializedAudit.includes(selected.id), false);
413
+ assert.equal(serializedAudit.includes("Tell me before"), false);
414
+ assert.equal(serializedAudit.includes("not-json"), false);
415
+ } finally {
416
+ await subject.cleanup();
417
+ }
418
+ });
419
+
420
+ test("drafting rejects missing sources and absent consent before the model runs", async () => {
421
+ const subject = await makeSubject();
422
+ try {
423
+ let calls = 0;
424
+ const modelAdapter = new SupportPassportModelAdapter({
425
+ routes: [
426
+ {
427
+ kind: "local",
428
+ invoke: async () => {
429
+ calls += 1;
430
+ return null;
431
+ },
432
+ },
433
+ ],
434
+ });
435
+ const service = new SupportPassportDraftService({
436
+ cardService: subject.cardService,
437
+ modelAdapter,
438
+ resolveOwner: subject.resolveOwner,
439
+ audit: { record: async () => undefined },
440
+ now: subject.now,
441
+ });
442
+
443
+ await assert.rejects(
444
+ (
445
+ service.draftCards as unknown as (input: {
446
+ principal: string;
447
+ sourceMemoryIds: string[];
448
+ consent: boolean;
449
+ }) => Promise<unknown>
450
+ )({
451
+ principal: "owner:alice",
452
+ sourceMemoryIds: ["missing-memory"],
453
+ consent: true,
454
+ }),
455
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
456
+ );
457
+
458
+ await assert.rejects(
459
+ service.draftCards({
460
+ principal: "owner:alice",
461
+ sourceMemoryIds: ["missing-memory"],
462
+ sourceMemoryRevisions: [{ memoryId: "missing-memory", revision: "a".repeat(64) }],
463
+ consent: true,
464
+ }),
465
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
466
+ );
467
+ await flushAudit();
468
+ await assert.rejects(
469
+ service.draftCards({
470
+ principal: "owner:alice",
471
+ sourceMemoryIds: ["missing-memory"],
472
+ sourceMemoryRevisions: [{ memoryId: "missing-memory", revision: "a".repeat(64) }],
473
+ consent: false,
474
+ }),
475
+ (error: unknown) => error instanceof SupportPassportError && error.code === "consent_required"
476
+ );
477
+ assert.equal(calls, 0);
478
+ } finally {
479
+ await subject.cleanup();
480
+ }
481
+ });
482
+
483
+ test("drafting stops before owner resolution when the request is already cancelled", async () => {
484
+ const subject = await makeSubject();
485
+ try {
486
+ let ownerResolutions = 0;
487
+ let modelCalls = 0;
488
+ const service = new SupportPassportDraftService({
489
+ cardService: subject.cardService,
490
+ modelAdapter: new SupportPassportModelAdapter({
491
+ routes: [
492
+ {
493
+ kind: "local",
494
+ invoke: async () => {
495
+ modelCalls += 1;
496
+ return null;
497
+ },
498
+ },
499
+ ],
500
+ }),
501
+ resolveOwner: async (principal) => {
502
+ ownerResolutions += 1;
503
+ return await subject.resolveOwner(principal);
504
+ },
505
+ audit: { record: async () => undefined },
506
+ now: subject.now,
507
+ });
508
+ const controller = new AbortController();
509
+ controller.abort();
510
+
511
+ await assert.rejects(
512
+ service.draftCards({
513
+ principal: "owner:alice",
514
+ sourceMemoryIds: ["selected-memory"],
515
+ sourceMemoryRevisions: [{ memoryId: "selected-memory", revision: "a".repeat(64) }],
516
+ consent: true,
517
+ signal: controller.signal,
518
+ }),
519
+ (error: unknown) => error instanceof Error && error.name === "AbortError"
520
+ );
521
+ assert.equal(ownerResolutions, 0);
522
+ assert.equal(modelCalls, 0);
523
+ } finally {
524
+ await subject.cleanup();
525
+ }
526
+ });
527
+
528
+ test("drafting cancellation races a selected source read", async () => {
529
+ const subject = await makeSubject();
530
+ const sourceReadStarted = Promise.withResolvers<void>();
531
+ const releaseSourceRead = Promise.withResolvers<void>();
532
+ try {
533
+ const selected = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
534
+ source: "test",
535
+ });
536
+ const getMemoryById = subject.aliceStorage.getMemoryById.bind(subject.aliceStorage);
537
+ subject.aliceStorage.getMemoryById = async (memoryId: string) => {
538
+ sourceReadStarted.resolve();
539
+ await releaseSourceRead.promise;
540
+ return await getMemoryById(memoryId);
541
+ };
542
+ let modelCalls = 0;
543
+ const service = new SupportPassportDraftService({
544
+ cardService: subject.cardService,
545
+ modelAdapter: new SupportPassportModelAdapter({
546
+ routes: [
547
+ {
548
+ kind: "local",
549
+ invoke: async () => {
550
+ modelCalls += 1;
551
+ return null;
552
+ },
553
+ },
554
+ ],
555
+ }),
556
+ resolveOwner: subject.resolveOwner,
557
+ audit: { record: async () => undefined },
558
+ now: subject.now,
559
+ });
560
+ const controller = new AbortController();
561
+ const drafting = service.draftCards({
562
+ principal: "owner:alice",
563
+ sourceMemoryIds: [selected.id],
564
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
565
+ consent: true,
566
+ signal: controller.signal,
567
+ });
568
+ await sourceReadStarted.promise;
569
+ controller.abort();
570
+
571
+ await assert.rejects(
572
+ Promise.race([
573
+ drafting,
574
+ new Promise<never>((_resolve, reject) =>
575
+ setTimeout(() => reject(new Error("Draft cancellation waited for the source read.")), 100)
576
+ ),
577
+ ]),
578
+ (error: unknown) => error instanceof Error && error.name === "AbortError"
579
+ );
580
+ assert.equal(modelCalls, 0);
581
+ } finally {
582
+ releaseSourceRead.resolve();
583
+ await new Promise<void>((resolve) => setImmediate(resolve));
584
+ await subject.cleanup();
585
+ }
586
+ });
587
+
588
+ test("drafting rejects a memory changed after the owner reviewed it", async () => {
589
+ const subject = await makeSubject();
590
+ try {
591
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
592
+ source: "test",
593
+ });
594
+ const reviewedRevision = sourceRevision(selected.id, "Tell me before plans change.");
595
+ assert.equal(await subject.aliceStorage.updateMemory(selected.id, "Tell me only after plans change."), true);
596
+ let modelCalls = 0;
597
+ const service = new SupportPassportDraftService({
598
+ cardService: subject.cardService,
599
+ modelAdapter: new SupportPassportModelAdapter({
600
+ routes: [
601
+ {
602
+ kind: "local",
603
+ invoke: async () => {
604
+ modelCalls += 1;
605
+ return null;
606
+ },
607
+ },
608
+ ],
609
+ }),
610
+ resolveOwner: subject.resolveOwner,
611
+ audit: { record: async () => undefined },
612
+ now: subject.now,
613
+ });
614
+
615
+ await assert.rejects(
616
+ service.draftCards({
617
+ principal: "owner:alice",
618
+ sourceMemoryIds: [selected.id],
619
+ sourceMemoryRevisions: reviewedRevision,
620
+ consent: true,
621
+ }),
622
+ (error: unknown) =>
623
+ error instanceof SupportPassportError && error.code === "revision_conflict" && error.status === 409
624
+ );
625
+ assert.equal(modelCalls, 0);
626
+ } finally {
627
+ await subject.cleanup();
628
+ }
629
+ });
630
+
631
+ test("drafting treats empty structured attributes as absent", async () => {
632
+ const subject = await makeSubject();
633
+ try {
634
+ const content = "Keep this literal line.\n[Attributes: user-authored note]";
635
+ const selected = await subject.aliceStorage.writeMemory("preference", content, { source: "test" });
636
+ const selectedMemory = await subject.aliceStorage.getMemoryById(selected.id);
637
+ assert.ok(selectedMemory);
638
+ const storedContent = await readFile(selectedMemory.path, "utf8");
639
+ await writeFile(
640
+ selectedMemory.path,
641
+ storedContent.replace("\n---\n", "\nstructuredAttributes: {}\n---\n"),
642
+ );
643
+ const modelInputs: string[] = [];
644
+ const service = new SupportPassportDraftService({
645
+ cardService: subject.cardService,
646
+ modelAdapter: new SupportPassportModelAdapter({
647
+ routes: [
648
+ {
649
+ kind: "local",
650
+ invoke: async (messages) => {
651
+ modelInputs.push(messages[1]?.content ?? "");
652
+ return {
653
+ modelUsed: "local/test-model",
654
+ content: JSON.stringify({
655
+ cards: [
656
+ {
657
+ title: "Literal note",
658
+ statement: "Keep the complete note.",
659
+ category: "other",
660
+ sourceMemoryIds: [selected.id],
661
+ },
662
+ ],
663
+ }),
664
+ };
665
+ },
666
+ },
667
+ ],
668
+ }),
669
+ resolveOwner: subject.resolveOwner,
670
+ audit: { record: async () => undefined },
671
+ now: subject.now,
672
+ });
673
+
674
+ await service.draftCards({
675
+ principal: "owner:alice",
676
+ sourceMemoryIds: [selected.id],
677
+ sourceMemoryRevisions: [{
678
+ memoryId: selected.id,
679
+ revision: computeSupportPassportSourceRevision(content, {}),
680
+ }],
681
+ consent: true,
682
+ });
683
+
684
+ assert.equal(modelInputs[0]?.includes("[Attributes: user-authored note]"), true);
685
+ assert.equal(
686
+ computeSupportPassportSourceRevision(content, {}),
687
+ computeSupportPassportSourceRevision(content),
688
+ );
689
+ assert.notEqual(
690
+ computeSupportPassportSourceRevision(content),
691
+ computeSupportPassportSourceRevision("Keep this literal line.")
692
+ );
693
+ } finally {
694
+ await subject.cleanup();
695
+ }
696
+ });
697
+
698
+ test("drafting strips persisted structured attributes from model input and source revisions", async () => {
699
+ const subject = await makeSubject();
700
+ try {
701
+ const content = "Give me time to answer.";
702
+ const selected = await subject.aliceStorage.writeMemory("preference", content, {
703
+ source: "test",
704
+ structuredAttributes: { need: "processing time" },
705
+ });
706
+ const stored = await subject.aliceStorage.getMemoryById(selected.id);
707
+ assert.ok(stored);
708
+ const modelInputs: string[] = [];
709
+ const service = new SupportPassportDraftService({
710
+ cardService: subject.cardService,
711
+ modelAdapter: new SupportPassportModelAdapter({
712
+ routes: [
713
+ {
714
+ kind: "local",
715
+ invoke: async (messages) => {
716
+ modelInputs.push(messages[1]?.content ?? "");
717
+ return {
718
+ modelUsed: "local/test-model",
719
+ content: JSON.stringify({
720
+ cards: [
721
+ {
722
+ title: "Processing time",
723
+ statement: content,
724
+ category: "communication",
725
+ sourceMemoryIds: [selected.id],
726
+ },
727
+ ],
728
+ }),
729
+ };
730
+ },
731
+ },
732
+ ],
733
+ }),
734
+ resolveOwner: subject.resolveOwner,
735
+ audit: { record: async () => undefined },
736
+ now: subject.now,
737
+ });
738
+
739
+ await service.draftCards({
740
+ principal: "owner:alice",
741
+ sourceMemoryIds: [selected.id],
742
+ sourceMemoryRevisions: [
743
+ {
744
+ memoryId: selected.id,
745
+ revision: computeSupportPassportSourceRevision(stored.content, stored.frontmatter.structuredAttributes),
746
+ },
747
+ ],
748
+ consent: true,
749
+ });
750
+
751
+ assert.equal(modelInputs[0]?.includes("[Attributes:"), false);
752
+ assert.equal(modelInputs[0]?.includes(content), true);
753
+ } finally {
754
+ await subject.cleanup();
755
+ }
756
+ });
757
+
758
+ test("drafting validates the complete source snapshot before model disclosure", async () => {
759
+ const subject = await makeSubject();
760
+ try {
761
+ const first = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
762
+ source: "test",
763
+ });
764
+ const second = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
765
+ source: "test",
766
+ });
767
+ const getMemoryById = subject.aliceStorage.getMemoryById.bind(subject.aliceStorage);
768
+ let archivedFirst = false;
769
+ subject.aliceStorage.getMemoryById = async (memoryId: string) => {
770
+ if (memoryId === second.id && !archivedFirst) {
771
+ archivedFirst = true;
772
+ const current = await getMemoryById(first.id);
773
+ assert.ok(current);
774
+ assert.equal(
775
+ await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(current, {
776
+ status: "archived",
777
+ archivedAt: "2026-08-11T12:00:00.000Z",
778
+ updated: "2026-08-11T12:00:00.000Z",
779
+ }),
780
+ true
781
+ );
782
+ }
783
+ return await getMemoryById(memoryId);
784
+ };
785
+ let modelCalls = 0;
786
+ const service = new SupportPassportDraftService({
787
+ cardService: subject.cardService,
788
+ modelAdapter: new SupportPassportModelAdapter({
789
+ routes: [
790
+ {
791
+ kind: "local",
792
+ invoke: async () => {
793
+ modelCalls += 1;
794
+ return null;
795
+ },
796
+ },
797
+ ],
798
+ }),
799
+ resolveOwner: subject.resolveOwner,
800
+ audit: { record: async () => undefined },
801
+ now: subject.now,
802
+ });
803
+
804
+ await assert.rejects(
805
+ service.draftCards({
806
+ principal: "owner:alice",
807
+ sourceMemoryIds: [first.id, second.id],
808
+ sourceMemoryRevisions: [
809
+ ...sourceRevision(first.id, "Give me time to answer."),
810
+ ...sourceRevision(second.id, "Tell me before plans change."),
811
+ ],
812
+ consent: true,
813
+ }),
814
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
815
+ );
816
+ assert.equal(modelCalls, 0);
817
+ } finally {
818
+ await subject.cleanup();
819
+ }
820
+ });
821
+
822
+ test("drafting revalidates selected memories after the model returns", async () => {
823
+ const subject = await makeSubject();
824
+ try {
825
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
826
+ source: "test",
827
+ });
828
+ const records: SupportPassportModelAuditRecord[] = [];
829
+ const route: SupportPassportModelRoute = {
830
+ kind: "local",
831
+ invoke: async () => {
832
+ const current = await subject.aliceStorage.getMemoryById(selected.id);
833
+ assert.ok(current);
834
+ assert.equal(
835
+ await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(current, {
836
+ status: "archived",
837
+ archivedAt: "2026-08-11T12:00:00.000Z",
838
+ updated: "2026-08-11T12:00:00.000Z",
839
+ }),
840
+ true
841
+ );
842
+ return {
843
+ modelUsed: "local/test-model",
844
+ content: JSON.stringify({
845
+ cards: [
846
+ {
847
+ title: "Plan changes",
848
+ statement: "Tell me before plans change.",
849
+ category: "transitions",
850
+ sourceMemoryIds: [selected.id],
851
+ },
852
+ ],
853
+ }),
854
+ };
855
+ },
856
+ };
857
+ const service = new SupportPassportDraftService({
858
+ cardService: subject.cardService,
859
+ modelAdapter: new SupportPassportModelAdapter({ routes: [route] }),
860
+ resolveOwner: subject.resolveOwner,
861
+ audit: {
862
+ record: async (record) => {
863
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
864
+ },
865
+ },
866
+ now: subject.now,
867
+ });
868
+
869
+ await assert.rejects(
870
+ service.draftCards({
871
+ principal: "owner:alice",
872
+ sourceMemoryIds: [selected.id],
873
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
874
+ consent: true,
875
+ }),
876
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
877
+ );
878
+ assert.deepEqual(await subject.cardService.listCards({ principal: "owner:alice" }), []);
879
+ await flushAudit();
880
+ assert.equal(records.length, 1);
881
+ assert.equal(records[0]?.outcome, "error");
882
+ assert.equal(records[0]?.errorClass, "invalid_input");
883
+ } finally {
884
+ await subject.cleanup();
885
+ }
886
+ });
887
+
888
+ test("drafting rolls back when a selected memory changes during persistence", async () => {
889
+ const subject = await makeSubject();
890
+ try {
891
+ const selected = await subject.aliceStorage.writeMemory("preference", "Tell me before plans change.", {
892
+ source: "test",
893
+ });
894
+ const writeSealedMemory = subject.aliceStorage.writeSealedMemory.bind(subject.aliceStorage);
895
+ subject.aliceStorage.writeSealedMemory = async (...args) => {
896
+ const written = await writeSealedMemory(...args);
897
+ const source = await subject.aliceStorage.getMemoryById(selected.id);
898
+ assert.ok(source);
899
+ assert.equal(
900
+ await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(source, {
901
+ status: "archived",
902
+ archivedAt: "2026-08-11T12:00:00.000Z",
903
+ updated: "2026-08-11T12:00:00.000Z",
904
+ }),
905
+ true
906
+ );
907
+ return written;
908
+ };
909
+ const service = new SupportPassportDraftService({
910
+ cardService: subject.cardService,
911
+ modelAdapter: new SupportPassportModelAdapter({
912
+ routes: [
913
+ {
914
+ kind: "local",
915
+ invoke: async () => ({
916
+ modelUsed: "local/test-model",
917
+ content: JSON.stringify({
918
+ cards: [
919
+ {
920
+ title: "Plan changes",
921
+ statement: "Tell me before plans change.",
922
+ category: "transitions",
923
+ sourceMemoryIds: [selected.id],
924
+ },
925
+ ],
926
+ }),
927
+ }),
928
+ },
929
+ ],
930
+ }),
931
+ resolveOwner: subject.resolveOwner,
932
+ audit: { record: async () => undefined },
933
+ now: subject.now,
934
+ });
935
+
936
+ await assert.rejects(
937
+ service.draftCards({
938
+ principal: "owner:alice",
939
+ sourceMemoryIds: [selected.id],
940
+ sourceMemoryRevisions: sourceRevision(selected.id, "Tell me before plans change."),
941
+ consent: true,
942
+ }),
943
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
944
+ );
945
+ assert.deepEqual(await subject.cardService.listCards({ principal: "owner:alice" }), []);
946
+ } finally {
947
+ await subject.cleanup();
948
+ }
949
+ });
950
+
951
+ test("cancellation rolls back a generated batch and records an aborted audit", async () => {
952
+ const subject = await makeSubject();
953
+ try {
954
+ const selected = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
955
+ source: "test",
956
+ });
957
+ const controller = new AbortController();
958
+ const records: SupportPassportModelAuditRecord[] = [];
959
+ let committed = 0;
960
+ const writeSealedMemory = subject.aliceStorage.writeSealedMemory.bind(subject.aliceStorage);
961
+ let draftWrites = 0;
962
+ subject.aliceStorage.writeSealedMemory = async (...args) => {
963
+ const written = await writeSealedMemory(...args);
964
+ draftWrites += 1;
965
+ controller.abort(new Error("cancelled during draft persistence"));
966
+ return written;
967
+ };
968
+ const service = new SupportPassportDraftService({
969
+ cardService: subject.cardService,
970
+ modelAdapter: new SupportPassportModelAdapter({
971
+ routes: [
972
+ {
973
+ kind: "local",
974
+ invoke: async () => ({
975
+ modelUsed: "local/test-model",
976
+ content: JSON.stringify({
977
+ cards: [
978
+ {
979
+ title: "Processing time",
980
+ statement: "Give me time to answer.",
981
+ category: "communication",
982
+ sourceMemoryIds: [selected.id],
983
+ },
984
+ {
985
+ title: "Pause before repeating",
986
+ statement: "Pause before you repeat a question.",
987
+ category: "communication",
988
+ sourceMemoryIds: [selected.id],
989
+ },
990
+ ],
991
+ }),
992
+ }),
993
+ },
994
+ ],
995
+ }),
996
+ resolveOwner: subject.resolveOwner,
997
+ audit: {
998
+ record: async (record) => {
999
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
1000
+ },
1001
+ },
1002
+ now: subject.now,
1003
+ });
1004
+
1005
+ await assert.rejects(
1006
+ service.draftCards({
1007
+ principal: "owner:alice",
1008
+ sourceMemoryIds: [selected.id],
1009
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
1010
+ consent: true,
1011
+ signal: controller.signal,
1012
+ onCommitted: () => {
1013
+ committed += 1;
1014
+ },
1015
+ }),
1016
+ /cancelled during draft persistence/
1017
+ );
1018
+ assert.equal(draftWrites, 1);
1019
+ assert.equal(committed, 1);
1020
+ assert.deepEqual(await subject.cardService.listCards({ principal: "owner:alice" }), []);
1021
+ await flushAudit();
1022
+ assert.equal(records[0]?.errorClass, "aborted");
1023
+ } finally {
1024
+ await subject.cleanup();
1025
+ }
1026
+ });
1027
+
1028
+ test("an incomplete cancellation rollback records the storage conflict", async () => {
1029
+ const subject = await makeSubject();
1030
+ try {
1031
+ const selected = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
1032
+ source: "test",
1033
+ });
1034
+ const controller = new AbortController();
1035
+ const records: SupportPassportModelAuditRecord[] = [];
1036
+ const writeSealedMemory = subject.aliceStorage.writeSealedMemory.bind(subject.aliceStorage);
1037
+ subject.aliceStorage.writeSealedMemory = async (...args) => {
1038
+ const written = await writeSealedMemory(...args);
1039
+ controller.abort(new Error("cancelled during draft persistence"));
1040
+ return written;
1041
+ };
1042
+ subject.aliceStorage.writeMemoryFrontmatterIfUnchanged = async () => false;
1043
+ const service = new SupportPassportDraftService({
1044
+ cardService: subject.cardService,
1045
+ modelAdapter: new SupportPassportModelAdapter({
1046
+ routes: [
1047
+ {
1048
+ kind: "local",
1049
+ invoke: async () => ({
1050
+ modelUsed: "local/test-model",
1051
+ content: JSON.stringify({
1052
+ cards: [
1053
+ {
1054
+ title: "Processing time",
1055
+ statement: "Give me time to answer.",
1056
+ category: "communication",
1057
+ sourceMemoryIds: [selected.id],
1058
+ },
1059
+ ],
1060
+ }),
1061
+ }),
1062
+ },
1063
+ ],
1064
+ }),
1065
+ resolveOwner: subject.resolveOwner,
1066
+ audit: {
1067
+ record: async (record) => {
1068
+ records.push(record);
1069
+ },
1070
+ },
1071
+ now: subject.now,
1072
+ });
1073
+
1074
+ await assert.rejects(
1075
+ service.draftCards({
1076
+ principal: "owner:alice",
1077
+ sourceMemoryIds: [selected.id],
1078
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
1079
+ consent: true,
1080
+ signal: controller.signal,
1081
+ }),
1082
+ (error: unknown) => error instanceof SupportPassportError && error.code === "storage_conflict"
1083
+ );
1084
+ await flushAudit();
1085
+ assert.equal(records[0]?.errorClass, "storage_conflict");
1086
+ } finally {
1087
+ await subject.cleanup();
1088
+ }
1089
+ });
1090
+
1091
+ test("audit write failures do not block successful drafts or replace model errors", async () => {
1092
+ const subject = await makeSubject();
1093
+ try {
1094
+ const selected = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
1095
+ source: "test",
1096
+ });
1097
+ const audit = {
1098
+ record: async () => {
1099
+ throw new Error("audit unavailable");
1100
+ },
1101
+ };
1102
+ const successful = new SupportPassportDraftService({
1103
+ cardService: subject.cardService,
1104
+ modelAdapter: new SupportPassportModelAdapter({
1105
+ routes: [
1106
+ {
1107
+ kind: "local",
1108
+ invoke: async () => ({
1109
+ modelUsed: "local/test-model",
1110
+ content: JSON.stringify({
1111
+ cards: [
1112
+ {
1113
+ title: "Processing time",
1114
+ statement: "Give me time to answer.",
1115
+ category: "communication",
1116
+ sourceMemoryIds: [selected.id],
1117
+ },
1118
+ ],
1119
+ }),
1120
+ }),
1121
+ },
1122
+ ],
1123
+ }),
1124
+ resolveOwner: subject.resolveOwner,
1125
+ audit,
1126
+ now: subject.now,
1127
+ });
1128
+ assert.equal(
1129
+ (
1130
+ await successful.draftCards({
1131
+ principal: "owner:alice",
1132
+ sourceMemoryIds: [selected.id],
1133
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
1134
+ consent: true,
1135
+ })
1136
+ ).length,
1137
+ 1
1138
+ );
1139
+
1140
+ const failing = new SupportPassportDraftService({
1141
+ cardService: subject.cardService,
1142
+ modelAdapter: new SupportPassportModelAdapter({
1143
+ routes: [{ kind: "local", invoke: async () => ({ modelUsed: "local/test-model", content: "not-json" }) }],
1144
+ }),
1145
+ resolveOwner: subject.resolveOwner,
1146
+ audit,
1147
+ now: subject.now,
1148
+ });
1149
+ await assert.rejects(
1150
+ failing.draftCards({
1151
+ principal: "owner:alice",
1152
+ sourceMemoryIds: [selected.id],
1153
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
1154
+ consent: true,
1155
+ }),
1156
+ (error: unknown) => error instanceof SupportPassportError && error.code === "model_output_invalid"
1157
+ );
1158
+ } finally {
1159
+ await subject.cleanup();
1160
+ }
1161
+ });
1162
+
1163
+ test("stalled error audits do not delay the model error", async () => {
1164
+ const subject = await makeSubject();
1165
+ const auditStarted = Promise.withResolvers<void>();
1166
+ const releaseAudit = Promise.withResolvers<void>();
1167
+ try {
1168
+ const selected = await subject.aliceStorage.writeMemory("preference", "Give me time to answer.", {
1169
+ source: "test",
1170
+ });
1171
+ const service = new SupportPassportDraftService({
1172
+ cardService: subject.cardService,
1173
+ modelAdapter: new SupportPassportModelAdapter({
1174
+ routes: [{ kind: "local", invoke: async () => ({ modelUsed: "local/test-model", content: "not-json" }) }],
1175
+ }),
1176
+ resolveOwner: subject.resolveOwner,
1177
+ audit: {
1178
+ record: async () => {
1179
+ auditStarted.resolve();
1180
+ await releaseAudit.promise;
1181
+ },
1182
+ },
1183
+ now: subject.now,
1184
+ });
1185
+
1186
+ await assert.rejects(
1187
+ Promise.race([
1188
+ service.draftCards({
1189
+ principal: "owner:alice",
1190
+ sourceMemoryIds: [selected.id],
1191
+ sourceMemoryRevisions: sourceRevision(selected.id, "Give me time to answer."),
1192
+ consent: true,
1193
+ }),
1194
+ new Promise<never>((_resolve, reject) =>
1195
+ setTimeout(() => reject(new Error("The model error waited for its audit.")), 100)
1196
+ ),
1197
+ ]),
1198
+ (error: unknown) => error instanceof SupportPassportError && error.code === "model_output_invalid"
1199
+ );
1200
+ await auditStarted.promise;
1201
+ } finally {
1202
+ releaseAudit.resolve();
1203
+ await subject.cleanup();
1204
+ }
1205
+ });
1206
+
1207
+ test("helper questions recheck the grant after the model call", async () => {
1208
+ const subject = await makeSubject();
1209
+ try {
1210
+ const draft = await subject.cardService.createManualDraft({
1211
+ principal: "owner:alice",
1212
+ title: "Quiet space",
1213
+ statement: "Offer me a quiet place and time.",
1214
+ category: "environment",
1215
+ reviewBy: "2026-09-01T12:00:00.000Z",
1216
+ });
1217
+ const active = await subject.cardService.approveCard({
1218
+ principal: "owner:alice",
1219
+ cardId: draft.cardId,
1220
+ expectedRevision: draft.revision,
1221
+ });
1222
+ const created = await subject.grantService.createGrant({
1223
+ principal: "owner:alice",
1224
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1225
+ expiresAt: "2026-08-11T13:00:00.000Z",
1226
+ });
1227
+ const route: SupportPassportModelRoute = {
1228
+ kind: "gateway",
1229
+ invoke: async () => {
1230
+ await subject.grantService.revokeGrant({
1231
+ principal: "owner:alice",
1232
+ grantId: created.grant.grantId,
1233
+ expectedStateVersion: created.grant.stateVersion,
1234
+ });
1235
+ return {
1236
+ modelUsed: "gateway/test-model",
1237
+ content: JSON.stringify({
1238
+ answer: "Offer a quiet place.",
1239
+ citedCardIds: [active.cardId],
1240
+ coverage: "grounded",
1241
+ }),
1242
+ };
1243
+ },
1244
+ };
1245
+ const records: SupportPassportModelAuditRecord[] = [];
1246
+ const questionService = new SupportPassportQuestionService({
1247
+ grantService: subject.grantService,
1248
+ modelAdapter: new SupportPassportModelAdapter({ routes: [route] }),
1249
+ audit: {
1250
+ record: async (record) => {
1251
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
1252
+ },
1253
+ },
1254
+ now: subject.now,
1255
+ });
1256
+
1257
+ await assert.rejects(
1258
+ questionService.askGrant({
1259
+ grantId: created.grant.grantId,
1260
+ secret: created.secret,
1261
+ question: "What can help?",
1262
+ }),
1263
+ (error: unknown) => error instanceof SupportPassportError && error.code === "grant_gone"
1264
+ );
1265
+ await flushAudit();
1266
+ assert.equal(records.length, 1);
1267
+ assert.equal(records[0]?.outcome, "error");
1268
+ assert.equal(records[0]?.errorClass, "grant_gone");
1269
+ } finally {
1270
+ await subject.cleanup();
1271
+ }
1272
+ });
1273
+
1274
+ test("helper question audits hash the canonical grant ID", async () => {
1275
+ const subject = await makeSubject();
1276
+ try {
1277
+ const draft = await subject.cardService.createManualDraft({
1278
+ principal: "owner:alice",
1279
+ title: "Quiet space",
1280
+ statement: "Offer me a quiet place and time.",
1281
+ category: "environment",
1282
+ reviewBy: "2026-09-01T12:00:00.000Z",
1283
+ });
1284
+ const active = await subject.cardService.approveCard({
1285
+ principal: "owner:alice",
1286
+ cardId: draft.cardId,
1287
+ expectedRevision: draft.revision,
1288
+ });
1289
+ const created = await subject.grantService.createGrant({
1290
+ principal: "owner:alice",
1291
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1292
+ expiresAt: "2026-08-11T13:00:00.000Z",
1293
+ });
1294
+ const records: SupportPassportModelAuditRecord[] = [];
1295
+ const service = new SupportPassportQuestionService({
1296
+ grantService: subject.grantService,
1297
+ modelAdapter: new SupportPassportModelAdapter({
1298
+ routes: [
1299
+ {
1300
+ kind: "local",
1301
+ invoke: async () => ({
1302
+ modelUsed: "local/test-model",
1303
+ content: JSON.stringify({
1304
+ answer: "Offer a quiet place.",
1305
+ citedCardIds: [active.cardId],
1306
+ coverage: "grounded",
1307
+ }),
1308
+ }),
1309
+ },
1310
+ ],
1311
+ }),
1312
+ audit: {
1313
+ record: async (record) => {
1314
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
1315
+ },
1316
+ },
1317
+ now: subject.now,
1318
+ });
1319
+
1320
+ await service.askGrant({
1321
+ grantId: created.grant.grantId.toUpperCase(),
1322
+ secret: created.secret,
1323
+ question: "What can help?",
1324
+ });
1325
+ await flushAudit();
1326
+
1327
+ assert.equal(records[0]?.actorHash, hashSupportPassportAuditValues("helper-grant", [created.grant.grantId]));
1328
+ } finally {
1329
+ await subject.cleanup();
1330
+ }
1331
+ });
1332
+
1333
+ test("helper questions honor cancellation during final grant validation", async () => {
1334
+ const subject = await makeSubject();
1335
+ const finalReadStarted = Promise.withResolvers<void>();
1336
+ const releaseFinalRead = Promise.withResolvers<void>();
1337
+ const finalReadFinished = Promise.withResolvers<void>();
1338
+ let finalReadBegan = false;
1339
+ try {
1340
+ const draft = await subject.cardService.createManualDraft({
1341
+ principal: "owner:alice",
1342
+ title: "Quiet space",
1343
+ statement: "Offer me a quiet place and time.",
1344
+ category: "environment",
1345
+ reviewBy: "2026-09-01T12:00:00.000Z",
1346
+ });
1347
+ const active = await subject.cardService.approveCard({
1348
+ principal: "owner:alice",
1349
+ cardId: draft.cardId,
1350
+ expectedRevision: draft.revision,
1351
+ });
1352
+ const created = await subject.grantService.createGrant({
1353
+ principal: "owner:alice",
1354
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1355
+ expiresAt: "2026-08-11T13:00:00.000Z",
1356
+ });
1357
+ const readGrant = subject.grantService.readGrant.bind(subject.grantService);
1358
+ let reads = 0;
1359
+ subject.grantService.readGrant = async (input) => {
1360
+ reads += 1;
1361
+ const isFinalRead = reads === 2;
1362
+ if (isFinalRead) {
1363
+ finalReadBegan = true;
1364
+ finalReadStarted.resolve();
1365
+ await releaseFinalRead.promise;
1366
+ }
1367
+ try {
1368
+ return await readGrant(input);
1369
+ } finally {
1370
+ if (isFinalRead) finalReadFinished.resolve();
1371
+ }
1372
+ };
1373
+ const records: SupportPassportModelAuditRecord[] = [];
1374
+ const service = new SupportPassportQuestionService({
1375
+ grantService: subject.grantService,
1376
+ modelAdapter: new SupportPassportModelAdapter({
1377
+ routes: [
1378
+ {
1379
+ kind: "local",
1380
+ invoke: async () => ({
1381
+ modelUsed: "local/test-model",
1382
+ content: JSON.stringify({
1383
+ answer: "Offer a quiet place.",
1384
+ citedCardIds: [active.cardId],
1385
+ coverage: "grounded",
1386
+ }),
1387
+ }),
1388
+ },
1389
+ ],
1390
+ }),
1391
+ audit: {
1392
+ record: async (record) => {
1393
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
1394
+ },
1395
+ },
1396
+ now: subject.now,
1397
+ });
1398
+ const controller = new AbortController();
1399
+ const answer = service.askGrant({
1400
+ grantId: created.grant.grantId,
1401
+ secret: created.secret,
1402
+ question: "What can help?",
1403
+ signal: controller.signal,
1404
+ });
1405
+ await finalReadStarted.promise;
1406
+ controller.abort();
1407
+ releaseFinalRead.resolve();
1408
+
1409
+ await assert.rejects(answer, (error: unknown) => error instanceof Error && error.name === "AbortError");
1410
+ await finalReadFinished.promise;
1411
+ await flushAudit();
1412
+ assert.equal(records[0]?.outcome, "error");
1413
+ assert.equal(records[0]?.errorClass, "aborted");
1414
+ } finally {
1415
+ releaseFinalRead.resolve();
1416
+ if (finalReadBegan) await finalReadFinished.promise;
1417
+ await flushAudit();
1418
+ await subject.cleanup();
1419
+ }
1420
+ });
1421
+
1422
+ test("helper question cancellation races the initial grant read", async () => {
1423
+ const subject = await makeSubject();
1424
+ const grantReadStarted = Promise.withResolvers<void>();
1425
+ const releaseGrantRead = Promise.withResolvers<void>();
1426
+ try {
1427
+ let modelCalls = 0;
1428
+ subject.grantService.readGrant = async () => {
1429
+ grantReadStarted.resolve();
1430
+ await releaseGrantRead.promise;
1431
+ throw new Error("released stalled grant read");
1432
+ };
1433
+ const service = new SupportPassportQuestionService({
1434
+ grantService: subject.grantService,
1435
+ modelAdapter: new SupportPassportModelAdapter({
1436
+ routes: [
1437
+ {
1438
+ kind: "gateway",
1439
+ invoke: async () => {
1440
+ modelCalls += 1;
1441
+ return null;
1442
+ },
1443
+ },
1444
+ ],
1445
+ }),
1446
+ audit: { record: async () => undefined },
1447
+ now: subject.now,
1448
+ });
1449
+ const controller = new AbortController();
1450
+ const answer = service.askGrant({
1451
+ grantId: "00000000-0000-4000-8000-000000000001",
1452
+ secret: "x".repeat(43),
1453
+ question: "What can help?",
1454
+ signal: controller.signal,
1455
+ });
1456
+ await grantReadStarted.promise;
1457
+ controller.abort();
1458
+
1459
+ await assert.rejects(
1460
+ Promise.race([
1461
+ answer,
1462
+ new Promise<never>((_resolve, reject) =>
1463
+ setTimeout(() => reject(new Error("Question cancellation waited for the grant read.")), 100)
1464
+ ),
1465
+ ]),
1466
+ (error: unknown) => error instanceof Error && error.name === "AbortError"
1467
+ );
1468
+ assert.equal(modelCalls, 0);
1469
+ } finally {
1470
+ releaseGrantRead.resolve();
1471
+ await new Promise<void>((resolve) => setImmediate(resolve));
1472
+ await subject.cleanup();
1473
+ }
1474
+ });
1475
+
1476
+ test("invalid helper questions use the invalid-input audit class", async () => {
1477
+ const subject = await makeSubject();
1478
+ try {
1479
+ const draft = await subject.cardService.createManualDraft({
1480
+ principal: "owner:alice",
1481
+ title: "Quiet space",
1482
+ statement: "Offer me a quiet place and time.",
1483
+ category: "environment",
1484
+ reviewBy: "2026-09-01T12:00:00.000Z",
1485
+ });
1486
+ const active = await subject.cardService.approveCard({
1487
+ principal: "owner:alice",
1488
+ cardId: draft.cardId,
1489
+ expectedRevision: draft.revision,
1490
+ });
1491
+ const created = await subject.grantService.createGrant({
1492
+ principal: "owner:alice",
1493
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1494
+ expiresAt: "2026-08-11T13:00:00.000Z",
1495
+ });
1496
+ let modelCalls = 0;
1497
+ const records: SupportPassportModelAuditRecord[] = [];
1498
+ const service = new SupportPassportQuestionService({
1499
+ grantService: subject.grantService,
1500
+ modelAdapter: new SupportPassportModelAdapter({
1501
+ routes: [
1502
+ {
1503
+ kind: "local",
1504
+ invoke: async () => {
1505
+ modelCalls += 1;
1506
+ return null;
1507
+ },
1508
+ },
1509
+ ],
1510
+ }),
1511
+ audit: {
1512
+ record: async (record) => {
1513
+ records.push(SupportPassportModelAuditRecordSchema.parse(record));
1514
+ },
1515
+ },
1516
+ now: subject.now,
1517
+ });
1518
+
1519
+ await assert.rejects(
1520
+ service.askGrant({
1521
+ grantId: created.grant.grantId,
1522
+ secret: created.secret,
1523
+ question: " ",
1524
+ }),
1525
+ (error: unknown) => error instanceof SupportPassportError && error.code === "invalid_input"
1526
+ );
1527
+ await flushAudit();
1528
+ assert.equal(modelCalls, 0);
1529
+ assert.equal(records.length, 1);
1530
+ assert.equal(records[0]?.errorClass, "invalid_input");
1531
+ } finally {
1532
+ await subject.cleanup();
1533
+ }
1534
+ });
1535
+
1536
+ test("an audit write failure does not block a grounded helper answer", async () => {
1537
+ const subject = await makeSubject();
1538
+ try {
1539
+ const draft = await subject.cardService.createManualDraft({
1540
+ principal: "owner:alice",
1541
+ title: "Quiet space",
1542
+ statement: "Offer me a quiet place and time.",
1543
+ category: "environment",
1544
+ reviewBy: "2026-09-01T12:00:00.000Z",
1545
+ });
1546
+ const active = await subject.cardService.approveCard({
1547
+ principal: "owner:alice",
1548
+ cardId: draft.cardId,
1549
+ expectedRevision: draft.revision,
1550
+ });
1551
+ const created = await subject.grantService.createGrant({
1552
+ principal: "owner:alice",
1553
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1554
+ expiresAt: "2026-08-11T13:00:00.000Z",
1555
+ });
1556
+ const service = new SupportPassportQuestionService({
1557
+ grantService: subject.grantService,
1558
+ modelAdapter: new SupportPassportModelAdapter({
1559
+ routes: [
1560
+ {
1561
+ kind: "gateway",
1562
+ invoke: async () => ({
1563
+ modelUsed: "gateway/test-model",
1564
+ content: JSON.stringify({
1565
+ answer: "Offer a quiet place.",
1566
+ citedCardIds: [active.cardId],
1567
+ coverage: "grounded",
1568
+ }),
1569
+ }),
1570
+ },
1571
+ ],
1572
+ }),
1573
+ audit: {
1574
+ record: async () => {
1575
+ throw new Error("audit unavailable");
1576
+ },
1577
+ },
1578
+ now: subject.now,
1579
+ });
1580
+
1581
+ const answer = await service.askGrant({
1582
+ grantId: created.grant.grantId,
1583
+ secret: created.secret,
1584
+ question: "What can help?",
1585
+ });
1586
+ assert.equal(answer.answer, "Offer a quiet place.");
1587
+ assert.deepEqual(answer.citedCardIds, [active.cardId]);
1588
+ } finally {
1589
+ await subject.cleanup();
1590
+ }
1591
+ });
1592
+
1593
+ test("a success audit runs after the grounded helper answer returns", async () => {
1594
+ const subject = await makeSubject();
1595
+ try {
1596
+ const draft = await subject.cardService.createManualDraft({
1597
+ principal: "owner:alice",
1598
+ title: "Quiet space",
1599
+ statement: "Offer me a quiet place and time.",
1600
+ category: "environment",
1601
+ reviewBy: "2026-09-01T12:00:00.000Z",
1602
+ });
1603
+ const active = await subject.cardService.approveCard({
1604
+ principal: "owner:alice",
1605
+ cardId: draft.cardId,
1606
+ expectedRevision: draft.revision,
1607
+ });
1608
+ const created = await subject.grantService.createGrant({
1609
+ principal: "owner:alice",
1610
+ cards: [{ cardId: active.cardId, revision: active.revision }],
1611
+ expiresAt: "2026-08-11T13:00:00.000Z",
1612
+ });
1613
+ let auditCalls = 0;
1614
+ const service = new SupportPassportQuestionService({
1615
+ grantService: subject.grantService,
1616
+ modelAdapter: new SupportPassportModelAdapter({
1617
+ routes: [
1618
+ {
1619
+ kind: "gateway",
1620
+ invoke: async () => ({
1621
+ modelUsed: "gateway/test-model",
1622
+ content: JSON.stringify({
1623
+ answer: "Offer a quiet place.",
1624
+ citedCardIds: [active.cardId],
1625
+ coverage: "grounded",
1626
+ }),
1627
+ }),
1628
+ },
1629
+ ],
1630
+ }),
1631
+ audit: {
1632
+ record: async () => {
1633
+ auditCalls += 1;
1634
+ },
1635
+ },
1636
+ now: subject.now,
1637
+ });
1638
+
1639
+ const answer = await service.askGrant({
1640
+ grantId: created.grant.grantId,
1641
+ secret: created.secret,
1642
+ question: "What can help?",
1643
+ });
1644
+ assert.equal(answer.answer, "Offer a quiet place.");
1645
+ assert.equal(auditCalls, 0);
1646
+ await new Promise<void>((resolve) => setImmediate(resolve));
1647
+ assert.equal(auditCalls, 1);
1648
+ } finally {
1649
+ await subject.cleanup();
1650
+ }
1651
+ });
1652
+
1653
+ test("the model audit store writes strict private JSONL without text fields", async () => {
1654
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-audit-"));
1655
+ try {
1656
+ const store = new SupportPassportModelAuditStore({ memoryDir: root });
1657
+ const record = makeAuditRecord();
1658
+ await store.record(record);
1659
+ const auditPath = path.join(root, "state", "support-passport", "audit", "2026-08-11.jsonl");
1660
+ assert.deepEqual(JSON.parse((await readFile(auditPath, "utf8")).trim()), record);
1661
+ assert.equal((await lstat(auditPath)).mode & 0o777, 0o600);
1662
+ assert.equal((await lstat(path.dirname(auditPath))).mode & 0o777, 0o700);
1663
+ assert.equal(SupportPassportModelAuditRecordSchema.safeParse({ ...record, question: "private" }).success, false);
1664
+ } finally {
1665
+ await rm(root, { recursive: true, force: true });
1666
+ }
1667
+ });
1668
+
1669
+ test("the model audit store expands a leading tilde in its memory directory", () => {
1670
+ const store = new SupportPassportModelAuditStore({ memoryDir: "~/support-passport-audit-path-test" });
1671
+ const memoryDir = (store as unknown as { memoryDir: string }).memoryDir;
1672
+ assert.equal(memoryDir.includes(`${path.sep}~${path.sep}`), false);
1673
+ assert.equal(path.basename(memoryDir), "support-passport-audit-path-test");
1674
+ assert.equal(path.isAbsolute(memoryDir), true);
1675
+ });
1676
+
1677
+ test("the model audit store rejects a symlink alias in its configured memory root", async () => {
1678
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-audit-root-link-"));
1679
+ try {
1680
+ const actual = path.join(root, "actual");
1681
+ const alias = path.join(root, "alias");
1682
+ await mkdir(actual);
1683
+ await symlink(actual, alias);
1684
+ const store = new SupportPassportModelAuditStore({ memoryDir: path.join(alias, "memory") });
1685
+
1686
+ await assert.rejects(store.record(makeAuditRecord()), /memory directory must be a stable directory/);
1687
+ } finally {
1688
+ await rm(root, { recursive: true, force: true });
1689
+ }
1690
+ });
1691
+
1692
+ test("the model audit store rejects symlinked and hard-linked audit files", async () => {
1693
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-audit-links-"));
1694
+ try {
1695
+ const store = new SupportPassportModelAuditStore({ memoryDir: root });
1696
+ await store.record(makeAuditRecord({ occurredAt: "2026-08-10T12:00:00.000Z" }));
1697
+ const auditPath = path.join(root, "state", "support-passport", "audit", "2026-08-11.jsonl");
1698
+ const outsidePath = path.join(root, "outside.jsonl");
1699
+ await writeFile(outsidePath, "outside\n", { mode: 0o600 });
1700
+
1701
+ await symlink(outsidePath, auditPath);
1702
+ await assert.rejects(store.record(makeAuditRecord()), /regular files in a stable directory/);
1703
+ assert.equal(await readFile(outsidePath, "utf8"), "outside\n");
1704
+
1705
+ await rm(auditPath);
1706
+ await link(outsidePath, auditPath);
1707
+ await assert.rejects(store.record(makeAuditRecord()), /regular files in a stable directory/);
1708
+ assert.equal(await readFile(outsidePath, "utf8"), "outside\n");
1709
+ } finally {
1710
+ await rm(root, { recursive: true, force: true });
1711
+ }
1712
+ });
1713
+
1714
+ test("the model audit lock uses the pinned audit directory", async () => {
1715
+ const root = await mkdtemp(path.join(tmpdir(), "remnic-support-model-audit-lock-"));
1716
+ try {
1717
+ let lockPath = "";
1718
+ const acceptLock = (async (candidate, _options, task) => {
1719
+ lockPath = candidate;
1720
+ return await task(true, { refresh: async () => true } as HeldFileLockController);
1721
+ }) as typeof import("../utils/serialize-mutations.js").withHeldFileLock;
1722
+ const store = new SupportPassportModelAuditStore({ memoryDir: root, withHeldFileLock: acceptLock });
1723
+
1724
+ await store.record(makeAuditRecord());
1725
+
1726
+ assert.equal(lockPath.startsWith("/proc/self/fd/") || lockPath.startsWith("/dev/fd/"), true);
1727
+ assert.equal(lockPath.includes(path.join(root, "state")), false);
1728
+ } finally {
1729
+ await rm(root, { recursive: true, force: true });
1730
+ }
1731
+ });