@remnic/core 9.54.5 → 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 (550) hide show
  1. package/dist/access-admin-ops-surface.d.ts +24 -24
  2. package/dist/access-admin-ops-surface.js +32 -29
  3. package/dist/access-authorization-probe.d.ts +25 -23
  4. package/dist/access-authorization-probe.js +35 -30
  5. package/dist/access-boundary.d.ts +26 -24
  6. package/dist/access-boundary.js +36 -31
  7. package/dist/access-cli.js +86 -80
  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 -19
  11. package/dist/access-extraction-force-flush.js +32 -29
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +22 -22
  14. package/dist/access-http-lifecycle-flush.d.ts +22 -22
  15. package/dist/access-http-offline-stream.d.ts +18 -18
  16. package/dist/access-http-query.js +32 -29
  17. package/dist/access-http.d.ts +55 -37
  18. package/dist/access-http.js +52 -43
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +32 -29
  21. package/dist/access-lcm-surface.d.ts +21 -21
  22. package/dist/access-lcm-surface.js +32 -29
  23. package/dist/access-mcp.d.ts +22 -22
  24. package/dist/access-mcp.js +40 -36
  25. package/dist/access-memory-search-fanout.d.ts +2 -2
  26. package/dist/{access-namespace-preflight-BdHlGd_T.d.ts → access-namespace-preflight-CCNn_6NE.d.ts} +1 -1
  27. package/dist/access-namespace-preflight.d.ts +3 -3
  28. package/dist/access-namespace-preflight.js +32 -29
  29. package/dist/access-observe-write-surface.d.ts +22 -22
  30. package/dist/access-observe-write-surface.js +32 -29
  31. package/dist/access-offline-manifest.d.ts +9 -9
  32. package/dist/access-offline-manifest.js +2 -2
  33. package/dist/access-operations-batch.js +34 -31
  34. package/dist/access-operations.d.ts +26 -26
  35. package/dist/access-operations.js +38 -34
  36. package/dist/access-recall-concurrency.d.ts +21 -21
  37. package/dist/access-recall-concurrency.js +32 -29
  38. package/dist/access-recall-response.d.ts +19 -19
  39. package/dist/access-recall-response.js +32 -29
  40. package/dist/access-recall-surface.d.ts +19 -19
  41. package/dist/access-recall-surface.js +32 -29
  42. package/dist/access-schema.d.ts +88 -88
  43. package/dist/access-schema.js +3 -3
  44. package/dist/{access-service-DL7X9hrg.d.ts → access-service-DkSdXK4S.d.ts} +1314 -83
  45. package/dist/access-service-helpers.d.ts +22 -22
  46. package/dist/access-service.d.ts +31 -31
  47. package/dist/access-service.js +32 -29
  48. package/dist/access-surface-catalog.d.ts +22 -22
  49. package/dist/access-surface-catalog.js +209 -2
  50. package/dist/access-surface-catalog.js.map +1 -1
  51. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  52. package/dist/action-confidence.d.ts +1 -1
  53. package/dist/active-memory-bridge.d.ts +6 -5
  54. package/dist/active-memory-bridge.js +2 -2
  55. package/dist/active-recall.d.ts +1 -1
  56. package/dist/active-recall.js +2 -1
  57. package/dist/active-recall.js.map +1 -1
  58. package/dist/adapters/index.js +4 -4
  59. package/dist/adapters/registry.js +2 -2
  60. package/dist/admin-console/public/what-helps-me/app.js +1594 -0
  61. package/dist/admin-console/public/what-helps-me/index.html +339 -0
  62. package/dist/admin-console/public/what-helps-me/model.js +685 -0
  63. package/dist/admin-console/public/what-helps-me/what-helps-me.css +1296 -0
  64. package/dist/ambient-provenance.d.ts +1 -1
  65. package/dist/artifact-search.d.ts +1 -1
  66. package/dist/{auto-sync-TDHLZPVP.js → auto-sync-NR3GPCOA.js} +4 -4
  67. package/dist/behavior-learner.d.ts +1 -1
  68. package/dist/behavior-signals.d.ts +1 -1
  69. package/dist/bootstrap.d.ts +9 -9
  70. package/dist/briefing.d.ts +2 -2
  71. package/dist/briefing.js +14 -14
  72. package/dist/buffer-surprise-report.d.ts +1 -1
  73. package/dist/buffer-turn-helpers.d.ts +1 -1
  74. package/dist/buffer.d.ts +2 -2
  75. package/dist/buffer.js +2 -2
  76. package/dist/bulk-import/index.d.ts +3 -3
  77. package/dist/calibration.d.ts +1 -1
  78. package/dist/calibration.js +1 -1
  79. package/dist/capabilities.d.ts +1 -1
  80. package/dist/{catalog-B90is4d8.d.ts → catalog-DGFf8mD9.d.ts} +1 -1
  81. package/dist/causal-behavior.d.ts +1 -1
  82. package/dist/causal-consolidation.d.ts +1 -1
  83. package/dist/causal-consolidation.js +15 -15
  84. package/dist/causal-trajectory-graph.d.ts +1 -1
  85. package/dist/{chunk-CSY44OKU.js → chunk-26ZXBTYN.js} +4 -4
  86. package/dist/chunk-26ZXBTYN.js.map +1 -0
  87. package/dist/{chunk-LLJMAULE.js → chunk-2FKEIPIT.js} +5 -5
  88. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  89. package/dist/chunk-37DZZFFM.js +502 -0
  90. package/dist/chunk-37DZZFFM.js.map +1 -0
  91. package/dist/chunk-3HSRWJTC.js +9 -0
  92. package/dist/chunk-3HSRWJTC.js.map +1 -0
  93. package/dist/{chunk-TKFGFDPJ.js → chunk-3TIZPSJC.js} +4 -4
  94. package/dist/{chunk-B4T3FQMT.js → chunk-43SEBY2M.js} +1 -1
  95. package/dist/{chunk-LVKYLMSM.js → chunk-46SZBVDS.js} +2 -2
  96. package/dist/{chunk-YKZLOPTC.js → chunk-5FD7JMTX.js} +2 -2
  97. package/dist/{chunk-2PNXOQ4P.js → chunk-5NB726GV.js} +3 -7
  98. package/dist/chunk-5NB726GV.js.map +1 -0
  99. package/dist/chunk-62OKGIJA.js +39 -0
  100. package/dist/chunk-62OKGIJA.js.map +1 -0
  101. package/dist/{chunk-WM5LD6AT.js → chunk-6CYHUWCO.js} +2 -2
  102. package/dist/{chunk-BIF4SHMD.js → chunk-76JJSXK7.js} +1 -1
  103. package/dist/chunk-76JJSXK7.js.map +1 -0
  104. package/dist/{chunk-3SXLD5T3.js → chunk-7BD6O7ZV.js} +189 -4
  105. package/dist/chunk-7BD6O7ZV.js.map +1 -0
  106. package/dist/chunk-7TBXNYLO.js +346 -0
  107. package/dist/chunk-7TBXNYLO.js.map +1 -0
  108. package/dist/{chunk-YR65W7BV.js → chunk-BRR5EUTF.js} +3 -3
  109. package/dist/{chunk-JKW663ZN.js → chunk-CIAI24TQ.js} +2 -2
  110. package/dist/{chunk-3WGI7F4V.js → chunk-D3LBIUWV.js} +9219 -5233
  111. package/dist/chunk-D3LBIUWV.js.map +1 -0
  112. package/dist/{chunk-YDWPYDML.js → chunk-EKTP7MNS.js} +4 -4
  113. package/dist/{chunk-6LEIWWAF.js → chunk-F2JRDPON.js} +2 -2
  114. package/dist/{chunk-VYQ4URQB.js → chunk-FFRTNF3L.js} +2 -2
  115. package/dist/{chunk-TY6MKIWP.js → chunk-FONWBCVY.js} +3 -3
  116. package/dist/{chunk-XSV2YDCJ.js → chunk-FPLM2TKD.js} +2 -2
  117. package/dist/{chunk-VTU5IEKY.js → chunk-FTPONF6B.js} +2 -2
  118. package/dist/{chunk-6PZGKGAO.js → chunk-FWX7W5PM.js} +2 -2
  119. package/dist/{chunk-QPW3PAIH.js → chunk-G6DFSS6A.js} +2 -2
  120. package/dist/{chunk-2DD6ZHOM.js → chunk-GJ776BC2.js} +3 -3
  121. package/dist/{chunk-XKAR4RF6.js → chunk-GN3WRFJY.js} +6 -6
  122. package/dist/{chunk-RG426DJO.js → chunk-H7NAZ3UB.js} +2 -2
  123. package/dist/{chunk-BSXGTDVJ.js → chunk-HBUFQRIM.js} +2 -2
  124. package/dist/{chunk-FFEBSFVT.js → chunk-HGJ6NJ4E.js} +2 -2
  125. package/dist/{chunk-WB7JSQCP.js → chunk-HIEWDJ2O.js} +5 -2
  126. package/dist/{chunk-WB7JSQCP.js.map → chunk-HIEWDJ2O.js.map} +1 -1
  127. package/dist/{chunk-QTO6BMQZ.js → chunk-I3MSGFGD.js} +17 -6
  128. package/dist/chunk-I3MSGFGD.js.map +1 -0
  129. package/dist/{chunk-5JKUSBMX.js → chunk-IKP5OYQQ.js} +2 -2
  130. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  131. package/dist/{chunk-DBY26JJJ.js → chunk-IQ4NVWEF.js} +2 -2
  132. package/dist/{chunk-7Q7JWLQ4.js → chunk-JYG4OLWH.js} +2 -2
  133. package/dist/{chunk-3OTSVCPK.js → chunk-K26NWEOC.js} +4 -4
  134. package/dist/{chunk-WZWZRZ2V.js → chunk-K2UGPTYY.js} +484 -43
  135. package/dist/chunk-K2UGPTYY.js.map +1 -0
  136. package/dist/{chunk-L6CQ7WIW.js → chunk-KFBOPMF6.js} +11 -11
  137. package/dist/chunk-KFBOPMF6.js.map +1 -0
  138. package/dist/{chunk-HOIRB77A.js → chunk-L6Z5OAS4.js} +2 -2
  139. package/dist/{chunk-7RZJWZ46.js → chunk-LEPOP46W.js} +5 -5
  140. package/dist/{chunk-QQXWFDRD.js → chunk-M4YHAODQ.js} +270 -684
  141. package/dist/chunk-M4YHAODQ.js.map +1 -0
  142. package/dist/{chunk-NGT35IGQ.js → chunk-MIH3CJPN.js} +3 -3
  143. package/dist/{chunk-QPAQSG3F.js → chunk-MK5VEWCR.js} +2 -2
  144. package/dist/chunk-NISZQW72.js +140 -0
  145. package/dist/chunk-NISZQW72.js.map +1 -0
  146. package/dist/{chunk-L7RTUEDG.js → chunk-OCIP6YMD.js} +2 -2
  147. package/dist/{chunk-2INWPC6E.js → chunk-PG4QBPVG.js} +20 -10
  148. package/dist/chunk-PG4QBPVG.js.map +1 -0
  149. package/dist/{chunk-DDA55IVB.js → chunk-PT54WISF.js} +189 -55
  150. package/dist/chunk-PT54WISF.js.map +1 -0
  151. package/dist/{chunk-UWJNKGBA.js → chunk-QZLDRQ4I.js} +5 -5
  152. package/dist/{chunk-UYOXFFEY.js → chunk-RUNORG2P.js} +3 -3
  153. package/dist/{chunk-LC3FD4BV.js → chunk-RZQ6W2W4.js} +39 -39
  154. package/dist/{chunk-MRGJZ75M.js → chunk-SMNYAMJD.js} +3 -3
  155. package/dist/{chunk-MKQBXQKU.js → chunk-TB6CBZ5V.js} +22 -15
  156. package/dist/chunk-TB6CBZ5V.js.map +1 -0
  157. package/dist/{chunk-DN2IDKI5.js → chunk-TO7ZTWJW.js} +2 -2
  158. package/dist/{chunk-K2TDK7GG.js → chunk-TWTUGCTY.js} +2 -2
  159. package/dist/{chunk-CUTFSLSD.js → chunk-UCTS3DDH.js} +2 -2
  160. package/dist/{chunk-JYIJNNN3.js → chunk-UG6VXIOK.js} +2 -2
  161. package/dist/{chunk-QOQQMVR2.js → chunk-UKYS3NU5.js} +2 -2
  162. package/dist/{chunk-VZRFGJCH.js → chunk-UPLZNJKN.js} +2 -2
  163. package/dist/{chunk-C23BYTJV.js → chunk-UW5NPZJ2.js} +40 -12
  164. package/dist/chunk-UW5NPZJ2.js.map +1 -0
  165. package/dist/{chunk-DOOWFDWN.js → chunk-V6ERBR7U.js} +2 -2
  166. package/dist/{chunk-7HG7MGAP.js → chunk-W5ZKPI34.js} +462 -2673
  167. package/dist/chunk-W5ZKPI34.js.map +1 -0
  168. package/dist/{chunk-HNDIND7P.js → chunk-XX4HPFFM.js} +54 -15
  169. package/dist/chunk-XX4HPFFM.js.map +1 -0
  170. package/dist/{chunk-N7CXQ5TA.js → chunk-XZHE2DEN.js} +1 -1
  171. package/dist/chunk-YXQEKCZM.js +223 -0
  172. package/dist/chunk-YXQEKCZM.js.map +1 -0
  173. package/dist/{chunk-6NNNURLK.js → chunk-Z54EM5YZ.js} +4 -4
  174. package/dist/{chunk-F2Y6I74C.js → chunk-ZRZVLB7O.js} +2 -2
  175. package/dist/{chunk-CLKBV3Z3.js → chunk-ZUKIUXHQ.js} +9 -9
  176. package/dist/{cli-jfuZXixq.d.ts → cli-Bze8S0Qs.d.ts} +5 -5
  177. package/dist/cli.d.ts +22 -22
  178. package/dist/cli.js +66 -58
  179. package/dist/coding/pre-action-gate.d.ts +1 -1
  180. package/dist/compounding/engine.d.ts +2 -2
  181. package/dist/compounding/engine.js +14 -14
  182. package/dist/compounding/preference-consolidator.d.ts +1 -1
  183. package/dist/compression-optimizer.d.ts +1 -1
  184. package/dist/config.d.ts +1 -1
  185. package/dist/config.js +2 -1
  186. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  187. package/dist/connectors/codex-materialize-runner.js +14 -14
  188. package/dist/connectors/codex-materialize.d.ts +1 -1
  189. package/dist/connectors/index.d.ts +1 -1
  190. package/dist/connectors/index.js +14 -14
  191. package/dist/consolidation-provenance-check.d.ts +2 -2
  192. package/dist/consolidation-undo.d.ts +2 -2
  193. package/dist/contradiction/index.d.ts +2 -2
  194. package/dist/contradiction/index.js +2 -2
  195. package/dist/{contradiction-scan-KCADHXPE.js → contradiction-scan-JDTBXRGW.js} +3 -3
  196. package/dist/converge-config.d.ts +1 -1
  197. package/dist/convergence-refresh.d.ts +2 -2
  198. package/dist/convergence-refresh.js +16 -16
  199. package/dist/conversation-index/backend.d.ts +1 -1
  200. package/dist/conversation-index/backend.js +2 -2
  201. package/dist/conversation-index/chunker.d.ts +1 -1
  202. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  203. package/dist/conversation-index/indexer.d.ts +1 -1
  204. package/dist/conversation-index/search.d.ts +1 -1
  205. package/dist/corpus-watermark.d.ts +1 -1
  206. package/dist/corpus-watermark.js +16 -16
  207. package/dist/day-summary.d.ts +1 -1
  208. package/dist/delinearize.d.ts +1 -1
  209. package/dist/dependency-propagation-config.d.ts +1 -1
  210. package/dist/{dependency-propagation-delivery-CTDuAVJc.d.ts → dependency-propagation-delivery-CzEfB31P.d.ts} +1 -1
  211. package/dist/direct-answer-wiring.d.ts +1 -1
  212. package/dist/direct-answer.d.ts +1 -1
  213. package/dist/embedding-fallback.d.ts +1 -1
  214. package/dist/enrichment/index.d.ts +1 -1
  215. package/dist/entity-retrieval.d.ts +2 -2
  216. package/dist/entity-retrieval.js +14 -14
  217. package/dist/entity-schema.d.ts +1 -1
  218. package/dist/explicit-capture.d.ts +9 -9
  219. package/dist/explicit-capture.js +4 -3
  220. package/dist/external-wiki-access.d.ts +22 -22
  221. package/dist/external-wiki-access.js +33 -30
  222. package/dist/external-wiki-collection-registration.d.ts +1 -1
  223. package/dist/external-wiki-collection.d.ts +1 -1
  224. package/dist/external-wiki-mcp-tools.d.ts +22 -22
  225. package/dist/extraction-error-classification.d.ts +1 -1
  226. package/dist/extraction-faithfulness.d.ts +1 -1
  227. package/dist/extraction-faithfulness.js +2 -2
  228. package/dist/extraction-judge-telemetry.d.ts +1 -1
  229. package/dist/extraction-judge-training.d.ts +1 -1
  230. package/dist/extraction-judge.d.ts +1 -1
  231. package/dist/extraction-judge.js +2 -2
  232. package/dist/extraction-liveness.d.ts +1 -1
  233. package/dist/extraction-normalization.d.ts +1 -1
  234. package/dist/extraction-prompt.d.ts +1 -1
  235. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  236. package/dist/extraction-source-grounding.d.ts +1 -1
  237. package/dist/extraction.d.ts +1 -1
  238. package/dist/extraction.js +20 -20
  239. package/dist/fallback-llm.d.ts +14 -1
  240. package/dist/fallback-llm.js +1 -1
  241. package/dist/{first-start-migration-LMMZFLKK.js → first-start-migration-CRZCZAJN.js} +5 -5
  242. package/dist/graph-dashboard-diff.d.ts +1 -1
  243. package/dist/graph-dashboard-key.d.ts +1 -1
  244. package/dist/graph-dashboard-parser.d.ts +1 -1
  245. package/dist/graph-edge-reinforcement.d.ts +1 -1
  246. package/dist/graph-path-reconstruction.d.ts +1 -1
  247. package/dist/graph-path-scoring.d.ts +1 -1
  248. package/dist/graph-snapshot.d.ts +1 -1
  249. package/dist/graph.d.ts +1 -1
  250. package/dist/harmonic-retrieval.js +1 -1
  251. package/dist/importance.d.ts +1 -1
  252. package/dist/importers/index.d.ts +1 -1
  253. package/dist/in-flight-reads.d.ts +1 -1
  254. package/dist/index.d.ts +704 -1421
  255. package/dist/index.js +203 -118
  256. package/dist/inline-script.d.ts +3 -0
  257. package/dist/inline-script.js +8 -0
  258. package/dist/inline-script.js.map +1 -0
  259. package/dist/intent.d.ts +1 -1
  260. package/dist/lcm/engine.d.ts +1 -1
  261. package/dist/lcm/engine.js +2 -2
  262. package/dist/lcm/index.d.ts +1 -1
  263. package/dist/lcm/index.js +2 -2
  264. package/dist/lcm/tools.d.ts +1 -1
  265. package/dist/lifecycle.d.ts +1 -1
  266. package/dist/live-connectors-runner.d.ts +1 -1
  267. package/dist/local-llm.d.ts +2 -1
  268. package/dist/local-llm.js +2 -2
  269. package/dist/local-model-endpoint.d.ts +1 -1
  270. package/dist/maintenance/memory-governance.d.ts +1 -1
  271. package/dist/maintenance/memory-governance.js +14 -14
  272. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  273. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -14
  274. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  275. package/dist/maintenance/rebuild-memory-projection.js +15 -15
  276. package/dist/{maintenance-heMZSq7G.d.ts → maintenance-C8aRWfhq.d.ts} +3 -3
  277. package/dist/mcp-memory-inspector-app.d.ts +22 -22
  278. package/dist/mcp-read-only-tools.js +1 -1
  279. package/dist/memory-action-policy.d.ts +1 -1
  280. package/dist/memory-cache.d.ts +1 -1
  281. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  282. package/dist/memory-projection-store.d.ts +1 -1
  283. package/dist/memory-provenance.d.ts +1 -1
  284. package/dist/memory-snapshot.d.ts +1 -1
  285. package/dist/memory-worth-outcomes.d.ts +2 -2
  286. package/dist/models-json.d.ts +1 -1
  287. package/dist/namespaces/migrate.d.ts +3 -3
  288. package/dist/namespaces/migrate.js +15 -15
  289. package/dist/namespaces/principal.d.ts +1 -1
  290. package/dist/namespaces/search.d.ts +1 -1
  291. package/dist/namespaces/search.js +6 -6
  292. package/dist/namespaces/storage.d.ts +3 -3
  293. package/dist/namespaces/storage.js +14 -14
  294. package/dist/native-knowledge.d.ts +1 -1
  295. package/dist/offline-sync-impression-drain.d.ts +1 -1
  296. package/dist/operator-doctor-corpus.d.ts +1 -1
  297. package/dist/operator-doctor-corpus.js +17 -17
  298. package/dist/operator-doctor-replica.d.ts +1 -1
  299. package/dist/operator-toolkit.d.ts +3 -3
  300. package/dist/operator-toolkit.js +23 -22
  301. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  302. package/dist/orchestration/maintenance.d.ts +4 -4
  303. package/dist/orchestration/maintenance.js +19 -19
  304. package/dist/{orchestrator-BQQPsSjs.d.ts → orchestrator-D85CzDDY.d.ts} +8 -8
  305. package/dist/orchestrator.d.ts +9 -9
  306. package/dist/orchestrator.js +86 -80
  307. package/dist/patterns-cli.d.ts +1 -1
  308. package/dist/{pipeline-DFfmsnGk.d.ts → pipeline-DPcUifCO.d.ts} +1 -1
  309. package/dist/policy-runtime.d.ts +1 -1
  310. package/dist/proactive-contention.d.ts +1 -1
  311. package/dist/provenance.d.ts +1 -1
  312. package/dist/public-http-D-SyWNj6.d.ts +25 -0
  313. package/dist/{qmd-BF5bcVRs.d.ts → qmd-dFCHp1r0.d.ts} +1 -1
  314. package/dist/qmd-preflight.d.ts +2 -2
  315. package/dist/qmd-recall-cache.d.ts +1 -1
  316. package/dist/qmd.d.ts +2 -2
  317. package/dist/recall-concurrency-config.d.ts +1 -1
  318. package/dist/recall-disclosure-escalation.d.ts +1 -1
  319. package/dist/recall-explain-renderer.d.ts +1 -1
  320. package/dist/recall-explain-renderer.js +3 -3
  321. package/dist/recall-memory-map.d.ts +1 -1
  322. package/dist/recall-planner-llm.d.ts +1 -1
  323. package/dist/recall-planner-llm.js +1 -1
  324. package/dist/recall-state.d.ts +1 -1
  325. package/dist/recall-tag-filter.d.ts +1 -1
  326. package/dist/recall-timings.d.ts +1 -1
  327. package/dist/recall-xray-cli.d.ts +1 -1
  328. package/dist/recall-xray-cli.js +4 -4
  329. package/dist/recall-xray-renderer.d.ts +1 -1
  330. package/dist/recall-xray-renderer.js +3 -3
  331. package/dist/recall-xray.d.ts +1 -1
  332. package/dist/recall-xray.js +2 -2
  333. package/dist/reconcile/cursor.d.ts +1 -1
  334. package/dist/reconcile/manifest.d.ts +1 -1
  335. package/dist/reconcile/plan.d.ts +1 -1
  336. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  337. package/dist/replay/normalizers/claude.d.ts +1 -1
  338. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  339. package/dist/replay/normalizers/shared.d.ts +1 -1
  340. package/dist/replay/runner.d.ts +1 -1
  341. package/dist/replay/types.d.ts +1 -1
  342. package/dist/replica-divergence.d.ts +1 -1
  343. package/dist/replica-peers-config.d.ts +1 -1
  344. package/dist/resolve-auth-token.d.ts +1 -1
  345. package/dist/resume-bundles.js +3 -2
  346. package/dist/retrieval-agents.d.ts +2 -2
  347. package/dist/retrieval-tiers.d.ts +1 -1
  348. package/dist/routing/engine.d.ts +1 -1
  349. package/dist/routing/store.d.ts +1 -1
  350. package/dist/salvage-envelope.d.ts +1 -1
  351. package/dist/schemas.d.ts +116 -116
  352. package/dist/{scope-profiles-CbUOsE_E.d.ts → scope-profiles-DICK7GtN.d.ts} +1 -1
  353. package/dist/search/embed-helper.d.ts +1 -1
  354. package/dist/search/factory.d.ts +1 -1
  355. package/dist/search/factory.js +5 -5
  356. package/dist/search/index.d.ts +1 -1
  357. package/dist/search/index.js +9 -9
  358. package/dist/search/lancedb-backend.d.ts +1 -1
  359. package/dist/search/meilisearch-backend.d.ts +1 -1
  360. package/dist/search/noop-backend.d.ts +1 -1
  361. package/dist/search/orama-backend.d.ts +1 -1
  362. package/dist/search/port.d.ts +1 -1
  363. package/dist/search/remote-backend.d.ts +1 -1
  364. package/dist/{semantic-consolidation-DAB3xdKM.d.ts → semantic-consolidation-BP7NAX0N.d.ts} +1 -1
  365. package/dist/semantic-consolidation.d.ts +2 -2
  366. package/dist/semantic-consolidation.js +15 -15
  367. package/dist/semantic-rule-promotion.js +14 -14
  368. package/dist/semantic-rule-verifier.d.ts +1 -1
  369. package/dist/semantic-rule-verifier.js +14 -14
  370. package/dist/{service-DGKg2O6r.d.ts → service-DT-pJyda.d.ts} +660 -660
  371. package/dist/session-observer-bands.d.ts +1 -1
  372. package/dist/session-observer-state.d.ts +1 -1
  373. package/dist/shared-context/manager.d.ts +9 -9
  374. package/dist/signal.d.ts +1 -1
  375. package/dist/source-agent-qualifier.d.ts +1 -1
  376. package/dist/source-agent-qualifier.js +16 -16
  377. package/dist/{storage-CC1jKcWg.d.ts → storage-qOnaJ1Lw.d.ts} +4 -1
  378. package/dist/storage.d.ts +2 -2
  379. package/dist/storage.js +13 -13
  380. package/dist/summarizer.d.ts +1 -1
  381. package/dist/summarizer.js +5 -5
  382. package/dist/summary-snapshot.d.ts +1 -1
  383. package/dist/support-passport/index.d.ts +518 -0
  384. package/dist/support-passport/index.js +357 -0
  385. package/dist/support-passport/index.js.map +1 -0
  386. package/dist/temporal-supersession.d.ts +2 -2
  387. package/dist/temporal-timeline-recall.d.ts +1 -1
  388. package/dist/temporal-validity.d.ts +1 -1
  389. package/dist/threading.d.ts +1 -1
  390. package/dist/tier-migration.d.ts +2 -2
  391. package/dist/tier-routing.d.ts +1 -1
  392. package/dist/tier-routing.js +2 -2
  393. package/dist/{tier-stats-JDN6HRNT.js → tier-stats-EXYSKSVN.js} +3 -3
  394. package/dist/topics.d.ts +1 -1
  395. package/dist/transcript.d.ts +1 -1
  396. package/dist/transfer/autodetect.js +1 -1
  397. package/dist/transfer/backup.js +1 -1
  398. package/dist/transfer/capsule-export.js +2 -2
  399. package/dist/transfer/types.d.ts +90 -90
  400. package/dist/trust-score-stage.d.ts +1 -1
  401. package/dist/trust-score.d.ts +1 -1
  402. package/dist/{types-BnQza1y8.d.ts → types-ed-SvS2t.d.ts} +7 -1
  403. package/dist/types.d.ts +1 -1
  404. package/dist/types.js +1 -1
  405. package/dist/utility-runtime.d.ts +1 -1
  406. package/dist/verified-recall.js +14 -14
  407. package/dist/write-envelope.d.ts +1 -1
  408. package/dist/write-rate-limiter.d.ts +11 -5
  409. package/dist/write-rate-limiter.js +1 -1
  410. package/package.json +12 -2
  411. package/scripts/build-with-heap.mjs +11 -1
  412. package/src/access-authorization-probe.ts +20 -2
  413. package/src/access-boundary.ts +32 -0
  414. package/src/access-http.test.ts +1940 -643
  415. package/src/access-http.ts +81 -74
  416. package/src/access-mcp.ts +3 -1
  417. package/src/access-operations.ts +2 -0
  418. package/src/access-service.ts +612 -845
  419. package/src/access-surface-catalog.test.ts +40 -10
  420. package/src/access-surface-catalog.ts +15 -0
  421. package/src/active-memory-bridge.ts +17 -19
  422. package/src/config.ts +18 -9
  423. package/src/fallback-llm.test.ts +54 -0
  424. package/src/fallback-llm.ts +54 -9
  425. package/src/inline-script.ts +6 -0
  426. package/src/local-llm-headers-timeout.test.ts +42 -0
  427. package/src/local-llm.ts +8 -8
  428. package/src/mcp-read-only-tools.ts +3 -0
  429. package/src/relay/mission-access.test.ts +11 -11
  430. package/src/relay/mission-ui.test.ts +3 -1
  431. package/src/storage/tombstone-blocked-capture-sync.ts +31 -0
  432. package/src/storage-contract/failure-semantics.test.ts +102 -0
  433. package/src/support-passport/access-http-base.ts +143 -0
  434. package/src/support-passport/access-http.ts +172 -0
  435. package/src/support-passport/access-operations.ts +272 -0
  436. package/src/support-passport/access-service-base.test.ts +193 -0
  437. package/src/support-passport/access-service-base.ts +176 -0
  438. package/src/support-passport/access-surface.test.ts +283 -0
  439. package/src/support-passport/access-surface.ts +286 -0
  440. package/src/support-passport/card-projection.test.ts +8 -0
  441. package/src/support-passport/card-projection.ts +77 -16
  442. package/src/support-passport/card-replacement.ts +126 -0
  443. package/src/support-passport/card-service-commit-forwarding.test.ts +160 -0
  444. package/src/support-passport/card-service.test.ts +533 -31
  445. package/src/support-passport/card-service.ts +506 -364
  446. package/src/support-passport/card-state.ts +158 -0
  447. package/src/support-passport/config-model-routing.test.ts +37 -0
  448. package/src/support-passport/config.test.ts +34 -0
  449. package/src/support-passport/config.ts +41 -0
  450. package/src/support-passport/contracts.test.ts +15 -2
  451. package/src/support-passport/contracts.ts +13 -0
  452. package/src/support-passport/errors.ts +6 -0
  453. package/src/support-passport/generated-batch.test.ts +185 -0
  454. package/src/support-passport/generated-batch.ts +338 -0
  455. package/src/support-passport/grant-contracts.ts +45 -1
  456. package/src/support-passport/grant-generated-batch-scope.test.ts +201 -0
  457. package/src/support-passport/grant-service-cache.test.ts +2 -2
  458. package/src/support-passport/grant-service-cancellation.test.ts +2 -0
  459. package/src/support-passport/grant-service-marker-cache.test.ts +62 -0
  460. package/src/support-passport/grant-service.test.ts +853 -239
  461. package/src/support-passport/grant-service.ts +206 -57
  462. package/src/support-passport/grant-store.ts +7 -3
  463. package/src/support-passport/index.ts +10 -0
  464. package/src/support-passport/mcp-tools.test.ts +222 -0
  465. package/src/support-passport/mcp-tools.ts +187 -0
  466. package/src/support-passport/model-adapter.test.ts +840 -0
  467. package/src/support-passport/model-adapter.ts +602 -0
  468. package/src/support-passport/model-audit-pinned-write.test.ts +59 -0
  469. package/src/support-passport/model-audit.ts +152 -0
  470. package/src/support-passport/model-bridge.test.ts +456 -0
  471. package/src/support-passport/model-bridge.ts +439 -0
  472. package/src/support-passport/model-contracts.ts +124 -0
  473. package/src/support-passport/model-service.test.ts +1731 -0
  474. package/src/support-passport/model-service.ts +399 -0
  475. package/src/support-passport/owner-http.test.ts +671 -0
  476. package/src/support-passport/owner-lock.ts +26 -9
  477. package/src/support-passport/private-file.test.ts +99 -0
  478. package/src/support-passport/private-file.ts +139 -12
  479. package/src/support-passport/public-http.test.ts +476 -0
  480. package/src/support-passport/public-http.ts +601 -0
  481. package/src/support-passport/ui.test.ts +181 -0
  482. package/src/types.ts +7 -0
  483. package/src/write-rate-limiter.test.ts +30 -6
  484. package/src/write-rate-limiter.ts +29 -18
  485. package/dist/chunk-2INWPC6E.js.map +0 -1
  486. package/dist/chunk-2PNXOQ4P.js.map +0 -1
  487. package/dist/chunk-3SXLD5T3.js.map +0 -1
  488. package/dist/chunk-3WGI7F4V.js.map +0 -1
  489. package/dist/chunk-7HG7MGAP.js.map +0 -1
  490. package/dist/chunk-BIF4SHMD.js.map +0 -1
  491. package/dist/chunk-C23BYTJV.js.map +0 -1
  492. package/dist/chunk-CSY44OKU.js.map +0 -1
  493. package/dist/chunk-DDA55IVB.js.map +0 -1
  494. package/dist/chunk-HNDIND7P.js.map +0 -1
  495. package/dist/chunk-L6CQ7WIW.js.map +0 -1
  496. package/dist/chunk-MKQBXQKU.js.map +0 -1
  497. package/dist/chunk-QQXWFDRD.js.map +0 -1
  498. package/dist/chunk-QTO6BMQZ.js.map +0 -1
  499. package/dist/chunk-WZWZRZ2V.js.map +0 -1
  500. /package/dist/{auto-sync-TDHLZPVP.js.map → auto-sync-NR3GPCOA.js.map} +0 -0
  501. /package/dist/{chunk-LLJMAULE.js.map → chunk-2FKEIPIT.js.map} +0 -0
  502. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  503. /package/dist/{chunk-TKFGFDPJ.js.map → chunk-3TIZPSJC.js.map} +0 -0
  504. /package/dist/{chunk-B4T3FQMT.js.map → chunk-43SEBY2M.js.map} +0 -0
  505. /package/dist/{chunk-LVKYLMSM.js.map → chunk-46SZBVDS.js.map} +0 -0
  506. /package/dist/{chunk-YKZLOPTC.js.map → chunk-5FD7JMTX.js.map} +0 -0
  507. /package/dist/{chunk-WM5LD6AT.js.map → chunk-6CYHUWCO.js.map} +0 -0
  508. /package/dist/{chunk-YR65W7BV.js.map → chunk-BRR5EUTF.js.map} +0 -0
  509. /package/dist/{chunk-JKW663ZN.js.map → chunk-CIAI24TQ.js.map} +0 -0
  510. /package/dist/{chunk-YDWPYDML.js.map → chunk-EKTP7MNS.js.map} +0 -0
  511. /package/dist/{chunk-6LEIWWAF.js.map → chunk-F2JRDPON.js.map} +0 -0
  512. /package/dist/{chunk-VYQ4URQB.js.map → chunk-FFRTNF3L.js.map} +0 -0
  513. /package/dist/{chunk-TY6MKIWP.js.map → chunk-FONWBCVY.js.map} +0 -0
  514. /package/dist/{chunk-XSV2YDCJ.js.map → chunk-FPLM2TKD.js.map} +0 -0
  515. /package/dist/{chunk-VTU5IEKY.js.map → chunk-FTPONF6B.js.map} +0 -0
  516. /package/dist/{chunk-6PZGKGAO.js.map → chunk-FWX7W5PM.js.map} +0 -0
  517. /package/dist/{chunk-QPW3PAIH.js.map → chunk-G6DFSS6A.js.map} +0 -0
  518. /package/dist/{chunk-2DD6ZHOM.js.map → chunk-GJ776BC2.js.map} +0 -0
  519. /package/dist/{chunk-XKAR4RF6.js.map → chunk-GN3WRFJY.js.map} +0 -0
  520. /package/dist/{chunk-RG426DJO.js.map → chunk-H7NAZ3UB.js.map} +0 -0
  521. /package/dist/{chunk-BSXGTDVJ.js.map → chunk-HBUFQRIM.js.map} +0 -0
  522. /package/dist/{chunk-FFEBSFVT.js.map → chunk-HGJ6NJ4E.js.map} +0 -0
  523. /package/dist/{chunk-5JKUSBMX.js.map → chunk-IKP5OYQQ.js.map} +0 -0
  524. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  525. /package/dist/{chunk-DBY26JJJ.js.map → chunk-IQ4NVWEF.js.map} +0 -0
  526. /package/dist/{chunk-7Q7JWLQ4.js.map → chunk-JYG4OLWH.js.map} +0 -0
  527. /package/dist/{chunk-3OTSVCPK.js.map → chunk-K26NWEOC.js.map} +0 -0
  528. /package/dist/{chunk-HOIRB77A.js.map → chunk-L6Z5OAS4.js.map} +0 -0
  529. /package/dist/{chunk-7RZJWZ46.js.map → chunk-LEPOP46W.js.map} +0 -0
  530. /package/dist/{chunk-NGT35IGQ.js.map → chunk-MIH3CJPN.js.map} +0 -0
  531. /package/dist/{chunk-QPAQSG3F.js.map → chunk-MK5VEWCR.js.map} +0 -0
  532. /package/dist/{chunk-L7RTUEDG.js.map → chunk-OCIP6YMD.js.map} +0 -0
  533. /package/dist/{chunk-UWJNKGBA.js.map → chunk-QZLDRQ4I.js.map} +0 -0
  534. /package/dist/{chunk-UYOXFFEY.js.map → chunk-RUNORG2P.js.map} +0 -0
  535. /package/dist/{chunk-LC3FD4BV.js.map → chunk-RZQ6W2W4.js.map} +0 -0
  536. /package/dist/{chunk-MRGJZ75M.js.map → chunk-SMNYAMJD.js.map} +0 -0
  537. /package/dist/{chunk-DN2IDKI5.js.map → chunk-TO7ZTWJW.js.map} +0 -0
  538. /package/dist/{chunk-K2TDK7GG.js.map → chunk-TWTUGCTY.js.map} +0 -0
  539. /package/dist/{chunk-CUTFSLSD.js.map → chunk-UCTS3DDH.js.map} +0 -0
  540. /package/dist/{chunk-JYIJNNN3.js.map → chunk-UG6VXIOK.js.map} +0 -0
  541. /package/dist/{chunk-QOQQMVR2.js.map → chunk-UKYS3NU5.js.map} +0 -0
  542. /package/dist/{chunk-VZRFGJCH.js.map → chunk-UPLZNJKN.js.map} +0 -0
  543. /package/dist/{chunk-DOOWFDWN.js.map → chunk-V6ERBR7U.js.map} +0 -0
  544. /package/dist/{chunk-N7CXQ5TA.js.map → chunk-XZHE2DEN.js.map} +0 -0
  545. /package/dist/{chunk-6NNNURLK.js.map → chunk-Z54EM5YZ.js.map} +0 -0
  546. /package/dist/{chunk-F2Y6I74C.js.map → chunk-ZRZVLB7O.js.map} +0 -0
  547. /package/dist/{chunk-CLKBV3Z3.js.map → chunk-ZUKIUXHQ.js.map} +0 -0
  548. /package/dist/{contradiction-scan-KCADHXPE.js.map → contradiction-scan-JDTBXRGW.js.map} +0 -0
  549. /package/dist/{first-start-migration-LMMZFLKK.js.map → first-start-migration-CRZCZAJN.js.map} +0 -0
  550. /package/dist/{tier-stats-JDN6HRNT.js.map → tier-stats-EXYSKSVN.js.map} +0 -0
@@ -1,6 +1,7 @@
1
+ import { randomUUID } from "node:crypto";
2
+
1
3
  import type { StorageManager } from "../index.js";
2
4
  import { log } from "../logger.js";
3
- import { stripAttributesSuffix } from "../structured-attributes.js";
4
5
  import type { MemoryFile } from "../types.js";
5
6
  import type { HeldFileLockController } from "../utils/serialize-mutations.js";
6
7
  import { composeMemoryEnvelope } from "../write-envelope.js";
@@ -9,10 +10,30 @@ import {
9
10
  SUPPORT_PASSPORT_CARD_TAG,
10
11
  type StoredSupportPassportCard,
11
12
  computeSupportPassportOwnerKey,
12
- decodeSupportPassportNamespaceAttributes,
13
13
  encodeSupportPassportNamespaceAttributes,
14
- projectSupportPassportCard,
15
14
  } from "./card-projection.js";
15
+ import {
16
+ completeSupportPassportReplacementPrior,
17
+ prepareSupportPassportReplacementPrior,
18
+ validateSupportPassportReplacementPrior,
19
+ } from "./card-replacement.js";
20
+ import {
21
+ MAX_OWNER_VISIBLE_CARDS,
22
+ type SupportPassportCardServiceDependencies,
23
+ type SupportPassportOwnerScope,
24
+ invalidInput,
25
+ isStorageConflict,
26
+ once,
27
+ ownerListCards,
28
+ ownerVisibleCards,
29
+ ownsMemory,
30
+ projectOwnedCard,
31
+ projectRequiredCard,
32
+ requireRevision,
33
+ requireStatus,
34
+ revisionFor,
35
+ validateOwnerScope,
36
+ } from "./card-state.js";
16
37
  import {
17
38
  type SupportPassportCard,
18
39
  type SupportPassportCardCategory,
@@ -22,52 +43,41 @@ import {
22
43
  SupportPassportListCardsInputSchema,
23
44
  type SupportPassportManualDraftInput,
24
45
  SupportPassportManualDraftInputSchema,
25
- SupportPassportNamespaceSchema,
26
46
  type SupportPassportReplaceCardInput,
27
47
  SupportPassportReplaceCardInputSchema,
28
- computeSupportPassportCardRevision,
29
48
  } from "./contracts.js";
30
49
  import { SupportPassportError } from "./errors.js";
50
+ import {
51
+ type GeneratedBatchMarker,
52
+ commitSupportPassportGeneratedBatch,
53
+ isCommittedGeneratedCard,
54
+ persistSupportPassportGeneratedBatchMarker,
55
+ projectCommittedSupportPassportCards,
56
+ readCommittedSupportPassportCards,
57
+ recoverSupportPassportGeneratedBatches,
58
+ rollbackSupportPassportGeneratedBatch,
59
+ } from "./generated-batch.js";
60
+ import { type SupportPassportDraftCard, SupportPassportDraftOutputSchema } from "./model-contracts.js";
31
61
  import { withSupportPassportOwnerLock } from "./owner-lock.js";
32
-
33
- export interface SupportPassportOwnerScope {
34
- principal: string;
35
- namespace: string;
36
- storage: StorageManager;
37
- }
38
-
39
- export interface SupportPassportCardServiceDependencies {
40
- resolveOwner(principal: string): Promise<SupportPassportOwnerScope>;
41
- now?: () => Date;
42
- }
43
-
44
- const OWNER_VISIBLE_STATUSES = new Set(["pending_review", "active"]);
45
- const MAX_OWNER_VISIBLE_CARDS = 100;
46
-
47
- function invalidInput(): SupportPassportError {
48
- return new SupportPassportError("invalid_input", "The support card request is invalid.", 400);
49
- }
50
-
51
- function isStorageConflict(error: unknown): error is SupportPassportError {
52
- return error instanceof SupportPassportError && error.code === "storage_conflict";
53
- }
54
-
62
+ export type { SupportPassportCardServiceDependencies, SupportPassportOwnerScope } from "./card-state.js";
55
63
  export class SupportPassportCardService {
56
64
  private readonly resolveOwner: SupportPassportCardServiceDependencies["resolveOwner"];
57
65
  private readonly now: () => Date;
58
-
59
66
  constructor(dependencies: SupportPassportCardServiceDependencies) {
60
67
  this.resolveOwner = dependencies.resolveOwner;
61
68
  this.now = dependencies.now ?? (() => new Date());
62
69
  }
63
70
 
64
- async listCards(input: { principal: string }): Promise<SupportPassportCard[]> {
71
+ async listCards(input: {
72
+ principal: string;
73
+ }): Promise<SupportPassportCard[]> {
65
74
  const parsed = SupportPassportListCardsInputSchema.safeParse(input);
66
75
  if (!parsed.success) throw invalidInput();
67
76
  const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
68
- return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
69
- const stored = await this.readStoredCards(storage, lock, principal, namespace);
70
- const cards = this.ownerVisibleCards(stored)
77
+ return await withSupportPassportOwnerLock(storage, { namespace, principal }, async () => {
78
+ const memories = await storage.readAllMemories();
79
+ const stored = await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
80
+ const cards = ownerListCards(stored)
71
81
  .sort((a, b) => a.order - b.order || a.card.cardId.localeCompare(b.card.cardId))
72
82
  .map((item) => item.card);
73
83
  const output = SupportPassportCardListSchema.safeParse(cards);
@@ -78,12 +88,17 @@ export class SupportPassportCardService {
78
88
  });
79
89
  }
80
90
 
81
- async createManualDraft(input: SupportPassportManualDraftInput): Promise<SupportPassportCard> {
91
+ async createManualDraft(
92
+ input: SupportPassportManualDraftInput,
93
+ options: { signal?: AbortSignal; onCommitted?: () => void } = {}
94
+ ): Promise<SupportPassportCard> {
82
95
  const parsed = SupportPassportManualDraftInputSchema.safeParse(input);
83
96
  if (!parsed.success) throw invalidInput();
84
97
  const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
85
- return await withSupportPassportOwnerLock(storage, { namespace, principal }, (lock) =>
86
- this.createDraft(
98
+ const onCommitted = once(options.onCommitted);
99
+ return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
100
+ options.signal?.throwIfAborted();
101
+ const created = await this.createDraft(
87
102
  storage,
88
103
  {
89
104
  title: parsed.data.title,
@@ -91,21 +106,127 @@ export class SupportPassportCardService {
91
106
  category: parsed.data.category,
92
107
  reviewBy: parsed.data.reviewBy,
93
108
  sourceMemoryIds: [],
109
+ onCommitted,
94
110
  },
95
111
  lock,
96
112
  principal,
97
113
  namespace
98
- )
99
- );
114
+ );
115
+ if (!options.signal?.aborted) return created;
116
+ if (!(await this.rejectGeneratedDraft(storage, created.cardId, lock, principal, namespace))) {
117
+ throw new SupportPassportError("storage_conflict", "A cancelled support card could not be rolled back.", 500);
118
+ }
119
+ options.signal.throwIfAborted();
120
+ return created;
121
+ });
122
+ }
123
+
124
+ async createGeneratedDrafts(input: {
125
+ principal: string;
126
+ cards: SupportPassportDraftCard[];
127
+ }): Promise<SupportPassportCard[]> {
128
+ const principal = SupportPassportListCardsInputSchema.safeParse({
129
+ principal: input.principal,
130
+ });
131
+ if (!principal.success) throw invalidInput();
132
+ const owner = await this.resolveOwnerScope(principal.data.principal);
133
+ return await this.createGeneratedDraftsForOwner({
134
+ authenticatedPrincipal: principal.data.principal,
135
+ owner,
136
+ cards: input.cards,
137
+ });
138
+ }
139
+
140
+ async createGeneratedDraftsForOwner(input: {
141
+ authenticatedPrincipal: string;
142
+ owner: SupportPassportOwnerScope;
143
+ cards: SupportPassportDraftCard[];
144
+ signal?: AbortSignal;
145
+ onCommitted?: () => void;
146
+ commitWithValidatedSources?: (commit: () => Promise<void>) => Promise<void>;
147
+ }): Promise<SupportPassportCard[]> {
148
+ const output = SupportPassportDraftOutputSchema.safeParse({
149
+ cards: input.cards,
150
+ });
151
+ if (!output.success) throw invalidInput();
152
+ const owner = validateOwnerScope(input.owner, input.authenticatedPrincipal);
153
+ const { principal, namespace, storage } = owner;
154
+ const onCommitted = once(input.onCommitted);
155
+ return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
156
+ const batchContext = this.generatedBatchContext(storage, lock, principal, namespace, onCommitted);
157
+ const batchId = randomUUID();
158
+ const created: SupportPassportCard[] = [];
159
+ const createdIds: string[] = [];
160
+ const createdRecords: StoredSupportPassportCard[] = [];
161
+ let marker: GeneratedBatchMarker | null = null;
162
+ try {
163
+ input.signal?.throwIfAborted();
164
+ input.signal?.throwIfAborted();
165
+ const storedCards = await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
166
+ if (ownerVisibleCards(storedCards).length + output.data.cards.length > MAX_OWNER_VISIBLE_CARDS) {
167
+ throw new SupportPassportError(
168
+ "invalid_input",
169
+ "A support passport can contain at most 100 visible cards.",
170
+ 400
171
+ );
172
+ }
173
+ let nextOrder = storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
174
+ marker = await persistSupportPassportGeneratedBatchMarker(batchContext, batchId, output.data.cards.length);
175
+ for (const card of output.data.cards) {
176
+ input.signal?.throwIfAborted();
177
+ created.push(
178
+ await this.persistDraft(
179
+ storage,
180
+ {
181
+ title: card.title,
182
+ statement: card.statement,
183
+ category: card.category,
184
+ sourceMemoryIds: card.sourceMemoryIds,
185
+ order: nextOrder,
186
+ generatedBatch: { batchId, size: output.data.cards.length },
187
+ onPersisted: (record) => {
188
+ createdIds.push(record.card.cardId);
189
+ createdRecords.push(record);
190
+ onCommitted();
191
+ },
192
+ },
193
+ lock,
194
+ principal,
195
+ namespace
196
+ )
197
+ );
198
+ nextOrder += 1;
199
+ }
200
+ input.signal?.throwIfAborted();
201
+ const commit = async () =>
202
+ await commitSupportPassportGeneratedBatch(batchContext, marker!, createdRecords);
203
+ if (input.commitWithValidatedSources) await input.commitWithValidatedSources(commit);
204
+ else await commit();
205
+ return created;
206
+ } catch (error) {
207
+ if (!marker) throw error;
208
+ if (
209
+ !(await rollbackSupportPassportGeneratedBatch(batchContext, batchId, createdIds))
210
+ ) {
211
+ throw new SupportPassportError("storage_conflict", "A generated draft batch could not be rolled back.", 500);
212
+ }
213
+ throw error;
214
+ }
215
+ });
100
216
  }
101
217
 
102
- async replaceCard(input: SupportPassportReplaceCardInput): Promise<SupportPassportCard> {
218
+ async replaceCard(
219
+ input: SupportPassportReplaceCardInput,
220
+ options: { signal?: AbortSignal; onCommitted?: () => void } = {}
221
+ ): Promise<SupportPassportCard> {
103
222
  const parsed = SupportPassportReplaceCardInputSchema.safeParse(input);
104
223
  if (!parsed.success) throw invalidInput();
105
224
  const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
225
+ const onCommitted = once(options.onCommitted);
106
226
  return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
227
+ options.signal?.throwIfAborted();
107
228
  const loadedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
108
- const storedCards = await this.readProjectedCards(storage, namespace, principal);
229
+ const storedCards = await readCommittedSupportPassportCards(storage, namespace, principal);
109
230
  const interruptedReplacements = storedCards.filter(
110
231
  (item) =>
111
232
  item.card.status === "pending_review" &&
@@ -142,22 +263,26 @@ export class SupportPassportCardService {
142
263
  interruptedReplacement.memory,
143
264
  lock,
144
265
  principal,
145
- namespace
266
+ namespace,
267
+ {},
268
+ onCommitted
146
269
  );
147
- return this.projectRequiredCard(recovered, namespace, principal).card;
270
+ return projectRequiredCard(recovered, namespace, principal).card;
148
271
  }
149
- this.requireRevision(loadedPrior, parsed.data.expectedRevision);
150
- await this.readStoredCards(storage, lock, principal, namespace);
272
+ requireRevision(loadedPrior, parsed.data.expectedRevision);
273
+ await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
151
274
  const refreshedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
152
- this.requireRevision(refreshedPrior, parsed.data.expectedRevision);
275
+ requireRevision(refreshedPrior, parsed.data.expectedRevision);
153
276
  const recoveredMemory = await this.recoverReplacementTransition(
154
277
  storage,
155
278
  refreshedPrior.memory,
156
279
  lock,
157
280
  principal,
158
- namespace
281
+ namespace,
282
+ {},
283
+ onCommitted
159
284
  );
160
- const prior = this.projectRequiredCard(recoveredMemory, namespace, principal);
285
+ const prior = projectRequiredCard(recoveredMemory, namespace, principal);
161
286
  if (prior.card.status !== "active" && prior.card.status !== "pending_review") {
162
287
  throw new SupportPassportError(
163
288
  "invalid_card_status",
@@ -178,11 +303,22 @@ export class SupportPassportCardService {
178
303
  replacedRevision: prior.card.revision,
179
304
  order: prior.order,
180
305
  draftReplacementPrepared: prior.card.status === "pending_review",
306
+ onCommitted,
181
307
  },
182
308
  lock,
183
309
  principal,
184
310
  namespace
185
311
  );
312
+ if (options.signal?.aborted) {
313
+ if (!(await this.rejectGeneratedDraft(storage, replacement.cardId, lock, principal, namespace))) {
314
+ throw new SupportPassportError(
315
+ "storage_conflict",
316
+ "A cancelled support card edit could not be rolled back.",
317
+ 500
318
+ );
319
+ }
320
+ options.signal.throwIfAborted();
321
+ }
186
322
  if (prior.card.status === "active") return replacement;
187
323
 
188
324
  const replacedAt = this.now().toISOString();
@@ -193,12 +329,27 @@ export class SupportPassportCardService {
193
329
  { actor: principal, reasonCode: "owner-replaced-draft" }
194
330
  );
195
331
  if (rejected) {
196
- return await this.finishPreparedDraftReplacement(storage, replacement.cardId, lock, principal, namespace);
332
+ onCommitted();
333
+ return await this.finishPreparedDraftReplacement(
334
+ storage,
335
+ replacement.cardId,
336
+ lock,
337
+ principal,
338
+ namespace,
339
+ onCommitted
340
+ );
197
341
  }
198
342
  const currentPrior = await storage.getMemoryById(prior.card.cardId);
199
- const currentPriorCard = currentPrior ? this.projectOwnedCard(currentPrior, namespace, principal) : null;
343
+ const currentPriorCard = currentPrior ? projectOwnedCard(currentPrior, namespace, principal) : null;
200
344
  if (currentPriorCard?.card.status === "rejected") {
201
- return await this.finishPreparedDraftReplacement(storage, replacement.cardId, lock, principal, namespace);
345
+ return await this.finishPreparedDraftReplacement(
346
+ storage,
347
+ replacement.cardId,
348
+ lock,
349
+ principal,
350
+ namespace,
351
+ onCommitted
352
+ );
202
353
  }
203
354
 
204
355
  await this.rejectCreatedDraft(
@@ -207,28 +358,36 @@ export class SupportPassportCardService {
207
358
  "draft-replacement-failed",
208
359
  lock,
209
360
  principal,
210
- namespace
361
+ namespace,
362
+ onCommitted
211
363
  );
212
364
  throw new SupportPassportError("storage_conflict", "The support card changed before it was edited.", 409);
213
365
  });
214
366
  }
215
367
 
216
- async approveCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
368
+ async approveCard(
369
+ input: SupportPassportCardMutationInput,
370
+ options: { signal?: AbortSignal; onCommitted?: () => void } = {}
371
+ ): Promise<SupportPassportCard> {
217
372
  const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
218
373
  if (!parsed.success) throw invalidInput();
219
374
  const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
375
+ const onCommitted = once(options.onCommitted);
220
376
  return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
377
+ options.signal?.throwIfAborted();
221
378
  const loadedCard = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
222
- this.requireRevision(loadedCard, parsed.data.expectedRevision);
223
- this.requireStatus(loadedCard, "pending_review");
379
+ requireRevision(loadedCard, parsed.data.expectedRevision);
380
+ requireStatus(loadedCard, "pending_review");
224
381
  const recoveredMemory = await this.recoverReplacementTransition(
225
382
  storage,
226
383
  loadedCard.memory,
227
384
  lock,
228
385
  principal,
229
- namespace
386
+ namespace,
387
+ {},
388
+ onCommitted
230
389
  );
231
- const card = this.projectRequiredCard(recoveredMemory, namespace, principal);
390
+ const card = projectRequiredCard(recoveredMemory, namespace, principal);
232
391
  if (card.card.status === "active") return card.card;
233
392
  if (card.card.status === "rejected") {
234
393
  throw new SupportPassportError(
@@ -237,8 +396,8 @@ export class SupportPassportCardService {
237
396
  409
238
397
  );
239
398
  }
240
- this.requireStatus(card, "pending_review");
241
- await this.validatePriorForReplacement(storage, card, namespace, principal);
399
+ requireStatus(card, "pending_review");
400
+ await validateSupportPassportReplacementPrior(storage, card, namespace, principal);
242
401
  const updatedAt = this.now().toISOString();
243
402
  await this.requireOwnerLock(lock);
244
403
  let approved: boolean;
@@ -246,21 +405,26 @@ export class SupportPassportCardService {
246
405
  approved = await storage.writeMemoryFrontmatterIfUnchanged(
247
406
  card.memory,
248
407
  { status: "active", updated: updatedAt },
249
- { actor: principal, reasonCode: "owner-approved" }
408
+ {
409
+ actor: principal,
410
+ reasonCode: parsed.data.reasonCode ?? "owner-approved",
411
+ }
250
412
  );
251
413
  } catch (error) {
252
414
  const current = await storage.getMemoryById(card.card.cardId);
253
- const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
415
+ const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
254
416
  if (currentCard?.card.status === "active") {
255
- return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace);
417
+ onCommitted();
418
+ return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
256
419
  }
257
420
  throw error;
258
421
  }
259
422
  if (!approved) {
260
423
  const current = await storage.getMemoryById(card.card.cardId);
261
- const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
424
+ const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
262
425
  if (currentCard?.card.status === "active") {
263
- return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace);
426
+ onCommitted();
427
+ return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
264
428
  }
265
429
  throw new SupportPassportError("storage_conflict", "The support card changed before approval.", 409);
266
430
  }
@@ -268,11 +432,12 @@ export class SupportPassportCardService {
268
432
  ...card.card,
269
433
  status: "active",
270
434
  updatedAt,
271
- revision: this.revisionFor(card.card, "active", updatedAt),
435
+ revision: revisionFor(card.card, "active", updatedAt),
272
436
  };
437
+ onCommitted();
273
438
  try {
274
439
  const current = await this.requireCard(storage, card.card.cardId, namespace, principal);
275
- return await this.finishCommittedApproval(storage, current, lock, principal, namespace);
440
+ return await this.finishCommittedApproval(storage, current, lock, principal, namespace, onCommitted);
276
441
  } catch (error) {
277
442
  if (isStorageConflict(error)) throw error;
278
443
  log.warn(
@@ -283,12 +448,18 @@ export class SupportPassportCardService {
283
448
  });
284
449
  }
285
450
 
286
- async rejectCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
287
- return await this.changeStatus(input, "pending_review", "rejected");
451
+ async rejectCard(
452
+ input: SupportPassportCardMutationInput,
453
+ options: { signal?: AbortSignal; onCommitted?: () => void } = {}
454
+ ): Promise<SupportPassportCard> {
455
+ return await this.changeStatus(input, "pending_review", "rejected", "owner-rejected", options);
288
456
  }
289
457
 
290
- async withdrawCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
291
- return await this.changeStatus(input, "active", "archived");
458
+ async withdrawCard(
459
+ input: SupportPassportCardMutationInput,
460
+ options: { signal?: AbortSignal; onCommitted?: () => void } = {}
461
+ ): Promise<SupportPassportCard> {
462
+ return await this.changeStatus(input, "active", "archived", "owner-withdrew", options);
292
463
  }
293
464
 
294
465
  private async createDraft(
@@ -304,6 +475,9 @@ export class SupportPassportCardService {
304
475
  replacedRevision?: string;
305
476
  order?: number;
306
477
  draftReplacementPrepared?: boolean;
478
+ generatedBatch?: { batchId: string; size: number };
479
+ onPersisted?: (card: StoredSupportPassportCard) => void;
480
+ onCommitted?: () => void;
307
481
  },
308
482
  lock: HeldFileLockController,
309
483
  principal: string,
@@ -311,8 +485,8 @@ export class SupportPassportCardService {
311
485
  ): Promise<SupportPassportCard> {
312
486
  const now = this.now();
313
487
  const reviewBy = input.reviewBy ?? now.toISOString();
314
- const storedCards = await this.readStoredCards(storage, lock, principal, namespace);
315
- const visibleCards = this.ownerVisibleCards(storedCards);
488
+ const storedCards = await this.readStoredCards(storage, lock, principal, namespace, input.onCommitted);
489
+ const visibleCards = ownerListCards(storedCards);
316
490
  const replacesVisibleCard = visibleCards.some(
317
491
  (item) =>
318
492
  (input.replacesDraftId === item.card.cardId && item.card.status === "pending_review") ||
@@ -321,8 +495,34 @@ export class SupportPassportCardService {
321
495
  if (visibleCards.length - (replacesVisibleCard ? 1 : 0) >= MAX_OWNER_VISIBLE_CARDS) {
322
496
  throw new SupportPassportError("invalid_input", "A support passport can contain at most 100 visible cards.", 400);
323
497
  }
324
- const maximumOrder = storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1);
325
- const order = input.order ?? maximumOrder + 1;
498
+ const order = input.order ?? storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
499
+ return await this.persistDraft(storage, { ...input, order }, lock, principal, namespace);
500
+ }
501
+
502
+ private async persistDraft(
503
+ storage: StorageManager,
504
+ input: {
505
+ title: string;
506
+ statement: string;
507
+ category: SupportPassportCardCategory;
508
+ reviewBy?: string;
509
+ sourceMemoryIds: string[];
510
+ supersedes?: string;
511
+ replacesDraftId?: string;
512
+ replacedRevision?: string;
513
+ order: number;
514
+ draftReplacementPrepared?: boolean;
515
+ generatedBatch?: { batchId: string; size: number };
516
+ onPersisted?: (card: StoredSupportPassportCard) => void;
517
+ onCommitted?: () => void;
518
+ },
519
+ lock: HeldFileLockController,
520
+ principal: string,
521
+ namespace: string
522
+ ): Promise<SupportPassportCard> {
523
+ const now = this.now();
524
+ const reviewBy = input.reviewBy ?? now.toISOString();
525
+ const order = input.order;
326
526
  if (!Number.isSafeInteger(order)) {
327
527
  throw new SupportPassportError("storage_conflict", "The support card order range is exhausted.", 409);
328
528
  }
@@ -338,15 +538,26 @@ export class SupportPassportCardService {
338
538
  [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.category]: input.category,
339
539
  [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.order]: String(order),
340
540
  [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.reviewBy]: reviewBy,
341
- [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.sourceMemoryIds]: input.sourceMemoryIds.join(","),
342
541
  ...(input.replacesDraftId
343
- ? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacesDraftId]: input.replacesDraftId }
542
+ ? {
543
+ [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacesDraftId]: input.replacesDraftId,
544
+ }
344
545
  : {}),
345
546
  ...(input.replacedRevision
346
- ? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacedRevision]: input.replacedRevision }
547
+ ? {
548
+ [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacedRevision]: input.replacedRevision,
549
+ }
347
550
  : {}),
348
551
  ...(input.draftReplacementPrepared
349
- ? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared]: "true" }
552
+ ? {
553
+ [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared]: "true",
554
+ }
555
+ : {}),
556
+ ...(input.generatedBatch
557
+ ? {
558
+ [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchId]: input.generatedBatch.batchId,
559
+ [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchSize]: String(input.generatedBatch.size),
560
+ }
350
561
  : {}),
351
562
  },
352
563
  sourceReason: "support-passport",
@@ -364,46 +575,61 @@ export class SupportPassportCardService {
364
575
  if (written.tombstoneBlocked) {
365
576
  throw new SupportPassportError("storage_conflict", "The support card needs memory review before use.", 409);
366
577
  }
367
- return this.projectRequiredCard(written.memory, namespace, principal).card;
578
+ const stored = projectRequiredCard(written.memory, namespace, principal);
579
+ input.onPersisted?.(stored);
580
+ if (!input.generatedBatch) input.onCommitted?.();
581
+ return stored.card;
368
582
  }
369
583
 
370
584
  private async changeStatus(
371
585
  input: SupportPassportCardMutationInput,
372
586
  expectedStatus: "pending_review" | "active",
373
- nextStatus: "rejected" | "archived"
587
+ nextStatus: "rejected" | "archived",
588
+ defaultReasonCode: string,
589
+ options: { signal?: AbortSignal; onCommitted?: () => void }
374
590
  ): Promise<SupportPassportCard> {
375
591
  const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
376
592
  if (!parsed.success) throw invalidInput();
377
593
  const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
594
+ const onCommitted = once(options.onCommitted);
378
595
  return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
596
+ options.signal?.throwIfAborted();
379
597
  let card = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
380
- this.requireRevision(card, parsed.data.expectedRevision);
381
- this.requireStatus(card, expectedStatus);
598
+ requireRevision(card, parsed.data.expectedRevision);
599
+ requireStatus(card, expectedStatus);
382
600
  if (expectedStatus === "pending_review" && nextStatus === "rejected") {
383
- await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
601
+ await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
384
602
  card = await this.requireCard(storage, card.card.cardId, namespace, principal);
385
- this.requireRevision(card, parsed.data.expectedRevision);
386
- this.requireStatus(card, expectedStatus);
603
+ requireRevision(card, parsed.data.expectedRevision);
604
+ requireStatus(card, expectedStatus);
387
605
  }
388
606
  if (
389
607
  expectedStatus === "pending_review" ||
390
608
  (expectedStatus === "active" && (card.replacesDraftId || card.memory.frontmatter.supersedes))
391
609
  ) {
392
- card = this.projectRequiredCard(
393
- await this.recoverReplacementTransition(storage, card.memory, lock, principal, namespace, {
394
- rollbackConflictedApproval: expectedStatus !== "active",
395
- }),
610
+ card = projectRequiredCard(
611
+ await this.recoverReplacementTransition(
612
+ storage,
613
+ card.memory,
614
+ lock,
615
+ principal,
616
+ namespace,
617
+ {
618
+ rollbackConflictedApproval: expectedStatus !== "active",
619
+ },
620
+ onCommitted
621
+ ),
396
622
  namespace,
397
623
  principal
398
624
  );
399
625
  }
400
626
  if (card.card.status === nextStatus) return card.card;
401
- this.requireStatus(card, expectedStatus);
627
+ requireStatus(card, expectedStatus);
402
628
  if (expectedStatus === "active" && nextStatus === "archived") {
403
- await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
629
+ await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
404
630
  card = await this.requireCard(storage, card.card.cardId, namespace, principal);
405
- this.requireRevision(card, parsed.data.expectedRevision);
406
- this.requireStatus(card, expectedStatus);
631
+ requireRevision(card, parsed.data.expectedRevision);
632
+ requireStatus(card, expectedStatus);
407
633
  }
408
634
  const updatedAt = this.now().toISOString();
409
635
  await this.requireOwnerLock(lock);
@@ -414,7 +640,10 @@ export class SupportPassportCardService {
414
640
  updated: updatedAt,
415
641
  ...(nextStatus === "archived" ? { archivedAt: updatedAt } : {}),
416
642
  },
417
- { actor: principal, reasonCode: nextStatus === "archived" ? "owner-withdrew" : "owner-rejected" }
643
+ {
644
+ actor: principal,
645
+ reasonCode: parsed.data.reasonCode ?? defaultReasonCode,
646
+ }
418
647
  );
419
648
  if (!changed)
420
649
  throw new SupportPassportError(
@@ -422,41 +651,19 @@ export class SupportPassportCardService {
422
651
  "The support card changed before the request completed.",
423
652
  409
424
653
  );
654
+ onCommitted();
425
655
  return {
426
656
  ...card.card,
427
657
  status: nextStatus,
428
658
  updatedAt,
429
- revision: this.revisionFor(card.card, nextStatus, updatedAt),
659
+ revision: revisionFor(card.card, nextStatus, updatedAt),
430
660
  };
431
661
  });
432
662
  }
433
663
 
434
- private revisionFor(card: SupportPassportCard, status: SupportPassportCard["status"], updatedAt: string): string {
435
- return computeSupportPassportCardRevision({
436
- cardId: card.cardId,
437
- title: card.title,
438
- statement: card.statement,
439
- category: card.category,
440
- status,
441
- updatedAt,
442
- reviewBy: card.reviewBy,
443
- });
444
- }
445
-
446
664
  private async resolveOwnerScope(principal: string): Promise<SupportPassportOwnerScope> {
447
- const requestedPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal });
448
665
  const owner = await this.resolveOwner(principal);
449
- const namespace = SupportPassportNamespaceSchema.safeParse(owner.namespace);
450
- const ownerPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal: owner.principal });
451
- if (
452
- !requestedPrincipal.success ||
453
- !namespace.success ||
454
- !ownerPrincipal.success ||
455
- ownerPrincipal.data.principal !== requestedPrincipal.data.principal
456
- ) {
457
- throw new SupportPassportError("card_data_invalid", "The support passport owner scope is invalid.", 500);
458
- }
459
- return { ...owner, principal: ownerPrincipal.data.principal, namespace: namespace.data };
666
+ return validateOwnerScope(owner, principal);
460
667
  }
461
668
 
462
669
  private async rejectCreatedDraft(
@@ -465,7 +672,8 @@ export class SupportPassportCardService {
465
672
  reasonCode: string,
466
673
  lock: HeldFileLockController,
467
674
  principal: string,
468
- namespace: string
675
+ namespace: string,
676
+ onCommitted?: () => void
469
677
  ): Promise<void> {
470
678
  try {
471
679
  const current = await this.requireCard(storage, cardId, namespace, principal);
@@ -475,7 +683,8 @@ export class SupportPassportCardService {
475
683
  { status: "rejected", updated: this.now().toISOString() },
476
684
  { actor: principal, reasonCode }
477
685
  );
478
- if (!rejected) log.warn("support passport could not roll back a replacement draft");
686
+ if (rejected) onCommitted?.();
687
+ else log.warn("support passport could not roll back a replacement draft");
479
688
  } catch (error) {
480
689
  log.warn(
481
690
  `support passport could not roll back a replacement draft: ${error instanceof Error ? error.message : String(error)}`
@@ -483,164 +692,70 @@ export class SupportPassportCardService {
483
692
  }
484
693
  }
485
694
 
486
- private async requireCard(
695
+ private async rejectGeneratedDraft(
487
696
  storage: StorageManager,
488
697
  cardId: string,
489
- namespace: string,
490
- principal: string
491
- ): Promise<StoredSupportPassportCard> {
492
- const stored = await storage.getMemoryById(cardId);
493
- const card = stored ? this.projectOwnedCard(stored, namespace, principal) : null;
494
- if (!card) throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
495
- return card;
496
- }
497
-
498
- private projectRequiredCard(memory: MemoryFile, namespace: string, principal: string): StoredSupportPassportCard {
499
- const card = projectSupportPassportCard(memory);
500
- if (!card) throw new SupportPassportError("card_data_invalid", "The support card data is invalid.", 500);
501
- if (
502
- card.namespace !== namespace ||
503
- card.owner !== computeSupportPassportOwnerKey(principal)
504
- ) {
505
- throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
506
- }
507
- return card;
508
- }
509
-
510
- private projectOwnedCard(memory: MemoryFile, namespace: string, principal: string): StoredSupportPassportCard | null {
511
- const card = projectSupportPassportCard(memory);
512
- return card?.namespace === namespace &&
513
- card.owner === computeSupportPassportOwnerKey(principal)
514
- ? card
515
- : null;
516
- }
517
-
518
- private ownsMemory(memory: MemoryFile, namespace: string, principal: string): boolean {
519
- const attributes = memory.frontmatter.structuredAttributes;
520
- return (
521
- attributes !== undefined &&
522
- decodeSupportPassportNamespaceAttributes(attributes) === namespace &&
523
- attributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner] === computeSupportPassportOwnerKey(principal)
524
- );
525
- }
526
-
527
- private async readProjectedCards(
528
- storage: StorageManager,
529
- namespace: string,
530
- principal: string
531
- ): Promise<StoredSupportPassportCard[]> {
532
- const owner = computeSupportPassportOwnerKey(principal);
533
- const projected = (await storage.readAllMemories())
534
- .map(projectSupportPassportCard)
535
- .filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
536
- const ids = new Set<string>();
537
- for (const item of projected) {
538
- if (ids.has(item.card.cardId)) {
539
- throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
540
- }
541
- ids.add(item.card.cardId);
542
- }
543
- return projected;
544
- }
545
-
546
- private requireRevision(card: StoredSupportPassportCard, expectedRevision: string): void {
547
- if (card.card.revision !== expectedRevision) {
548
- throw new SupportPassportError("revision_conflict", "The support card changed after it was loaded.", 409);
549
- }
550
- }
551
-
552
- private requireStatus(card: StoredSupportPassportCard, status: SupportPassportCard["status"]): void {
553
- if (card.card.status !== status) {
554
- throw new SupportPassportError("invalid_card_status", `The support card must have status ${status}.`, 409);
698
+ lock: HeldFileLockController,
699
+ principal: string,
700
+ namespace: string
701
+ ): Promise<boolean> {
702
+ for (let attempt = 0; attempt < 2; attempt += 1) {
703
+ const memory = await storage.getMemoryById(cardId);
704
+ if (!memory) return true;
705
+ const stored = projectOwnedCard(memory, namespace, principal);
706
+ if (!stored) return false;
707
+ if (stored.card.status === "rejected") return true;
708
+ if (stored.card.status !== "pending_review") return false;
709
+ await this.requireOwnerLock(lock);
710
+ const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
711
+ memory,
712
+ { status: "rejected", updated: this.now().toISOString() },
713
+ { actor: principal, reasonCode: "draft-batch-failed" }
714
+ );
715
+ if (rejected) return true;
555
716
  }
717
+ return false;
556
718
  }
557
719
 
558
720
  private async readStoredCards(
559
721
  storage: StorageManager,
560
722
  lock: HeldFileLockController,
561
723
  principal: string,
562
- namespace: string
724
+ namespace: string,
725
+ onCommitted?: () => void
563
726
  ): Promise<StoredSupportPassportCard[]> {
564
727
  const initialVersion = storage.getCorpusScanVersion();
565
728
  const initial = await storage.readAllMemories();
729
+ await recoverSupportPassportGeneratedBatches(
730
+ this.generatedBatchContext(storage, lock, principal, namespace, onCommitted),
731
+ initial
732
+ );
566
733
  for (const memory of initial) {
567
- const card = this.projectOwnedCard(memory, namespace, principal);
568
- if (card) await this.recoverReplacementTransition(storage, memory, lock, principal, namespace);
734
+ const card = projectOwnedCard(memory, namespace, principal);
735
+ if (card) {
736
+ await this.recoverReplacementTransition(storage, memory, lock, principal, namespace, {}, onCommitted);
737
+ }
569
738
  }
570
739
  const memories: MemoryFile[] =
571
740
  storage.getCorpusScanVersion() === initialVersion ? initial : await storage.readAllMemories();
572
- const owner = computeSupportPassportOwnerKey(principal);
573
- const projected = memories
574
- .map(projectSupportPassportCard)
575
- .filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
576
- const ids = new Set<string>();
577
- for (const item of projected) {
578
- if (ids.has(item.card.cardId)) {
579
- throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
580
- }
581
- ids.add(item.card.cardId);
582
- }
583
- return projected;
584
- }
585
-
586
- private ownerVisibleCards(storedCards: StoredSupportPassportCard[]): StoredSupportPassportCard[] {
587
- const visibleCards = storedCards.filter(
588
- (item) => OWNER_VISIBLE_STATUSES.has(item.card.status) && !item.draftReplacementPrepared
589
- );
590
- if (visibleCards.length <= MAX_OWNER_VISIBLE_CARDS) return visibleCards;
591
- const activeCardsWithPendingReplacements = new Set(
592
- visibleCards
593
- .filter((item) => item.card.status === "pending_review")
594
- .map((item) => item.memory.frontmatter.supersedes)
595
- .filter((cardId): cardId is string => typeof cardId === "string")
596
- );
597
- return visibleCards.filter(
598
- (item) => item.card.status !== "active" || !activeCardsWithPendingReplacements.has(item.card.cardId)
599
- );
741
+ return await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
600
742
  }
601
743
 
602
- private async preparePriorForReplacement(
744
+ private generatedBatchContext(
603
745
  storage: StorageManager,
604
- replacement: StoredSupportPassportCard,
605
746
  lock: HeldFileLockController,
606
747
  principal: string,
607
- namespace: string
608
- ): Promise<string | null> {
609
- const priorId = replacement.memory.frontmatter.supersedes;
610
- if (!priorId) return null;
611
- const priorMemory = await storage.getMemoryById(priorId);
612
- const priorIsOwned = priorMemory ? this.ownsMemory(priorMemory, namespace, principal) : false;
613
- const alreadyRetired =
614
- priorIsOwned &&
615
- priorMemory?.frontmatter.status === "superseded" &&
616
- priorMemory.frontmatter.supersededBy === replacement.card.cardId;
617
- const prior = priorMemory && priorIsOwned && !alreadyRetired ? projectSupportPassportCard(priorMemory) : null;
618
- if (prior?.card.status === "rejected") return null;
619
- if (!priorMemory || (!alreadyRetired && prior?.card.status !== "active")) {
620
- throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
621
- }
622
- if (alreadyRetired) return priorId;
623
- const retiredAt = this.now().toISOString();
624
- await this.requireOwnerLock(lock);
625
- const retired = await storage.writeMemoryFrontmatterIfUnchanged(
626
- priorMemory,
627
- {
628
- status: "superseded",
629
- supersededBy: replacement.card.cardId,
630
- supersededAt: retiredAt,
631
- supersessionCause: "direct",
632
- updated: retiredAt,
633
- },
634
- {
635
- actor: principal,
636
- reasonCode: "support-passport-replacement-pending",
637
- relatedMemoryIds: [replacement.card.cardId],
638
- }
639
- );
640
- if (!retired) {
641
- throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
642
- }
643
- return priorId;
748
+ namespace: string,
749
+ onCommitted?: () => void
750
+ ) {
751
+ return {
752
+ storage,
753
+ principal,
754
+ namespace,
755
+ now: this.now,
756
+ requireOwnerLock: async () => await this.requireOwnerLock(lock),
757
+ ...(onCommitted ? { onCommitted } : {}),
758
+ };
644
759
  }
645
760
 
646
761
  private async rejectPendingReplacementForPredecessor(
@@ -648,12 +763,13 @@ export class SupportPassportCardService {
648
763
  predecessor: StoredSupportPassportCard,
649
764
  lock: HeldFileLockController,
650
765
  principal: string,
651
- namespace: string
766
+ namespace: string,
767
+ onCommitted?: () => void
652
768
  ): Promise<void> {
653
769
  const storedCards =
654
770
  predecessor.card.status === "active"
655
- ? await this.readStoredCards(storage, lock, principal, namespace)
656
- : await this.readProjectedCards(storage, namespace, principal);
771
+ ? await this.readStoredCards(storage, lock, principal, namespace, onCommitted)
772
+ : await readCommittedSupportPassportCards(storage, namespace, principal);
657
773
  const replacements = storedCards.filter((item) => {
658
774
  if (item.card.status !== "pending_review") return false;
659
775
  return predecessor.card.status === "pending_review"
@@ -678,44 +794,27 @@ export class SupportPassportCardService {
678
794
  reasonCode: predecessor.card.status === "pending_review" ? "source-draft-rejected" : "predecessor-withdrawn",
679
795
  }
680
796
  );
681
- if (rejected) return;
797
+ if (rejected) {
798
+ onCommitted?.();
799
+ return;
800
+ }
682
801
  const current = await storage.getMemoryById(replacement.card.cardId);
683
- const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
802
+ const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
684
803
  if (currentCard?.card.status !== "rejected") {
685
804
  throw new SupportPassportError("storage_conflict", "The pending support card edit could not be cancelled.", 409);
686
805
  }
687
806
  }
688
807
 
689
- private async validatePriorForReplacement(
690
- storage: StorageManager,
691
- replacement: StoredSupportPassportCard,
692
- namespace: string,
693
- principal: string
694
- ): Promise<void> {
695
- const priorId = replacement.memory.frontmatter.supersedes;
696
- if (!priorId) return;
697
- const priorMemory = await storage.getMemoryById(priorId);
698
- const priorIsOwned = priorMemory ? this.ownsMemory(priorMemory, namespace, principal) : false;
699
- const alreadyRetired =
700
- priorIsOwned &&
701
- priorMemory?.frontmatter.status === "superseded" &&
702
- priorMemory.frontmatter.supersededBy === replacement.card.cardId;
703
- const prior = priorMemory && priorIsOwned && !alreadyRetired ? projectSupportPassportCard(priorMemory) : null;
704
- if (prior?.card.status === "rejected" || alreadyRetired) return;
705
- if (!priorMemory || prior?.card.status !== "active") {
706
- throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
707
- }
708
- }
709
-
710
808
  private async recoverReplacementTransition(
711
809
  storage: StorageManager,
712
810
  memory: MemoryFile,
713
811
  lock: HeldFileLockController,
714
812
  principal: string,
715
813
  namespace: string,
716
- options: { rollbackConflictedApproval?: boolean } = {}
814
+ options: { rollbackConflictedApproval?: boolean } = {},
815
+ onCommitted?: () => void
717
816
  ): Promise<MemoryFile> {
718
- const replacement = this.projectOwnedCard(memory, namespace, principal);
817
+ const replacement = projectOwnedCard(memory, namespace, principal);
719
818
  if (replacement?.card.status !== "pending_review" && replacement?.card.status !== "active") return memory;
720
819
  if (!replacement.replacesDraftId && !memory.frontmatter.supersedes) return memory;
721
820
  if (
@@ -724,9 +823,9 @@ export class SupportPassportCardService {
724
823
  ) {
725
824
  return memory;
726
825
  }
727
- await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace);
826
+ await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace, onCommitted);
728
827
  const currentMemory = (await storage.getMemoryById(replacement.card.cardId)) ?? memory;
729
- const currentCard = this.projectOwnedCard(currentMemory, namespace, principal);
828
+ const currentCard = projectOwnedCard(currentMemory, namespace, principal);
730
829
  if (currentCard?.card.status !== "pending_review" && currentCard?.card.status !== "active") {
731
830
  return currentMemory;
732
831
  }
@@ -736,7 +835,7 @@ export class SupportPassportCardService {
736
835
  const prior = await storage.getMemoryById(priorId);
737
836
  if (
738
837
  prior &&
739
- this.ownsMemory(prior, namespace, principal) &&
838
+ ownsMemory(prior, namespace, principal) &&
740
839
  prior.frontmatter.status === "superseded" &&
741
840
  prior.frontmatter.supersededBy === replacement.card.cardId
742
841
  ) {
@@ -746,17 +845,18 @@ export class SupportPassportCardService {
746
845
  replacement.card.cardId,
747
846
  lock,
748
847
  principal,
749
- namespace
848
+ namespace,
849
+ onCommitted
750
850
  );
751
851
  }
752
852
  return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
753
853
  }
754
854
  try {
755
- await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace);
855
+ await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace, onCommitted);
756
856
  } catch (error) {
757
857
  if (!isStorageConflict(error)) throw error;
758
858
  if (options.rollbackConflictedApproval === false) throw error;
759
- await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace);
859
+ await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace, onCommitted);
760
860
  }
761
861
  return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
762
862
  }
@@ -766,13 +866,33 @@ export class SupportPassportCardService {
766
866
  replacement: StoredSupportPassportCard,
767
867
  lock: HeldFileLockController,
768
868
  principal: string,
769
- namespace: string
869
+ namespace: string,
870
+ onCommitted?: () => void
770
871
  ): Promise<void> {
771
- const priorId = await this.preparePriorForReplacement(storage, replacement, lock, principal, namespace);
772
- if (priorId && !(await this.completePriorRetirement(storage, priorId, replacement, lock, principal, namespace))) {
872
+ const priorId = await prepareSupportPassportReplacementPrior({
873
+ storage,
874
+ replacement,
875
+ principal,
876
+ namespace,
877
+ now: this.now,
878
+ requireOwnerLock: () => this.requireOwnerLock(lock),
879
+ onCommitted,
880
+ });
881
+ if (
882
+ priorId &&
883
+ !(await completeSupportPassportReplacementPrior({
884
+ storage,
885
+ priorId,
886
+ replacement,
887
+ principal,
888
+ namespace,
889
+ requireOwnerLock: () => this.requireOwnerLock(lock),
890
+ }))
891
+ ) {
773
892
  throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
774
893
  }
775
- await this.markReplacementComplete(storage, replacement.card.cardId, lock, principal, namespace);
894
+ if (priorId) onCommitted?.();
895
+ await this.markReplacementComplete(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
776
896
  }
777
897
 
778
898
  private async finishCommittedApproval(
@@ -780,15 +900,23 @@ export class SupportPassportCardService {
780
900
  replacement: StoredSupportPassportCard,
781
901
  lock: HeldFileLockController,
782
902
  principal: string,
783
- namespace: string
903
+ namespace: string,
904
+ onCommitted?: () => void
784
905
  ): Promise<SupportPassportCard> {
785
906
  try {
786
907
  if (replacement.replacesDraftId || replacement.memory.frontmatter.supersedes) {
787
- await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace);
908
+ await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace, onCommitted);
788
909
  }
789
910
  } catch (error) {
790
911
  if (isStorageConflict(error)) {
791
- await this.rollbackConflictedApproval(storage, replacement.card.cardId, lock, principal, namespace);
912
+ await this.rollbackConflictedApproval(
913
+ storage,
914
+ replacement.card.cardId,
915
+ lock,
916
+ principal,
917
+ namespace,
918
+ onCommitted
919
+ );
792
920
  throw error;
793
921
  }
794
922
  log.warn(
@@ -803,7 +931,8 @@ export class SupportPassportCardService {
803
931
  replacementId: string,
804
932
  lock: HeldFileLockController,
805
933
  principal: string,
806
- namespace: string
934
+ namespace: string,
935
+ onCommitted?: () => void
807
936
  ): Promise<void> {
808
937
  const replacement = await this.requireCard(storage, replacementId, namespace, principal);
809
938
  if (replacement.card.status !== "active") return;
@@ -820,54 +949,19 @@ export class SupportPassportCardService {
820
949
  409
821
950
  );
822
951
  }
952
+ onCommitted?.();
823
953
  const priorId = replacement.memory.frontmatter.supersedes;
824
954
  if (priorId) {
825
- await this.restorePriorAfterApprovalFailure(storage, priorId, replacementId, lock, principal, namespace);
826
- }
827
- }
828
-
829
- private async completePriorRetirement(
830
- storage: StorageManager,
831
- priorId: string,
832
- replacement: StoredSupportPassportCard,
833
- lock: HeldFileLockController,
834
- principal: string,
835
- namespace: string
836
- ): Promise<boolean> {
837
- const replacementId = replacement.card.cardId;
838
- const prior = await storage.getMemoryById(priorId);
839
- if (
840
- !prior ||
841
- !this.ownsMemory(prior, namespace, principal) ||
842
- prior.frontmatter.status !== "superseded" ||
843
- prior.frontmatter.supersededBy !== replacementId
844
- ) {
845
- return false;
955
+ await this.restorePriorAfterApprovalFailure(
956
+ storage,
957
+ priorId,
958
+ replacementId,
959
+ lock,
960
+ principal,
961
+ namespace,
962
+ onCommitted
963
+ );
846
964
  }
847
- await this.requireOwnerLock(lock);
848
- const completed = await storage.supersedeMemory(
849
- priorId,
850
- replacementId,
851
- "support-passport-replacement",
852
- { supersessionCause: "direct" },
853
- {
854
- actor: principal,
855
- requireActive: true,
856
- acceptExactReplay: true,
857
- expectedSnapshot: prior,
858
- audit: {
859
- content: stripAttributesSuffix(prior.content),
860
- tags: ["supersession", "auto-resolved", "support-passport-audit"],
861
- structuredAttributes: {
862
- ...encodeSupportPassportNamespaceAttributes(namespace),
863
- [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner]: computeSupportPassportOwnerKey(principal),
864
- },
865
- source: "support-passport",
866
- },
867
- }
868
- );
869
- if (!completed) log.warn("support passport could not complete replacement retirement side effects");
870
- return completed;
871
965
  }
872
966
 
873
967
  private async recoverReplacedDraft(
@@ -875,11 +969,12 @@ export class SupportPassportCardService {
875
969
  replacement: StoredSupportPassportCard,
876
970
  lock: HeldFileLockController,
877
971
  principal: string,
878
- namespace: string
972
+ namespace: string,
973
+ onCommitted?: () => void
879
974
  ): Promise<void> {
880
975
  if (!replacement.replacesDraftId) return;
881
976
  const replacedDraft = await storage.getMemoryById(replacement.replacesDraftId);
882
- const projectedDraft = replacedDraft ? this.projectOwnedCard(replacedDraft, namespace, principal) : null;
977
+ const projectedDraft = replacedDraft ? projectOwnedCard(replacedDraft, namespace, principal) : null;
883
978
  if (
884
979
  replacedDraft &&
885
980
  projectedDraft?.card.status === "pending_review" &&
@@ -892,27 +987,42 @@ export class SupportPassportCardService {
892
987
  { actor: principal, reasonCode: "complete-draft-replacement" }
893
988
  );
894
989
  if (rejected) {
895
- await this.finishPreparedDraftReplacement(storage, replacement.card.cardId, lock, principal, namespace);
990
+ onCommitted?.();
991
+ await this.finishPreparedDraftReplacement(
992
+ storage,
993
+ replacement.card.cardId,
994
+ lock,
995
+ principal,
996
+ namespace,
997
+ onCommitted
998
+ );
896
999
  return;
897
1000
  }
898
1001
  }
899
1002
  const currentDraft = await storage.getMemoryById(replacement.replacesDraftId);
900
1003
  if (
901
1004
  currentDraft &&
902
- this.ownsMemory(currentDraft, namespace, principal) &&
1005
+ ownsMemory(currentDraft, namespace, principal) &&
903
1006
  currentDraft.frontmatter.status === "rejected"
904
1007
  ) {
905
- await this.finishPreparedDraftReplacement(storage, replacement.card.cardId, lock, principal, namespace);
1008
+ await this.finishPreparedDraftReplacement(
1009
+ storage,
1010
+ replacement.card.cardId,
1011
+ lock,
1012
+ principal,
1013
+ namespace,
1014
+ onCommitted
1015
+ );
906
1016
  return;
907
1017
  }
908
1018
  if (
909
1019
  currentDraft &&
910
- this.ownsMemory(currentDraft, namespace, principal) &&
1020
+ ownsMemory(currentDraft, namespace, principal) &&
911
1021
  currentDraft.frontmatter.status === "pending_review"
912
1022
  ) {
913
1023
  throw new SupportPassportError("storage_conflict", "The replaced draft changed during recovery.", 409);
914
1024
  }
915
- await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace);
1025
+ await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
916
1026
  }
917
1027
 
918
1028
  private async finishPreparedDraftReplacement(
@@ -920,11 +1030,14 @@ export class SupportPassportCardService {
920
1030
  replacementId: string,
921
1031
  lock: HeldFileLockController,
922
1032
  principal: string,
923
- namespace: string
1033
+ namespace: string,
1034
+ onCommitted?: () => void
924
1035
  ): Promise<SupportPassportCard> {
925
1036
  const replacement = await this.requireCard(storage, replacementId, namespace, principal);
926
1037
  if (!replacement.draftReplacementPrepared) return replacement.card;
927
- const structuredAttributes = { ...replacement.memory.frontmatter.structuredAttributes };
1038
+ const structuredAttributes = {
1039
+ ...replacement.memory.frontmatter.structuredAttributes,
1040
+ };
928
1041
  delete structuredAttributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared];
929
1042
  await this.requireOwnerLock(lock);
930
1043
  const finished = await storage.writeMemoryFrontmatterIfUnchanged(
@@ -933,8 +1046,15 @@ export class SupportPassportCardService {
933
1046
  { actor: principal, reasonCode: "draft-replacement-complete" }
934
1047
  );
935
1048
  if (finished) {
936
- return this.projectRequiredCard(
937
- { ...replacement.memory, frontmatter: { ...replacement.memory.frontmatter, structuredAttributes } },
1049
+ onCommitted?.();
1050
+ return projectRequiredCard(
1051
+ {
1052
+ ...replacement.memory,
1053
+ frontmatter: {
1054
+ ...replacement.memory.frontmatter,
1055
+ structuredAttributes,
1056
+ },
1057
+ },
938
1058
  namespace,
939
1059
  principal
940
1060
  ).card;
@@ -949,18 +1069,22 @@ export class SupportPassportCardService {
949
1069
  replacementId: string,
950
1070
  lock: HeldFileLockController,
951
1071
  principal: string,
952
- namespace: string
1072
+ namespace: string,
1073
+ onCommitted?: () => void
953
1074
  ): Promise<void> {
954
1075
  const currentReplacement = await this.requireCard(storage, replacementId, namespace, principal);
955
1076
  if (currentReplacement.card.status === "rejected") return;
956
- this.requireStatus(currentReplacement, "pending_review");
1077
+ requireStatus(currentReplacement, "pending_review");
957
1078
  await this.requireOwnerLock(lock);
958
1079
  const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
959
1080
  currentReplacement.memory,
960
1081
  { status: "rejected", updated: this.now().toISOString() },
961
1082
  { actor: principal, reasonCode: "replaced-draft-approved" }
962
1083
  );
963
- if (rejected) return;
1084
+ if (rejected) {
1085
+ onCommitted?.();
1086
+ return;
1087
+ }
964
1088
  const latest = await storage.getMemoryById(replacementId);
965
1089
  if (latest?.frontmatter.status !== "rejected") {
966
1090
  throw new SupportPassportError("storage_conflict", "The orphaned replacement could not be rejected.", 409);
@@ -973,15 +1097,16 @@ export class SupportPassportCardService {
973
1097
  replacementId: string,
974
1098
  lock: HeldFileLockController,
975
1099
  principal: string,
976
- namespace: string
1100
+ namespace: string,
1101
+ onCommitted?: () => void
977
1102
  ): Promise<void> {
978
1103
  const replacement = await storage.getMemoryById(replacementId);
979
- if (replacement && !this.ownsMemory(replacement, namespace, principal)) {
1104
+ if (replacement && !ownsMemory(replacement, namespace, principal)) {
980
1105
  throw new SupportPassportError("storage_conflict", "The replacement support card changed ownership.", 409);
981
1106
  }
982
1107
  if (replacement?.frontmatter.status === "active") return;
983
1108
  const prior = await storage.getMemoryById(priorId);
984
- if (prior && !this.ownsMemory(prior, namespace, principal)) {
1109
+ if (prior && !ownsMemory(prior, namespace, principal)) {
985
1110
  throw new SupportPassportError("storage_conflict", "The prior support card changed ownership.", 409);
986
1111
  }
987
1112
  if (prior?.frontmatter.status === "active" && prior.frontmatter.supersededBy === undefined) return;
@@ -1001,7 +1126,10 @@ export class SupportPassportCardService {
1001
1126
  },
1002
1127
  { actor: principal, reasonCode: "replacement-activation-failed" }
1003
1128
  );
1004
- if (restored) return;
1129
+ if (restored) {
1130
+ onCommitted?.();
1131
+ return;
1132
+ }
1005
1133
  const currentPrior = await storage.getMemoryById(priorId);
1006
1134
  if (currentPrior?.frontmatter.status === "active" && currentPrior.frontmatter.supersededBy === undefined) return;
1007
1135
  throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
@@ -1012,7 +1140,8 @@ export class SupportPassportCardService {
1012
1140
  replacementId: string,
1013
1141
  lock: HeldFileLockController,
1014
1142
  principal: string,
1015
- namespace: string
1143
+ namespace: string,
1144
+ onCommitted?: () => void
1016
1145
  ): Promise<void> {
1017
1146
  const replacement = await this.requireCard(storage, replacementId, namespace, principal);
1018
1147
  if (
@@ -1032,11 +1161,24 @@ export class SupportPassportCardService {
1032
1161
  },
1033
1162
  { actor: principal, reasonCode: "replacement-side-effects-complete" }
1034
1163
  );
1035
- if (!marked) log.warn("support passport could not mark replacement side effects complete");
1164
+ if (marked) onCommitted?.();
1165
+ else log.warn("support passport could not mark replacement side effects complete");
1036
1166
  }
1037
1167
 
1038
1168
  private async requireOwnerLock(lock: HeldFileLockController): Promise<void> {
1039
1169
  if (await lock.refresh()) return;
1040
1170
  throw new SupportPassportError("storage_conflict", "The support passport lock changed. Try again.", 409);
1041
1171
  }
1172
+
1173
+ private async requireCard(
1174
+ storage: StorageManager,
1175
+ cardId: string,
1176
+ namespace: string,
1177
+ principal: string
1178
+ ): Promise<StoredSupportPassportCard> {
1179
+ const memory = await storage.getMemoryById(cardId);
1180
+ const card = memory ? projectOwnedCard(memory, namespace, principal) : null;
1181
+ if (card && (await isCommittedGeneratedCard(storage, card))) return card;
1182
+ throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
1183
+ }
1042
1184
  }