@noy-db/hub 0.7.1-pre.0 → 0.8.0

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 (812) hide show
  1. package/CHANGELOG.md +147 -14
  2. package/LICENSE +202 -21
  3. package/NOTICE +1 -0
  4. package/README.md +5 -5
  5. package/codemods/0.8.0-pre.json +61 -0
  6. package/dist/api-OS747DS4.js +18 -0
  7. package/dist/as/index.js +11 -12
  8. package/dist/as/index.js.map +1 -1
  9. package/dist/at/index.js +9 -10
  10. package/dist/attestation/index.js +37 -26
  11. package/dist/attestation/index.js.map +1 -1
  12. package/dist/{backup-XKCXVKV3.js → backup-YE2VQ26S.js} +15 -16
  13. package/dist/backup-YE2VQ26S.js.map +1 -0
  14. package/dist/blobs/index.js +13 -14
  15. package/dist/blobs/index.js.map +1 -1
  16. package/dist/broker/index.js +12 -13
  17. package/dist/broker/index.js.map +1 -1
  18. package/dist/by/index.js +2 -2
  19. package/dist/capsule/contract.d.ts +159 -0
  20. package/dist/capsule/contract.js +95 -0
  21. package/dist/capsule/enclave-aes/bound.d.ts +52 -0
  22. package/dist/capsule/enclave-aes/capabilities.d.ts +2 -0
  23. package/dist/capsule/enclave-aes/classify/compare.d.ts +20 -0
  24. package/dist/{kernel/enclave → capsule/enclave-aes}/classify/find.d.ts +1 -1
  25. package/dist/{kernel/enclave → capsule/enclave-aes}/classify/reveal.d.ts +1 -1
  26. package/dist/{kernel/enclave → capsule/enclave-aes}/classify/verify.d.ts +2 -2
  27. package/dist/{kernel/enclave → capsule/enclave-aes}/classify/write.d.ts +1 -1
  28. package/dist/{kernel/enclave → capsule/enclave-aes}/crypto.d.ts +33 -129
  29. package/dist/capsule/enclave-aes/index.d.ts +113 -0
  30. package/dist/capsule/enclave-aes/index.js +335 -0
  31. package/dist/capsule/enclave-aes/index.js.map +1 -0
  32. package/dist/capsule/enclave-aes/primitives.d.ts +29 -0
  33. package/dist/capsule/index.d.ts +27 -0
  34. package/dist/capsule/plumbing/ct-equal.d.ts +11 -0
  35. package/dist/{kernel/enclave/record-keys → capsule/plumbing}/deterministic.d.ts +8 -21
  36. package/dist/capsule/plumbing/digest.d.ts +122 -0
  37. package/dist/{kernel/enclave/record-keys → capsule/plumbing}/envelope-body.d.ts +44 -48
  38. package/dist/capsule/plumbing/index.d.ts +100 -0
  39. package/dist/capsule/plumbing/lifecycle.d.ts +50 -0
  40. package/dist/{kernel/enclave/record-keys → capsule/plumbing}/record-codec.d.ts +80 -20
  41. package/dist/{kernel/enclave → capsule/plumbing}/record-envelope.d.ts +1 -1
  42. package/dist/capsule/plumbing/rekey-blob.d.ts +33 -0
  43. package/dist/capsule/plumbing/rekey.d.ts +44 -0
  44. package/dist/capsule/plumbing/sealed-slot.d.ts +22 -0
  45. package/dist/capsule/plumbing/sealed-slots.d.ts +37 -0
  46. package/dist/capsule/plumbing/sealing.d.ts +29 -0
  47. package/dist/capsule/plumbing/sign.d.ts +9 -0
  48. package/dist/{kernel/enclave/record-keys → capsule/plumbing}/tombstone.d.ts +2 -2
  49. package/dist/capsule-VOLX3QRQ.js +209 -0
  50. package/dist/cargo/index.js +39 -36
  51. package/dist/{chunk-Q2MPFCQ7.js → chunk-23D6M6VM.js} +5 -5
  52. package/dist/chunk-23D6M6VM.js.map +1 -0
  53. package/dist/{chunk-R7BX2NB3.js → chunk-244QBK7Z.js} +4 -4
  54. package/dist/chunk-244QBK7Z.js.map +1 -0
  55. package/dist/chunk-2AFX33YA.js +936 -0
  56. package/dist/chunk-2AFX33YA.js.map +1 -0
  57. package/dist/{chunk-MVMXZYXT.js → chunk-2GHDLTPE.js} +2 -2
  58. package/dist/{chunk-GYGLAVCX.js → chunk-2MG3SW7Q.js} +6 -2
  59. package/dist/chunk-2MG3SW7Q.js.map +1 -0
  60. package/dist/{chunk-XLMEZIAJ.js → chunk-2ODVB6LZ.js} +2 -2
  61. package/dist/{chunk-HJUWN7JN.js → chunk-2PVXF5CS.js} +33 -8
  62. package/dist/chunk-2PVXF5CS.js.map +1 -0
  63. package/dist/{chunk-YJRT5UQM.js → chunk-2RH777HH.js} +21 -20
  64. package/dist/{chunk-YJRT5UQM.js.map → chunk-2RH777HH.js.map} +1 -1
  65. package/dist/{chunk-7O3Y4PPD.js → chunk-37EPTBBR.js} +153 -16
  66. package/dist/chunk-37EPTBBR.js.map +1 -0
  67. package/dist/{chunk-2ENE6445.js → chunk-3A5DUZ56.js} +102 -16
  68. package/dist/chunk-3A5DUZ56.js.map +1 -0
  69. package/dist/chunk-3EWS67C7.js +461 -0
  70. package/dist/chunk-3EWS67C7.js.map +1 -0
  71. package/dist/{chunk-SGZAIJCV.js → chunk-3L2NBKDR.js} +4 -8
  72. package/dist/chunk-3L2NBKDR.js.map +1 -0
  73. package/dist/{chunk-YMUFFJCO.js → chunk-3TDUWVWB.js} +5 -2
  74. package/dist/chunk-3TDUWVWB.js.map +1 -0
  75. package/dist/{chunk-4GVM6QZA.js → chunk-3XJQBBZD.js} +26 -28
  76. package/dist/chunk-3XJQBBZD.js.map +1 -0
  77. package/dist/{chunk-4ZS56W5S.js → chunk-42ALPZ2B.js} +672 -664
  78. package/dist/chunk-42ALPZ2B.js.map +1 -0
  79. package/dist/chunk-4CIQDWRB.js +524 -0
  80. package/dist/chunk-4CIQDWRB.js.map +1 -0
  81. package/dist/{chunk-RMS3NEA3.js → chunk-4JPMZTJY.js} +2 -2
  82. package/dist/{chunk-P3MG6QCN.js → chunk-4T3SE27M.js} +5 -9
  83. package/dist/chunk-4T3SE27M.js.map +1 -0
  84. package/dist/chunk-4V3Q35WG.js +204 -0
  85. package/dist/chunk-4V3Q35WG.js.map +1 -0
  86. package/dist/{chunk-NQKIUNW5.js → chunk-52MGURXO.js} +2 -2
  87. package/dist/{chunk-EQEYOJBM.js → chunk-5A4345TG.js} +4 -4
  88. package/dist/chunk-5A4345TG.js.map +1 -0
  89. package/dist/{chunk-334WSVOW.js → chunk-5FE75J4S.js} +6 -6
  90. package/dist/chunk-5FE75J4S.js.map +1 -0
  91. package/dist/chunk-5KC7QYEC.js +565 -0
  92. package/dist/chunk-5KC7QYEC.js.map +1 -0
  93. package/dist/{chunk-RSGLUIO6.js → chunk-5UMK7GRI.js} +13 -12
  94. package/dist/chunk-5UMK7GRI.js.map +1 -0
  95. package/dist/chunk-5WWDGFOS.js +275 -0
  96. package/dist/chunk-5WWDGFOS.js.map +1 -0
  97. package/dist/{chunk-QPQUVOZ2.js → chunk-6QVKPHBF.js} +3 -5
  98. package/dist/chunk-6QVKPHBF.js.map +1 -0
  99. package/dist/{chunk-ACAQ2DSG.js → chunk-747G6CT3.js} +2 -2
  100. package/dist/chunk-747G6CT3.js.map +1 -0
  101. package/dist/{chunk-QTQUH4DS.js → chunk-7R2Q7CPL.js} +16 -16
  102. package/dist/chunk-7R2Q7CPL.js.map +1 -0
  103. package/dist/{chunk-UGTBXF5U.js → chunk-7XMHMVTS.js} +3 -3
  104. package/dist/{chunk-YJX7EOJD.js → chunk-A4XMIZT3.js} +6 -6
  105. package/dist/{chunk-YJX7EOJD.js.map → chunk-A4XMIZT3.js.map} +1 -1
  106. package/dist/{chunk-A66I4JOA.js → chunk-AEQGFRU4.js} +4 -4
  107. package/dist/chunk-AEQGFRU4.js.map +1 -0
  108. package/dist/chunk-AL6ML4LA.js +1558 -0
  109. package/dist/chunk-AL6ML4LA.js.map +1 -0
  110. package/dist/{chunk-BZ3534YM.js → chunk-AUKKUPVB.js} +2 -2
  111. package/dist/{chunk-CUZLNGD5.js → chunk-B2YY6WDD.js} +1 -1
  112. package/dist/chunk-B2YY6WDD.js.map +1 -0
  113. package/dist/chunk-BKT776R5.js +1 -0
  114. package/dist/chunk-BNWA3WG5.js +22 -0
  115. package/dist/chunk-BNWA3WG5.js.map +1 -0
  116. package/dist/{chunk-VXANTWBJ.js → chunk-BPWYQ25R.js} +92 -6
  117. package/dist/{chunk-VXANTWBJ.js.map → chunk-BPWYQ25R.js.map} +1 -1
  118. package/dist/{chunk-6LKOJBV6.js → chunk-BVCUTXE3.js} +2 -2
  119. package/dist/chunk-CY2CYVWZ.js +41 -0
  120. package/dist/chunk-CY2CYVWZ.js.map +1 -0
  121. package/dist/{chunk-M4YPHYEH.js → chunk-DHHGL42E.js} +70 -25
  122. package/dist/chunk-DHHGL42E.js.map +1 -0
  123. package/dist/{chunk-VTFVLCUH.js → chunk-DIZLQVAA.js} +2 -2
  124. package/dist/{chunk-G73UVHCA.js → chunk-DLD5LGDS.js} +11 -12
  125. package/dist/chunk-DLD5LGDS.js.map +1 -0
  126. package/dist/{chunk-WQBG4SZS.js → chunk-DQESF6RP.js} +60 -49
  127. package/dist/chunk-DQESF6RP.js.map +1 -0
  128. package/dist/{chunk-LWBCUYZW.js → chunk-E2N2T5ZO.js} +2 -2
  129. package/dist/{chunk-3PGS7TYR.js → chunk-EGVJ7TM4.js} +29 -32
  130. package/dist/chunk-EGVJ7TM4.js.map +1 -0
  131. package/dist/{chunk-JVIBCBC7.js → chunk-EJLV62NI.js} +2 -2
  132. package/dist/{chunk-KKEESNPE.js → chunk-EYG2CY2X.js} +126 -19
  133. package/dist/chunk-EYG2CY2X.js.map +1 -0
  134. package/dist/{enclave-55PAM4YO.js → chunk-EZGV6F47.js} +83 -56
  135. package/dist/chunk-EZGV6F47.js.map +1 -0
  136. package/dist/{chunk-CHQCZZWC.js → chunk-EZOAKKTB.js} +36 -27
  137. package/dist/chunk-EZOAKKTB.js.map +1 -0
  138. package/dist/chunk-F6NCB7KT.js +13 -0
  139. package/dist/chunk-F6NCB7KT.js.map +1 -0
  140. package/dist/chunk-FFJFE4T7.js +100 -0
  141. package/dist/chunk-FFJFE4T7.js.map +1 -0
  142. package/dist/{chunk-JMGDHKQF.js → chunk-FLPCETP6.js} +2 -2
  143. package/dist/chunk-FOEDQATO.js +53 -0
  144. package/dist/chunk-FOEDQATO.js.map +1 -0
  145. package/dist/{chunk-DGOZ4UZD.js → chunk-FVH5QYY2.js} +2 -2
  146. package/dist/{chunk-HIWCKBEY.js → chunk-G2MVBDFP.js} +54 -28
  147. package/dist/chunk-G2MVBDFP.js.map +1 -0
  148. package/dist/chunk-GMKGGY7V.js +234 -0
  149. package/dist/chunk-GMKGGY7V.js.map +1 -0
  150. package/dist/{chunk-EIPVYUEP.js → chunk-GQ4MF3CM.js} +3 -3
  151. package/dist/chunk-GQ4MF3CM.js.map +1 -0
  152. package/dist/{chunk-PEOZ34EM.js → chunk-GSAK56VF.js} +1 -1
  153. package/dist/chunk-GSAK56VF.js.map +1 -0
  154. package/dist/{chunk-5VCRVKOZ.js → chunk-GUZET4SG.js} +12 -4
  155. package/dist/chunk-GUZET4SG.js.map +1 -0
  156. package/dist/{chunk-JBDFMYKQ.js → chunk-GZT3NQRA.js} +2 -2
  157. package/dist/{chunk-K23VKTN4.js → chunk-H23XHRTT.js} +19 -34
  158. package/dist/chunk-H23XHRTT.js.map +1 -0
  159. package/dist/{chunk-TFR7DSOV.js → chunk-H427H63K.js} +2 -2
  160. package/dist/{chunk-DFMWUOU5.js → chunk-H7JBY4P7.js} +23 -32
  161. package/dist/chunk-H7JBY4P7.js.map +1 -0
  162. package/dist/{chunk-SKGMN35F.js → chunk-HS554XAF.js} +3 -3
  163. package/dist/chunk-HS554XAF.js.map +1 -0
  164. package/dist/{chunk-HK67PX4J.js → chunk-HW7CMKUG.js} +1 -1
  165. package/dist/chunk-HW7CMKUG.js.map +1 -0
  166. package/dist/{chunk-3BVH633C.js → chunk-IMQKWR46.js} +5 -7
  167. package/dist/chunk-IMQKWR46.js.map +1 -0
  168. package/dist/{chunk-R42NSZI6.js → chunk-IUBMZPHM.js} +7 -7
  169. package/dist/chunk-IUBMZPHM.js.map +1 -0
  170. package/dist/{chunk-CUQPWYRX.js → chunk-IUT3GWP3.js} +75 -386
  171. package/dist/chunk-IUT3GWP3.js.map +1 -0
  172. package/dist/{chunk-RF6D3Y5E.js → chunk-JDEYF3PJ.js} +4 -8
  173. package/dist/{chunk-RF6D3Y5E.js.map → chunk-JDEYF3PJ.js.map} +1 -1
  174. package/dist/{chunk-PPTHY5QO.js → chunk-JIBUPZHA.js} +99 -8
  175. package/dist/chunk-JIBUPZHA.js.map +1 -0
  176. package/dist/{chunk-SMH6SRX7.js → chunk-JOSPH3E5.js} +2 -2
  177. package/dist/chunk-JQYKPKG6.js +193 -0
  178. package/dist/chunk-JQYKPKG6.js.map +1 -0
  179. package/dist/{chunk-7APJ6CO6.js → chunk-JSMTXZR3.js} +45 -13
  180. package/dist/chunk-JSMTXZR3.js.map +1 -0
  181. package/dist/chunk-JWWYRR4Q.js +12 -0
  182. package/dist/chunk-JWWYRR4Q.js.map +1 -0
  183. package/dist/{chunk-BVN6KXL2.js → chunk-KZXDLBY6.js} +14 -9
  184. package/dist/chunk-KZXDLBY6.js.map +1 -0
  185. package/dist/chunk-LGNDFRYC.js +23 -0
  186. package/dist/chunk-LGNDFRYC.js.map +1 -0
  187. package/dist/{chunk-XKWGNJ26.js → chunk-LIOVX5TJ.js} +7 -11
  188. package/dist/chunk-LIOVX5TJ.js.map +1 -0
  189. package/dist/{chunk-AYX7AINO.js → chunk-LNSVGRHO.js} +5 -7
  190. package/dist/chunk-LNSVGRHO.js.map +1 -0
  191. package/dist/chunk-M5B2W6YY.js +293 -0
  192. package/dist/chunk-M5B2W6YY.js.map +1 -0
  193. package/dist/chunk-MBMTND6D.js +456 -0
  194. package/dist/chunk-MBMTND6D.js.map +1 -0
  195. package/dist/{chunk-3BZZJJMQ.js → chunk-MILI22XQ.js} +3 -3
  196. package/dist/{chunk-A2HKRLPV.js → chunk-MXKI4ABF.js} +18 -59
  197. package/dist/chunk-MXKI4ABF.js.map +1 -0
  198. package/dist/{chunk-ZYIYHJ5R.js → chunk-MXOQ5JCG.js} +869 -238
  199. package/dist/chunk-MXOQ5JCG.js.map +1 -0
  200. package/dist/{chunk-V7ZRRCRJ.js → chunk-N7EVQD5S.js} +9 -13
  201. package/dist/chunk-N7EVQD5S.js.map +1 -0
  202. package/dist/chunk-N7UDP5VU.js +757 -0
  203. package/dist/chunk-N7UDP5VU.js.map +1 -0
  204. package/dist/chunk-NBQ3KK6X.js +270 -0
  205. package/dist/chunk-NBQ3KK6X.js.map +1 -0
  206. package/dist/{chunk-ZORTV6NH.js → chunk-NCO25JZO.js} +88 -4
  207. package/dist/chunk-NCO25JZO.js.map +1 -0
  208. package/dist/chunk-NH2UOV3B.js +18 -0
  209. package/dist/chunk-NH2UOV3B.js.map +1 -0
  210. package/dist/chunk-NYMLINRP.js +28 -0
  211. package/dist/chunk-NYMLINRP.js.map +1 -0
  212. package/dist/{chunk-YYP7WSEB.js → chunk-O2IMQSNA.js} +2 -2
  213. package/dist/chunk-O46CHQT7.js +215 -0
  214. package/dist/chunk-O46CHQT7.js.map +1 -0
  215. package/dist/chunk-OIEO4WDL.js +27 -0
  216. package/dist/chunk-OIEO4WDL.js.map +1 -0
  217. package/dist/{chunk-TRGTKX5E.js → chunk-OKSTSITU.js} +27 -4
  218. package/dist/chunk-OKSTSITU.js.map +1 -0
  219. package/dist/chunk-OYW3I5M4.js +31 -0
  220. package/dist/chunk-OYW3I5M4.js.map +1 -0
  221. package/dist/{chunk-Q7HZX3IF.js → chunk-PD64727P.js} +2 -2
  222. package/dist/chunk-PD64727P.js.map +1 -0
  223. package/dist/{chunk-FET5XCAT.js → chunk-PP3K67AJ.js} +34 -6
  224. package/dist/chunk-PP3K67AJ.js.map +1 -0
  225. package/dist/chunk-Q2XRFWOI.js +40 -0
  226. package/dist/chunk-Q2XRFWOI.js.map +1 -0
  227. package/dist/{chunk-QIZPZMUI.js → chunk-Q4UHJ5SN.js} +2 -2
  228. package/dist/{chunk-FAHMLERK.js → chunk-QBLT6POL.js} +2 -2
  229. package/dist/chunk-QMWY2FDS.js +157 -0
  230. package/dist/chunk-QMWY2FDS.js.map +1 -0
  231. package/dist/{chunk-LH5A3ZPG.js → chunk-RB4DOPGZ.js} +150 -22
  232. package/dist/chunk-RB4DOPGZ.js.map +1 -0
  233. package/dist/chunk-RHVMZSUU.js +7 -0
  234. package/dist/chunk-RHVMZSUU.js.map +1 -0
  235. package/dist/{chunk-BUE2D7PX.js → chunk-SMV2HRDU.js} +2 -2
  236. package/dist/chunk-SUZKW6UR.js +25 -0
  237. package/dist/chunk-SUZKW6UR.js.map +1 -0
  238. package/dist/{chunk-7MG7SJT7.js → chunk-SV2PM2OY.js} +1 -1
  239. package/dist/chunk-SV2PM2OY.js.map +1 -0
  240. package/dist/{chunk-UXVWEX5L.js → chunk-SYNY3VB5.js} +6 -2
  241. package/dist/chunk-SYNY3VB5.js.map +1 -0
  242. package/dist/{chunk-4GFX6MPA.js → chunk-TEHHROZL.js} +6 -8
  243. package/dist/chunk-TEHHROZL.js.map +1 -0
  244. package/dist/chunk-TKVUE4JF.js +1289 -0
  245. package/dist/chunk-TKVUE4JF.js.map +1 -0
  246. package/dist/{chunk-SFAWCTEM.js → chunk-TZZBXPZK.js} +2 -2
  247. package/dist/{chunk-F3W6YMDC.js → chunk-U2FZNEWJ.js} +4 -4
  248. package/dist/chunk-U2FZNEWJ.js.map +1 -0
  249. package/dist/{chunk-BLN54W3L.js → chunk-UA372JHH.js} +56 -3
  250. package/dist/chunk-UA372JHH.js.map +1 -0
  251. package/dist/{chunk-6YXCPL4T.js → chunk-UEFOQH74.js} +2 -2
  252. package/dist/chunk-UEFOQH74.js.map +1 -0
  253. package/dist/{chunk-2N5ML6HT.js → chunk-UIYESRD6.js} +9 -11
  254. package/dist/chunk-UIYESRD6.js.map +1 -0
  255. package/dist/{chunk-UHHPLO3I.js → chunk-V3RWOOSO.js} +4 -4
  256. package/dist/{chunk-KTFRCC5C.js → chunk-V7ADQF5O.js} +82 -8
  257. package/dist/chunk-V7ADQF5O.js.map +1 -0
  258. package/dist/{chunk-453F654R.js → chunk-VAORSCSU.js} +3 -3
  259. package/dist/chunk-VAORSCSU.js.map +1 -0
  260. package/dist/{chunk-GPNTB4Y7.js → chunk-VFHCKG5M.js} +6 -10
  261. package/dist/chunk-VFHCKG5M.js.map +1 -0
  262. package/dist/{chunk-2577DKXB.js → chunk-VHBKYN2J.js} +3 -3
  263. package/dist/chunk-VHBKYN2J.js.map +1 -0
  264. package/dist/chunk-VHM7HCKB.js +220 -0
  265. package/dist/chunk-VHM7HCKB.js.map +1 -0
  266. package/dist/{chunk-ZHKOPKE3.js → chunk-VKI2EB3N.js} +3 -3
  267. package/dist/chunk-VKI2EB3N.js.map +1 -0
  268. package/dist/{chunk-UDHWDSRL.js → chunk-VMV5SBE4.js} +16 -17
  269. package/dist/chunk-VMV5SBE4.js.map +1 -0
  270. package/dist/{chunk-SPYWXKJN.js → chunk-VQDZYB2B.js} +3 -3
  271. package/dist/chunk-VQDZYB2B.js.map +1 -0
  272. package/dist/{chunk-Q2PU57HC.js → chunk-VVOLDVIR.js} +2 -2
  273. package/dist/{chunk-3VF3WQ4F.js → chunk-WPOYWFSE.js} +7 -7
  274. package/dist/{chunk-3VF3WQ4F.js.map → chunk-WPOYWFSE.js.map} +1 -1
  275. package/dist/{chunk-OTQZ7ZWU.js → chunk-WXE4W2U6.js} +7 -11
  276. package/dist/chunk-WXE4W2U6.js.map +1 -0
  277. package/dist/chunk-XBKH4FOC.js +195 -0
  278. package/dist/chunk-XBKH4FOC.js.map +1 -0
  279. package/dist/{chunk-TUFM4UZP.js → chunk-XQRFSKYM.js} +2 -2
  280. package/dist/{chunk-3V4KWFX7.js → chunk-XSPFDUDG.js} +58 -53
  281. package/dist/chunk-XSPFDUDG.js.map +1 -0
  282. package/dist/{chunk-GY4WYNNG.js → chunk-Y2RKVFKR.js} +2 -2
  283. package/dist/{chunk-UP4BECPY.js → chunk-Y3LPTBG7.js} +89 -27
  284. package/dist/chunk-Y3LPTBG7.js.map +1 -0
  285. package/dist/{chunk-4OZNXTO4.js → chunk-YRXFFWG4.js} +2 -2
  286. package/dist/classified/index.js +5 -5
  287. package/dist/{classified-marker-BWOFVYAC.js → classified-marker-SIYKUU5L.js} +3 -3
  288. package/dist/classified-marker-SIYKUU5L.js.map +1 -0
  289. package/dist/collection-facade-CS7CA2XO.js +47 -0
  290. package/dist/{computed-VPOKQK4N.js → computed-FAD7SED4.js} +3 -3
  291. package/dist/consent/index.js +10 -11
  292. package/dist/consent/index.js.map +1 -1
  293. package/dist/cover/index.js +10 -11
  294. package/dist/coverage/index.js +306 -0
  295. package/dist/coverage/index.js.map +1 -0
  296. package/dist/custody/index.js +12 -13
  297. package/dist/{dead-filter-PKQDLOT4.js → dead-filter-4F3LWYKH.js} +11 -3
  298. package/dist/dead-filter-4F3LWYKH.js.map +1 -0
  299. package/dist/debug/index.js +11 -2
  300. package/dist/debug/index.js.map +1 -1
  301. package/dist/{delegation-DS5NRILW.js → delegation-NANZDG4N.js} +13 -16
  302. package/dist/delegation-NANZDG4N.js.map +1 -0
  303. package/dist/derivations/index.js +25 -23
  304. package/dist/derive-XLOJVXPU.js +20 -0
  305. package/dist/directory/index.js +10 -11
  306. package/dist/{dispatch-IEPAVQUM.js → dispatch-H4HV32AM.js} +5 -5
  307. package/dist/{dispatch-Q6MWLOJY.js → dispatch-JDZNGEB3.js} +20 -21
  308. package/dist/dispatch-JDZNGEB3.js.map +1 -0
  309. package/dist/executor-FMPVN6DT.js +35 -0
  310. package/dist/executor-LBBBRTN5.js +8 -0
  311. package/dist/executor-OSRZST2U.js +8 -0
  312. package/dist/export-accessible-RAOJAAU5.js +22 -0
  313. package/dist/extract-partition-JD5CUSTG.js +37 -0
  314. package/dist/{fanout-sidecar-S3QL53YX.js → fanout-sidecar-L7NHPIOC.js} +13 -15
  315. package/dist/fanout-sidecar-L7NHPIOC.js.map +1 -0
  316. package/dist/find-JRN4RNQ5.js +10 -0
  317. package/dist/forget/index.js +9 -10
  318. package/dist/forget/index.js.map +1 -1
  319. package/dist/guards/index.js +4 -4
  320. package/dist/history/index.js +13 -14
  321. package/dist/history/index.js.map +1 -1
  322. package/dist/i18n/index.js +14 -15
  323. package/dist/i18n/index.js.map +1 -1
  324. package/dist/index.d.ts +36 -14
  325. package/dist/index.js +970 -163
  326. package/dist/index.js.map +1 -1
  327. package/dist/indexing/index.js +13 -5
  328. package/dist/indexing/index.js.map +1 -1
  329. package/dist/introspection/index.js +11 -12
  330. package/dist/issue-YIVBZUTH.js +19 -0
  331. package/dist/ivf-flat-UZ6DQDLS.js +314 -0
  332. package/dist/ivf-flat-UZ6DQDLS.js.map +1 -0
  333. package/dist/{json-schema-V3M5MUCM.js → json-schema-3SHX72CC.js} +2 -1
  334. package/dist/json-schema-3SHX72CC.js.map +1 -0
  335. package/dist/kernel/attestation-types.d.ts +62 -0
  336. package/dist/kernel/collection-config.d.ts +5 -68
  337. package/dist/kernel/collection.d.ts +30 -9
  338. package/dist/kernel/cutover-transform.d.ts +1 -1
  339. package/dist/kernel/errors.d.ts +257 -38
  340. package/dist/kernel/generation-map.d.ts +48 -0
  341. package/dist/kernel/merge-authority.d.ts +1 -1
  342. package/dist/kernel/noydb.d.ts +6 -1
  343. package/dist/kernel/paths.d.ts +29 -5
  344. package/dist/kernel/prepared-write.d.ts +1 -1
  345. package/dist/kernel/present-cache.d.ts +58 -0
  346. package/dist/kernel/query/all.d.ts +7 -0
  347. package/dist/kernel/query/builder.d.ts +601 -270
  348. package/dist/kernel/query/civil-date.d.ts +64 -0
  349. package/dist/kernel/query/cursor.d.ts +40 -0
  350. package/dist/kernel/query/distinct-key.d.ts +98 -0
  351. package/dist/kernel/query/hydration.d.ts +76 -0
  352. package/dist/kernel/query/index.d.ts +31 -15
  353. package/dist/kernel/query/internal/core.d.ts +30 -0
  354. package/dist/kernel/query/internal/hooks.d.ts +22 -0
  355. package/dist/kernel/query/internal/maintenance.d.ts +43 -0
  356. package/dist/kernel/query/live/incremental.d.ts +314 -0
  357. package/dist/kernel/query/live/index.d.ts +22 -0
  358. package/dist/kernel/query/live/install.d.ts +2 -0
  359. package/dist/kernel/query/{live.d.ts → live/live.d.ts} +39 -9
  360. package/dist/kernel/query/live/methods.d.ts +107 -0
  361. package/dist/kernel/query/predicate.d.ts +119 -1
  362. package/dist/kernel/query/reduce/date-trunc.d.ts +75 -0
  363. package/dist/kernel/query/reduce/index.d.ts +36 -0
  364. package/dist/kernel/query/reduce/install.d.ts +2 -0
  365. package/dist/kernel/query/reduce/methods.d.ts +259 -0
  366. package/dist/kernel/query/reduce/scan-methods.d.ts +121 -0
  367. package/dist/kernel/query/relate/explain.d.ts +199 -0
  368. package/dist/kernel/query/relate/index.d.ts +48 -0
  369. package/dist/kernel/query/relate/install.d.ts +2 -0
  370. package/dist/kernel/query/relate/join-on.d.ts +126 -0
  371. package/dist/kernel/query/relate/join-reduce.d.ts +16 -0
  372. package/dist/kernel/query/relate/join.d.ts +452 -0
  373. package/dist/kernel/query/relate/methods.d.ts +426 -0
  374. package/dist/kernel/query/relate/partition.d.ts +87 -0
  375. package/dist/kernel/query/relate/scan-methods.d.ts +163 -0
  376. package/dist/kernel/query/relate/traverse.d.ts +109 -0
  377. package/dist/kernel/query/scan-builder.d.ts +79 -176
  378. package/dist/kernel/query/sort-key.d.ts +52 -0
  379. package/dist/kernel/secret-mode.d.ts +1 -1
  380. package/dist/kernel/tx-write-gate.d.ts +77 -0
  381. package/dist/kernel/types.d.ts +113 -8
  382. package/dist/kernel/validation.d.ts +27 -10
  383. package/dist/kernel/vault.d.ts +11 -6
  384. package/dist/kernel/via/compose.d.ts +3 -0
  385. package/dist/kernel/via/index.d.ts +56 -0
  386. package/dist/kernel/via/pipeline.d.ts +48 -0
  387. package/dist/kernel/write-queue.d.ts +17 -0
  388. package/dist/{ledger-EDXFFZQL.js → ledger-5ZMGE44K.js} +11 -12
  389. package/dist/liberate-WZSZM2W6.js +24 -0
  390. package/dist/link-set-MFXC24VK.js +29 -0
  391. package/dist/{managed-secret-NCWEMPUQ.js → managed-secret-MFTFT22X.js} +10 -11
  392. package/dist/materialized-views/index.js +30 -24
  393. package/dist/{migrate-cek-5ENH64AA.js → migrate-cek-DZRFFOHW.js} +2 -2
  394. package/dist/money/index.js +5 -5
  395. package/dist/noydb-ITIGNKUF.js +96 -0
  396. package/dist/overlay-views/index.js +4 -4
  397. package/dist/periods/index.js +12 -13
  398. package/dist/pod/index.js +74 -67
  399. package/dist/{pod-handle-ECVCDIXW.js → pod-handle-A3LET7NO.js} +10 -11
  400. package/dist/pod-handle-A3LET7NO.js.map +1 -0
  401. package/dist/policy/index.js +10 -11
  402. package/dist/port/on/index.d.ts +1 -1
  403. package/dist/port/to/index.d.ts +1 -1
  404. package/dist/port/with/blob-strategy.d.ts +10 -1
  405. package/dist/port/with/classified-strategy.d.ts +1 -1
  406. package/dist/port/with/collection-options.d.ts +6 -1
  407. package/dist/port/with/collection-registries.d.ts +1 -1
  408. package/dist/port/with/coverage-strategy.d.ts +83 -0
  409. package/dist/port/with/lookup-strategy.d.ts +2 -2
  410. package/dist/port/with/service-bus.d.ts +46 -0
  411. package/dist/port/with/strategies.d.ts +2 -0
  412. package/dist/portability/index.js +8 -8
  413. package/dist/{post-register-3IPCSG6Q.js → post-register-7ND27CF2.js} +6 -6
  414. package/dist/post-register-7ND27CF2.js.map +1 -0
  415. package/dist/query/all/index.js +31 -0
  416. package/dist/query/index.js +20 -32
  417. package/dist/query/live/index.js +21 -0
  418. package/dist/query/live/index.js.map +1 -0
  419. package/dist/query/reduce/index.js +33 -0
  420. package/dist/query/reduce/index.js.map +1 -0
  421. package/dist/query/relate/index.js +56 -0
  422. package/dist/query/relate/index.js.map +1 -0
  423. package/dist/reduce/index.js +72 -18
  424. package/dist/reduce/index.js.map +1 -1
  425. package/dist/register-DT5HZ2LZ.js +30 -0
  426. package/dist/registry-BHRZGZHF.js +8 -0
  427. package/dist/registry-ET6ZFFWA.js +34 -0
  428. package/dist/registry-HXFGLGTG.js +8 -0
  429. package/dist/registry-THCPDEOD.js +23 -0
  430. package/dist/request-withdrawal-AV2MJJKY.js +30 -0
  431. package/dist/{reveal-QZDHSGD4.js → reveal-B2TLP4JQ.js} +10 -9
  432. package/dist/reveal-B2TLP4JQ.js.map +1 -0
  433. package/dist/revoke-6DWIEOCV.js +24 -0
  434. package/dist/satellites/index.js +1 -1
  435. package/dist/schema-update/index.js +2 -2
  436. package/dist/sealed-record/index.js +16 -18
  437. package/dist/sealed-record/index.js.map +1 -1
  438. package/dist/search/index.js +11 -5
  439. package/dist/{seed-X5CC22CQ.js → seed-XNSE4U3S.js} +15 -18
  440. package/dist/seed-XNSE4U3S.js.map +1 -0
  441. package/dist/sequence/index.js +9 -10
  442. package/dist/session/index.js +9 -10
  443. package/dist/session/index.js.map +1 -1
  444. package/dist/shadow/index.js +2 -2
  445. package/dist/signer-MHGW3OIL.js +23 -0
  446. package/dist/snapshots/index.js +11 -11
  447. package/dist/stale-6GCFMOOR.js +28 -0
  448. package/dist/storage-BYN5LO6Z.js +21 -0
  449. package/dist/storage-BYN5LO6Z.js.map +1 -0
  450. package/dist/store/index.js +1 -1
  451. package/dist/{store-coordination-provider-4WZAIRHC.js → store-coordination-provider-C4BPT5XF.js} +11 -12
  452. package/dist/store-coordination-provider-C4BPT5XF.js.map +1 -0
  453. package/dist/sync/index.js +10 -11
  454. package/dist/sync/index.js.map +1 -1
  455. package/dist/team/index.js +17 -16
  456. package/dist/tiers/index.js +13 -12
  457. package/dist/to/index.js +2 -2
  458. package/dist/transactions/index.js +3 -3
  459. package/dist/util/index.js +1 -1
  460. package/dist/vault-head/index.js +11 -14
  461. package/dist/vault-head/index.js.map +1 -1
  462. package/dist/{verify-6MDTIUEV.js → verify-UKALW7C2.js} +14 -12
  463. package/dist/verify-UKALW7C2.js.map +1 -0
  464. package/dist/via/blob/index.d.ts +1 -0
  465. package/dist/via/classified/binding.d.ts +2 -2
  466. package/dist/via/classified/config-drift.d.ts +2 -2
  467. package/dist/via/classified/errors.d.ts +1 -1
  468. package/dist/via/geo/binding.d.ts +31 -0
  469. package/dist/via/geo/descriptor.d.ts +54 -0
  470. package/dist/via/geo/geohash.d.ts +95 -0
  471. package/dist/via/geo/index.d.ts +13 -0
  472. package/dist/via/geo/where.d.ts +50 -0
  473. package/dist/via/i18n/core.d.ts +1 -1
  474. package/dist/via/lookup/handle.d.ts +4 -4
  475. package/dist/via/lookup/registry.d.ts +1 -1
  476. package/dist/via/lookup/snapshot.d.ts +3 -3
  477. package/dist/walk-4VPK7AS4.js +20 -0
  478. package/dist/walk-4VPK7AS4.js.map +1 -0
  479. package/dist/with-audit/attestation/index.d.ts +2 -1
  480. package/dist/with-audit/attestation/issue.d.ts +2 -2
  481. package/dist/with-audit/attestation/revoke.d.ts +2 -2
  482. package/dist/with-audit/attestation/scheme.d.ts +7 -0
  483. package/dist/with-audit/attestation/signer.d.ts +1 -1
  484. package/dist/with-audit/attestation/strategy.d.ts +2 -2
  485. package/dist/with-audit/attestation/types.d.ts +10 -0
  486. package/dist/with-audit/attestation/vault-facade.d.ts +2 -2
  487. package/dist/with-audit/attestation/verifier.d.ts +6 -0
  488. package/dist/with-audit/consent/consent.d.ts +1 -1
  489. package/dist/with-audit/consent/strategy.d.ts +1 -1
  490. package/dist/with-audit/coverage/accounting.d.ts +112 -0
  491. package/dist/with-audit/coverage/active.d.ts +29 -0
  492. package/dist/with-audit/coverage/index.d.ts +61 -0
  493. package/dist/with-audit/coverage/sketch.d.ts +75 -0
  494. package/dist/with-audit/forget/subject-index.d.ts +1 -1
  495. package/dist/with-audit/periods/periods.d.ts +55 -7
  496. package/dist/with-audit/periods/reserved-collections.d.ts +3 -0
  497. package/dist/with-audit/periods/vault-facade.d.ts +18 -1
  498. package/dist/with-audit/sealed-record/index.d.ts +2 -2
  499. package/dist/with-audit/sealed-record/strategy.d.ts +1 -1
  500. package/dist/with-audit/tiers/index.d.ts +53 -2
  501. package/dist/with-audit/tiers/strategy.d.ts +8 -0
  502. package/dist/with-cargo/adopt-partition.d.ts +1 -1
  503. package/dist/with-cargo/extract-partition.d.ts +1 -1
  504. package/dist/with-cargo/floor.d.ts +3 -3
  505. package/dist/with-commit/history/history.d.ts +2 -2
  506. package/dist/with-commit/history/index.d.ts +1 -1
  507. package/dist/with-commit/history/ledger/store.d.ts +1 -1
  508. package/dist/with-commit/history/strategy.d.ts +1 -1
  509. package/dist/with-commit/history/time-machine.d.ts +1 -1
  510. package/dist/with-commit/numbering/index.d.ts +1 -1
  511. package/dist/with-commit/sequence/index.d.ts +1 -1
  512. package/dist/with-commit/tx/invariants.d.ts +1 -1
  513. package/dist/with-commit/tx/transaction.d.ts +13 -3
  514. package/dist/with-commit/vault-head/head.d.ts +1 -1
  515. package/dist/with-commit/vault-head/index.d.ts +1 -1
  516. package/dist/with-commit/vault-head/strategy.d.ts +1 -1
  517. package/dist/with-commit/vault-head/verify.d.ts +1 -1
  518. package/dist/with-formula/derivations/dispatch.d.ts +1 -1
  519. package/dist/with-formula/derivations/fanout-sidecar.d.ts +1 -1
  520. package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -3
  521. package/dist/with-formula/materialized-views/emit-memo.d.ts +40 -0
  522. package/dist/with-formula/materialized-views/executor.d.ts +8 -0
  523. package/dist/with-formula/materialized-views/index.d.ts +1 -1
  524. package/dist/with-formula/materialized-views/registry.d.ts +15 -0
  525. package/dist/with-formula/materialized-views/types.d.ts +106 -14
  526. package/dist/with-formula/materialized-views/window-stage.d.ts +11 -0
  527. package/dist/with-formula/overlay-views/types.d.ts +1 -1
  528. package/dist/with-formula/overlay-views/virtual-collection.d.ts +13 -4
  529. package/dist/with-lookup/embeddings/chunks.d.ts +79 -0
  530. package/dist/with-lookup/embeddings/descriptor.d.ts +21 -0
  531. package/dist/with-lookup/embeddings/index.d.ts +3 -0
  532. package/dist/with-lookup/embeddings/ivf-flat.d.ts +93 -0
  533. package/dist/with-lookup/embeddings/vector-index.d.ts +73 -0
  534. package/dist/with-lookup/embeddings/vector-set.d.ts +94 -1
  535. package/dist/with-lookup/embeddings/with-vector-index.d.ts +67 -0
  536. package/dist/with-lookup/indexing/collection-facade.d.ts +86 -3
  537. package/dist/with-lookup/indexing/compound-indexes.d.ts +129 -0
  538. package/dist/with-lookup/indexing/eager-indexes.d.ts +170 -1
  539. package/dist/with-lookup/indexing/index-snapshot.d.ts +62 -0
  540. package/dist/with-lookup/indexing/lazy-builder.d.ts +33 -0
  541. package/dist/with-lookup/indexing/persisted-field-indexes.d.ts +104 -0
  542. package/dist/with-lookup/indexing/persisted-indexes.d.ts +12 -0
  543. package/dist/with-lookup/indexing/sorted-indexes.d.ts +117 -0
  544. package/dist/with-lookup/indexing/unique-constraints.d.ts +106 -23
  545. package/dist/with-lookup/reduce/active.d.ts +2 -24
  546. package/dist/with-lookup/reduce/group-core.d.ts +43 -0
  547. package/dist/with-lookup/reduce/groupby.d.ts +195 -36
  548. package/dist/with-lookup/reduce/incremental-group.d.ts +153 -0
  549. package/dist/with-lookup/reduce/index.d.ts +26 -5
  550. package/dist/with-lookup/reduce/reducers.d.ts +255 -1
  551. package/dist/with-lookup/reduce/reduction.d.ts +44 -13
  552. package/dist/with-lookup/reduce/strategy.d.ts +43 -3
  553. package/dist/with-lookup/reduce/window.d.ts +262 -0
  554. package/dist/with-lookup/search/collection-facade.d.ts +45 -9
  555. package/dist/with-lookup/search/index-store.d.ts +5 -3
  556. package/dist/with-lookup/search/index.d.ts +4 -2
  557. package/dist/with-lookup/search/inverted-index.d.ts +38 -2
  558. package/dist/with-lookup/search/persisted-index-store.d.ts +5 -2
  559. package/dist/with-lookup/search/phrase.d.ts +28 -0
  560. package/dist/with-lookup/search/retrieve-types.d.ts +57 -1
  561. package/dist/with-lookup/search/serialize.d.ts +29 -3
  562. package/dist/with-lookup/search/strategy.d.ts +2 -6
  563. package/dist/with-party/broker/index.d.ts +3 -3
  564. package/dist/with-party/directory/cover/index.d.ts +1 -1
  565. package/dist/with-party/directory/cover/storage.d.ts +1 -1
  566. package/dist/with-party/directory/cover/types.d.ts +1 -1
  567. package/dist/with-party/directory/index.d.ts +1 -1
  568. package/dist/with-party/directory/types.d.ts +1 -1
  569. package/dist/with-party/directory/user-envelope/api.d.ts +1 -1
  570. package/dist/with-party/directory/user-envelope/storage.d.ts +1 -1
  571. package/dist/with-party/policy/engine.d.ts +1 -1
  572. package/dist/with-party/policy/index.d.ts +1 -1
  573. package/dist/with-party/policy/presets.d.ts +1 -1
  574. package/dist/with-party/team/authenticators.d.ts +91 -1
  575. package/dist/with-party/team/delegation.d.ts +1 -1
  576. package/dist/with-party/team/echo-secret.d.ts +2 -2
  577. package/dist/with-party/team/index.d.ts +4 -3
  578. package/dist/with-party/team/keyring.d.ts +2 -2
  579. package/dist/with-party/team/magic-link-grant.d.ts +1 -1
  580. package/dist/with-party/team/managed-secret.d.ts +1 -1
  581. package/dist/with-party/team/noydb-facade.d.ts +12 -3
  582. package/dist/with-party/team/recovery.d.ts +1 -1
  583. package/dist/with-party/team/roster-tag.d.ts +1 -1
  584. package/dist/with-party/team/rotate-recover.d.ts +1 -1
  585. package/dist/with-party/team/wrapped-deks.d.ts +1 -1
  586. package/dist/with-pod/format.d.ts +1 -1
  587. package/dist/with-pod/index.d.ts +1 -1
  588. package/dist/with-pod/pod.d.ts +1 -1
  589. package/dist/with-shape/blobs/blob-compaction.d.ts +17 -1
  590. package/dist/with-shape/blobs/blob-intent.d.ts +1 -1
  591. package/dist/with-shape/blobs/blob-set.d.ts +23 -4
  592. package/dist/with-shape/blobs/legacy-sweep.d.ts +35 -0
  593. package/dist/with-shape/introspection/describe.d.ts +7 -1
  594. package/dist/with-shape/introspection/field-meta.d.ts +18 -0
  595. package/dist/with-shape/introspection/types.d.ts +57 -1
  596. package/dist/with-shape/introspection/walk.d.ts +1 -1
  597. package/dist/with-shape/links/lazy-handle.d.ts +1 -1
  598. package/dist/with-shape/links/link-set.d.ts +1 -1
  599. package/dist/with-shape/links/vault-facade.d.ts +1 -1
  600. package/dist/with-shape/manifest/derive.d.ts +1 -1
  601. package/dist/with-shape/manifest/storage.d.ts +1 -1
  602. package/dist/with-shape/persisted-schemas/register.d.ts +1 -1
  603. package/dist/with-shape/persisted-schemas/storage.d.ts +1 -1
  604. package/dist/with-shape/satellites/dead-filter.d.ts +1 -1
  605. package/dist/with-shape/satellites/declare.d.ts +1 -1
  606. package/dist/with-shape/satellites/marker.d.ts +1 -1
  607. package/dist/with-shape/satellites/post-register.d.ts +1 -1
  608. package/dist/with-store/partitioned/index.d.ts +337 -0
  609. package/dist/with-sync/change-broadcast.d.ts +153 -0
  610. package/dist/with-sync/presence.d.ts +5 -10
  611. package/dist/withdraw-accessible-HL7GACUE.js +27 -0
  612. package/dist/withdraw-accessible-HL7GACUE.js.map +1 -0
  613. package/package.json +48 -12
  614. package/dist/api-WYW4YEQG.js +0 -19
  615. package/dist/backup-XKCXVKV3.js.map +0 -1
  616. package/dist/chunk-2577DKXB.js.map +0 -1
  617. package/dist/chunk-2ENE6445.js.map +0 -1
  618. package/dist/chunk-2N5ML6HT.js.map +0 -1
  619. package/dist/chunk-334WSVOW.js.map +0 -1
  620. package/dist/chunk-3BVH633C.js.map +0 -1
  621. package/dist/chunk-3PGS7TYR.js.map +0 -1
  622. package/dist/chunk-3V4KWFX7.js.map +0 -1
  623. package/dist/chunk-453F654R.js.map +0 -1
  624. package/dist/chunk-4GFX6MPA.js.map +0 -1
  625. package/dist/chunk-4GVM6QZA.js.map +0 -1
  626. package/dist/chunk-4ZS56W5S.js.map +0 -1
  627. package/dist/chunk-5VCRVKOZ.js.map +0 -1
  628. package/dist/chunk-6YXCPL4T.js.map +0 -1
  629. package/dist/chunk-7APJ6CO6.js.map +0 -1
  630. package/dist/chunk-7MG2LHO5.js +0 -1616
  631. package/dist/chunk-7MG2LHO5.js.map +0 -1
  632. package/dist/chunk-7MG7SJT7.js.map +0 -1
  633. package/dist/chunk-7O3Y4PPD.js.map +0 -1
  634. package/dist/chunk-A2HKRLPV.js.map +0 -1
  635. package/dist/chunk-A66I4JOA.js.map +0 -1
  636. package/dist/chunk-ACAQ2DSG.js.map +0 -1
  637. package/dist/chunk-ATL3ZVXT.js +0 -151
  638. package/dist/chunk-ATL3ZVXT.js.map +0 -1
  639. package/dist/chunk-AYX7AINO.js.map +0 -1
  640. package/dist/chunk-BLN54W3L.js.map +0 -1
  641. package/dist/chunk-BRLKZUBT.js +0 -95
  642. package/dist/chunk-BRLKZUBT.js.map +0 -1
  643. package/dist/chunk-BTDR2PXY.js +0 -29
  644. package/dist/chunk-BTDR2PXY.js.map +0 -1
  645. package/dist/chunk-BVN6KXL2.js.map +0 -1
  646. package/dist/chunk-CHQCZZWC.js.map +0 -1
  647. package/dist/chunk-CUQPWYRX.js.map +0 -1
  648. package/dist/chunk-CUZLNGD5.js.map +0 -1
  649. package/dist/chunk-DFMWUOU5.js.map +0 -1
  650. package/dist/chunk-EIPVYUEP.js.map +0 -1
  651. package/dist/chunk-EQEYOJBM.js.map +0 -1
  652. package/dist/chunk-F3W6YMDC.js.map +0 -1
  653. package/dist/chunk-FET5XCAT.js.map +0 -1
  654. package/dist/chunk-G73UVHCA.js.map +0 -1
  655. package/dist/chunk-GPNTB4Y7.js.map +0 -1
  656. package/dist/chunk-GYGLAVCX.js.map +0 -1
  657. package/dist/chunk-HIWCKBEY.js.map +0 -1
  658. package/dist/chunk-HJUWN7JN.js.map +0 -1
  659. package/dist/chunk-HK67PX4J.js.map +0 -1
  660. package/dist/chunk-HPVZCW43.js +0 -479
  661. package/dist/chunk-HPVZCW43.js.map +0 -1
  662. package/dist/chunk-I3ISCYT7.js +0 -159
  663. package/dist/chunk-I3ISCYT7.js.map +0 -1
  664. package/dist/chunk-K23VKTN4.js.map +0 -1
  665. package/dist/chunk-KKEESNPE.js.map +0 -1
  666. package/dist/chunk-KTFRCC5C.js.map +0 -1
  667. package/dist/chunk-LH5A3ZPG.js.map +0 -1
  668. package/dist/chunk-LP7BEXCT.js +0 -32
  669. package/dist/chunk-LP7BEXCT.js.map +0 -1
  670. package/dist/chunk-M4YPHYEH.js.map +0 -1
  671. package/dist/chunk-NSITFOCJ.js +0 -99
  672. package/dist/chunk-NSITFOCJ.js.map +0 -1
  673. package/dist/chunk-OTQZ7ZWU.js.map +0 -1
  674. package/dist/chunk-P3MG6QCN.js.map +0 -1
  675. package/dist/chunk-PEOZ34EM.js.map +0 -1
  676. package/dist/chunk-PPTHY5QO.js.map +0 -1
  677. package/dist/chunk-Q2MPFCQ7.js.map +0 -1
  678. package/dist/chunk-Q7HZX3IF.js.map +0 -1
  679. package/dist/chunk-QPQUVOZ2.js.map +0 -1
  680. package/dist/chunk-QTQUH4DS.js.map +0 -1
  681. package/dist/chunk-R42NSZI6.js.map +0 -1
  682. package/dist/chunk-R7BX2NB3.js.map +0 -1
  683. package/dist/chunk-RSGLUIO6.js.map +0 -1
  684. package/dist/chunk-RTBJ5IOO.js +0 -87
  685. package/dist/chunk-RTBJ5IOO.js.map +0 -1
  686. package/dist/chunk-SGZAIJCV.js.map +0 -1
  687. package/dist/chunk-SKGMN35F.js.map +0 -1
  688. package/dist/chunk-SPYWXKJN.js.map +0 -1
  689. package/dist/chunk-SZJ3DLZA.js +0 -66
  690. package/dist/chunk-SZJ3DLZA.js.map +0 -1
  691. package/dist/chunk-TRGTKX5E.js.map +0 -1
  692. package/dist/chunk-UDHWDSRL.js.map +0 -1
  693. package/dist/chunk-UP4BECPY.js.map +0 -1
  694. package/dist/chunk-UXVWEX5L.js.map +0 -1
  695. package/dist/chunk-V7ZRRCRJ.js.map +0 -1
  696. package/dist/chunk-WQBG4SZS.js.map +0 -1
  697. package/dist/chunk-XKWGNJ26.js.map +0 -1
  698. package/dist/chunk-YMUFFJCO.js.map +0 -1
  699. package/dist/chunk-ZHKOPKE3.js.map +0 -1
  700. package/dist/chunk-ZN4FD4PE.js +0 -364
  701. package/dist/chunk-ZN4FD4PE.js.map +0 -1
  702. package/dist/chunk-ZORTV6NH.js.map +0 -1
  703. package/dist/chunk-ZYIYHJ5R.js.map +0 -1
  704. package/dist/classified-marker-BWOFVYAC.js.map +0 -1
  705. package/dist/collection-facade-JSJYIKS4.js +0 -45
  706. package/dist/dead-filter-PKQDLOT4.js.map +0 -1
  707. package/dist/delegation-DS5NRILW.js.map +0 -1
  708. package/dist/derive-E642AOUB.js +0 -21
  709. package/dist/dispatch-Q6MWLOJY.js.map +0 -1
  710. package/dist/executor-5NIMLSKZ.js +0 -8
  711. package/dist/executor-7UQ3BZ4F.js +0 -29
  712. package/dist/executor-AQVXV5GZ.js +0 -8
  713. package/dist/export-accessible-ORMKLA3E.js +0 -22
  714. package/dist/extract-partition-NKYO3WNG.js +0 -37
  715. package/dist/fanout-sidecar-S3QL53YX.js.map +0 -1
  716. package/dist/find-NOGJ25VX.js +0 -10
  717. package/dist/issue-3DTQ7VJJ.js +0 -18
  718. package/dist/json-schema-V3M5MUCM.js.map +0 -1
  719. package/dist/kernel/enclave/classify/compare.d.ts +0 -10
  720. package/dist/kernel/enclave/index.d.ts +0 -87
  721. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +0 -124
  722. package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +0 -88
  723. package/dist/kernel/enclave/record-keys/rekey.d.ts +0 -91
  724. package/dist/kernel/enclave/record-keys/sealed-slot.d.ts +0 -14
  725. package/dist/kernel/enclave/record-keys/sealed-slots.d.ts +0 -123
  726. package/dist/kernel/enclave/record-keys/sealing.d.ts +0 -70
  727. package/dist/kernel/query/join.d.ts +0 -232
  728. package/dist/liberate-V35QUHJP.js +0 -24
  729. package/dist/link-set-2TTM3IZC.js +0 -30
  730. package/dist/noydb-62MX4QUG.js +0 -90
  731. package/dist/pod-handle-ECVCDIXW.js.map +0 -1
  732. package/dist/post-register-3IPCSG6Q.js.map +0 -1
  733. package/dist/register-HHLXEQDN.js +0 -31
  734. package/dist/registry-23WF3TGH.js +0 -8
  735. package/dist/registry-4AYVUNXK.js +0 -8
  736. package/dist/registry-JS3YFQQM.js +0 -18
  737. package/dist/registry-XX2Z7B46.js +0 -32
  738. package/dist/request-withdrawal-CHPATE4G.js +0 -30
  739. package/dist/reveal-QZDHSGD4.js.map +0 -1
  740. package/dist/revoke-7M2VW4ON.js +0 -23
  741. package/dist/seed-X5CC22CQ.js.map +0 -1
  742. package/dist/signer-SJQJVSM6.js +0 -24
  743. package/dist/stale-BZS6H2RY.js +0 -29
  744. package/dist/storage-RW4QBS23.js +0 -22
  745. package/dist/store-coordination-provider-4WZAIRHC.js.map +0 -1
  746. package/dist/verify-6MDTIUEV.js.map +0 -1
  747. package/dist/walk-ZURCH6RZ.js +0 -21
  748. package/dist/withdraw-accessible-PXR4IXDX.js +0 -27
  749. /package/dist/{api-WYW4YEQG.js.map → api-OS747DS4.js.map} +0 -0
  750. /package/dist/{computed-VPOKQK4N.js.map → capsule/contract.js.map} +0 -0
  751. /package/dist/{kernel/enclave → capsule/enclave-aes}/broker/proof.d.ts +0 -0
  752. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/bidx.d.ts +0 -0
  753. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/digest.d.ts +0 -0
  754. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/vdig.d.ts +0 -0
  755. /package/dist/{kernel/enclave/classify → capsule/plumbing}/kofn.d.ts +0 -0
  756. /package/dist/{kernel/enclave/classify → capsule/plumbing}/normalize.d.ts +0 -0
  757. /package/dist/{kernel/enclave → capsule/plumbing}/record-aad.d.ts +0 -0
  758. /package/dist/{collection-facade-JSJYIKS4.js.map → capsule-VOLX3QRQ.js.map} +0 -0
  759. /package/dist/{chunk-MVMXZYXT.js.map → chunk-2GHDLTPE.js.map} +0 -0
  760. /package/dist/{chunk-XLMEZIAJ.js.map → chunk-2ODVB6LZ.js.map} +0 -0
  761. /package/dist/{chunk-RMS3NEA3.js.map → chunk-4JPMZTJY.js.map} +0 -0
  762. /package/dist/{chunk-NQKIUNW5.js.map → chunk-52MGURXO.js.map} +0 -0
  763. /package/dist/{chunk-UGTBXF5U.js.map → chunk-7XMHMVTS.js.map} +0 -0
  764. /package/dist/{chunk-BZ3534YM.js.map → chunk-AUKKUPVB.js.map} +0 -0
  765. /package/dist/{derive-E642AOUB.js.map → chunk-BKT776R5.js.map} +0 -0
  766. /package/dist/{chunk-6LKOJBV6.js.map → chunk-BVCUTXE3.js.map} +0 -0
  767. /package/dist/{chunk-VTFVLCUH.js.map → chunk-DIZLQVAA.js.map} +0 -0
  768. /package/dist/{chunk-LWBCUYZW.js.map → chunk-E2N2T5ZO.js.map} +0 -0
  769. /package/dist/{chunk-JVIBCBC7.js.map → chunk-EJLV62NI.js.map} +0 -0
  770. /package/dist/{chunk-JMGDHKQF.js.map → chunk-FLPCETP6.js.map} +0 -0
  771. /package/dist/{chunk-DGOZ4UZD.js.map → chunk-FVH5QYY2.js.map} +0 -0
  772. /package/dist/{chunk-JBDFMYKQ.js.map → chunk-GZT3NQRA.js.map} +0 -0
  773. /package/dist/{chunk-TFR7DSOV.js.map → chunk-H427H63K.js.map} +0 -0
  774. /package/dist/{chunk-SMH6SRX7.js.map → chunk-JOSPH3E5.js.map} +0 -0
  775. /package/dist/{chunk-3BZZJJMQ.js.map → chunk-MILI22XQ.js.map} +0 -0
  776. /package/dist/{chunk-YYP7WSEB.js.map → chunk-O2IMQSNA.js.map} +0 -0
  777. /package/dist/{chunk-QIZPZMUI.js.map → chunk-Q4UHJ5SN.js.map} +0 -0
  778. /package/dist/{chunk-FAHMLERK.js.map → chunk-QBLT6POL.js.map} +0 -0
  779. /package/dist/{chunk-BUE2D7PX.js.map → chunk-SMV2HRDU.js.map} +0 -0
  780. /package/dist/{chunk-SFAWCTEM.js.map → chunk-TZZBXPZK.js.map} +0 -0
  781. /package/dist/{chunk-UHHPLO3I.js.map → chunk-V3RWOOSO.js.map} +0 -0
  782. /package/dist/{chunk-Q2PU57HC.js.map → chunk-VVOLDVIR.js.map} +0 -0
  783. /package/dist/{chunk-TUFM4UZP.js.map → chunk-XQRFSKYM.js.map} +0 -0
  784. /package/dist/{chunk-GY4WYNNG.js.map → chunk-Y2RKVFKR.js.map} +0 -0
  785. /package/dist/{chunk-4OZNXTO4.js.map → chunk-YRXFFWG4.js.map} +0 -0
  786. /package/dist/{enclave-55PAM4YO.js.map → collection-facade-CS7CA2XO.js.map} +0 -0
  787. /package/dist/{executor-5NIMLSKZ.js.map → computed-FAD7SED4.js.map} +0 -0
  788. /package/dist/{executor-7UQ3BZ4F.js.map → derive-XLOJVXPU.js.map} +0 -0
  789. /package/dist/{dispatch-IEPAVQUM.js.map → dispatch-H4HV32AM.js.map} +0 -0
  790. /package/dist/{executor-AQVXV5GZ.js.map → executor-FMPVN6DT.js.map} +0 -0
  791. /package/dist/{export-accessible-ORMKLA3E.js.map → executor-LBBBRTN5.js.map} +0 -0
  792. /package/dist/{extract-partition-NKYO3WNG.js.map → executor-OSRZST2U.js.map} +0 -0
  793. /package/dist/{find-NOGJ25VX.js.map → export-accessible-RAOJAAU5.js.map} +0 -0
  794. /package/dist/{issue-3DTQ7VJJ.js.map → extract-partition-JD5CUSTG.js.map} +0 -0
  795. /package/dist/{ledger-EDXFFZQL.js.map → find-JRN4RNQ5.js.map} +0 -0
  796. /package/dist/{liberate-V35QUHJP.js.map → issue-YIVBZUTH.js.map} +0 -0
  797. /package/dist/{link-set-2TTM3IZC.js.map → ledger-5ZMGE44K.js.map} +0 -0
  798. /package/dist/{managed-secret-NCWEMPUQ.js.map → liberate-WZSZM2W6.js.map} +0 -0
  799. /package/dist/{noydb-62MX4QUG.js.map → link-set-MFXC24VK.js.map} +0 -0
  800. /package/dist/{register-HHLXEQDN.js.map → managed-secret-MFTFT22X.js.map} +0 -0
  801. /package/dist/{migrate-cek-5ENH64AA.js.map → migrate-cek-DZRFFOHW.js.map} +0 -0
  802. /package/dist/{registry-23WF3TGH.js.map → noydb-ITIGNKUF.js.map} +0 -0
  803. /package/dist/{registry-4AYVUNXK.js.map → query/all/index.js.map} +0 -0
  804. /package/dist/{registry-JS3YFQQM.js.map → register-DT5HZ2LZ.js.map} +0 -0
  805. /package/dist/{registry-XX2Z7B46.js.map → registry-BHRZGZHF.js.map} +0 -0
  806. /package/dist/{request-withdrawal-CHPATE4G.js.map → registry-ET6ZFFWA.js.map} +0 -0
  807. /package/dist/{revoke-7M2VW4ON.js.map → registry-HXFGLGTG.js.map} +0 -0
  808. /package/dist/{signer-SJQJVSM6.js.map → registry-THCPDEOD.js.map} +0 -0
  809. /package/dist/{stale-BZS6H2RY.js.map → request-withdrawal-AV2MJJKY.js.map} +0 -0
  810. /package/dist/{storage-RW4QBS23.js.map → revoke-6DWIEOCV.js.map} +0 -0
  811. /package/dist/{walk-ZURCH6RZ.js.map → signer-MHGW3OIL.js.map} +0 -0
  812. /package/dist/{withdraw-accessible-PXR4IXDX.js.map → stale-6GCFMOOR.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-party/directory/user-envelope/api.ts"],"sourcesContent":["/**\n * Public `vault.user.*` API surface.\n *\n * Three families:\n * - Write-self: `me` / `updateMe` / `setMe` — always target the writer's\n * own keyringId. **Own-only write rule** is structural — no method\n * exists to write someone else's envelope.\n * - Read-anyone: `get` / `list` — read other principals' envelopes\n * (subject to `view-team-profiles` policy gate).\n * - Reactive: `subscribe` / `live` — in-process event emission on local\n * writes. Cross-instance updates land via the team/sync engine and\n * surface to subscribers when the sync diff replays through this API.\n *\n * @see design-history/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport { PolicyDeniedError } from '../../../kernel/errors.js'\nimport {\n loadUserEnvelope,\n saveUserEnvelope,\n listUserEnvelopeIds,\n} from './storage.js'\nimport type {\n UserEnvelope,\n DeepPartialOrNull,\n Unsubscribe,\n UserEnvelopePresented,\n LiveUserEnvelope,\n VaultUserApi,\n UserApiDeps,\n UserApiFactory,\n} from '../../../kernel/types.js'\nimport {\n persistUserVisibility,\n readUserVisibility,\n} from '../visibility.js'\nimport type { UserVisibility } from '../types.js'\nimport type { ExportAccessibleOptions } from '../../../with-audit/portability/export-accessible.js'\nimport type { WithdrawAccessibleOptions, WithdrawResult } from '../../../with-audit/portability/withdraw-accessible.js'\nimport type {\n RequestWithdrawalOptions,\n RequestWithdrawalResult,\n WithdrawalRequest,\n WithdrawalRequestStatus,\n ApproveWithdrawalOptions,\n RejectWithdrawalOptions,\n} from '../../../with-audit/portability/request-withdrawal.js'\n\ninterface ChangeListener<T = unknown> {\n (env: UserEnvelope<T> | null): void\n}\n\n/**\n * Implementation behind `vault.user`. Constructed once per Vault via\n * {@link createUserApi}, holds the writer's keyringId in closure so\n * `updateMe`/`setMe` cannot target any other principal — the own-only\n * rule is enforced at the type level (no `set(otherKeyringId, …)`\n * method) AND at runtime (the keyringId argument simply doesn't exist\n * on the write path).\n */\nexport class UserApi implements VaultUserApi {\n /** keyringId → set of listeners. Wildcard '*' fires on every change. */\n private readonly listeners = new Map<string, Set<ChangeListener>>()\n\n private readonly adapter: UserApiDeps['adapter']\n private readonly vaultName: UserApiDeps['vaultName']\n /** The writer's own keyringId. Frozen at construction time. */\n private readonly writerKeyringId: UserApiDeps['writerKeyringId']\n private readonly getDek: UserApiDeps['getDek']\n /**\n * Policy-gate validator. When omitted, gates are skipped — useful\n * for low-level tests that exercise the storage layer directly.\n * Production paths always wire the Noydb-backed implementation.\n */\n private readonly checkGate?: UserApiDeps['checkGate']\n /**\n * Noydb-backed `exportMyAccessibleData`, injected by the Vault\n * (which holds the keyring + bundle machinery). Omitted in low-level tests.\n */\n private readonly exportAccessible?: UserApiDeps['exportAccessible']\n /**\n * Noydb-backed `unilateralWithdrawal`, injected by the Vault.\n * Destructive — extract + dispose (delete | freeze). Omitted in low-level tests.\n */\n private readonly unilateralWithdraw?: UserApiDeps['unilateralWithdraw']\n /**\n * Noydb-backed two-party withdrawal ceremony, injected by the\n * Vault. requestWithdraw = requester side; the rest = owner side.\n */\n private readonly requestWithdraw?: UserApiDeps['requestWithdraw']\n private readonly listWithdrawals?: UserApiDeps['listWithdrawals']\n private readonly approveWithdraw?: UserApiDeps['approveWithdraw']\n private readonly rejectWithdraw?: UserApiDeps['rejectWithdraw']\n\n constructor(deps: UserApiDeps) {\n this.adapter = deps.adapter\n this.vaultName = deps.vaultName\n this.writerKeyringId = deps.writerKeyringId\n this.getDek = deps.getDek\n this.checkGate = deps.checkGate\n this.exportAccessible = deps.exportAccessible\n this.unilateralWithdraw = deps.unilateralWithdraw\n this.requestWithdraw = deps.requestWithdraw\n this.listWithdrawals = deps.listWithdrawals\n this.approveWithdraw = deps.approveWithdraw\n this.rejectWithdraw = deps.rejectWithdraw\n }\n\n /**\n * File a two-party withdrawal request for the caller's accessible\n * scope. Non-destructive (writes a pending request); an owner later approves\n * or rejects. This is the path for read-only roles (`client`/`viewer`) that\n * cannot self-serve a destructive `unilateralWithdrawal`. Gated by\n * `user-request-withdrawal` (enabled by default).\n */\n async requestWithdrawal(opts: RequestWithdrawalOptions = {}): Promise<RequestWithdrawalResult> {\n if (this.checkGate) await this.checkGate('user-request-withdrawal')\n if (!this.requestWithdraw) {\n throw new Error('requestWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.requestWithdraw(opts)\n }\n\n /** Owner side: list filed withdrawal requests (optionally by status). */\n async listWithdrawalRequests(opts: { status?: WithdrawalRequestStatus } = {}): Promise<WithdrawalRequest[]> {\n if (!this.listWithdrawals) {\n throw new Error('listWithdrawalRequests requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.listWithdrawals(opts)\n }\n\n /**\n * Owner side: approve a pending request. Extracts the requester's\n * recorded scope under firm authority, disposes of the source per the\n * request's disposition, and returns the re-keyed bundle to hand back. Gated\n * by `approve-user-withdrawal` (tier-2 default) + owner/admin role.\n */\n async approveWithdrawal(requestId: string, opts: ApproveWithdrawalOptions = {}): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.approveWithdraw) {\n throw new Error('approveWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.approveWithdraw(requestId, opts)\n }\n\n /** Owner side: reject a pending request (no data is touched). */\n async rejectWithdrawal(requestId: string, opts: RejectWithdrawalOptions = {}): Promise<WithdrawalRequest> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.rejectWithdraw) {\n throw new Error('rejectWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.rejectWithdraw(requestId, opts)\n }\n\n /**\n * Single-party withdrawal: export the caller's accessible scope\n * (re-keyed) and dispose of the source (`delete` or `freeze`). Gated by the\n * fail-closed built-in `client-unilateral-withdraw` policy — undefined or\n * disabled → throws (use `requestWithdrawal`). The firm enables it at vault\n * creation.\n */\n async unilateralWithdrawal(opts: WithdrawAccessibleOptions): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('client-unilateral-withdraw')\n if (!this.unilateralWithdraw) {\n throw new Error('unilateralWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.unilateralWithdraw(opts)\n }\n\n /**\n * Export the calling user's accessible scope as a portable, re-keyed\n * `.noydb` bundle. Non-destructive and **always allowed** (data sovereignty\n * by construction) but audited. Scope = the caller's DEK access set.\n */\n async exportMyAccessibleData(opts: ExportAccessibleOptions = {}): Promise<Uint8Array> {\n if (!this.exportAccessible) {\n throw new Error('exportMyAccessibleData requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.exportAccessible(opts)\n }\n\n // ─── Write-self ──────────────────────────────────────────────────────\n\n /** Read the writer's own envelope. Returns null if never written. */\n async me<T = unknown>(): Promise<UserEnvelope<T> | null> {\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, this.writerKeyringId, dek)\n }\n\n /**\n * Deep-merge a partial patch into the writer's own envelope. Creates\n * the envelope on first call. Optimistic-concurrency safe — a stale\n * `_v` (parallel writer on another device) throws `ConflictError`.\n *\n * Patch semantics:\n * - `undefined` (or omitted key) — skip; existing value preserved\n * - `null` — delete the field from the merged result\n * - any other value — overwrite (deep-merge for plain objects,\n * replace for primitives / arrays)\n *\n * To clear a field, pass `null` rather than `undefined`. Callers\n * with shape `T = string | null` where `null` is a meaningful value\n * should use `setMe` for that specific field instead — `null` here\n * always means delete.\n *\n * Gated by the `edit-own-profile` policy gate (default `minTier: 3`).\n * Pass `presented` to satisfy tightened policies that require a\n * factor proof (e.g. STRICT_POLICY's TOTP requirement).\n */\n async updateMe<T extends object = Record<string, unknown>>(\n patch: DeepPartialOrNull<T>,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const current = await loadUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n dek,\n )\n const merged: T = current ? deepMerge(current.data, patch) : (patch as unknown as T)\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n merged,\n dek,\n current?._v ?? 0,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n /**\n * Replace the writer's own envelope with `payload`. Use sparingly —\n * `updateMe` is the canonical mutation. No `expectedVersion` check;\n * callers explicitly take last-write-wins semantics.\n *\n * Gated by `edit-own-profile`. See `updateMe` for `presented` usage.\n */\n async setMe<T = unknown>(\n payload: T,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n payload,\n dek,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n // ─── Visibility ──────────────────────────────────────────────────────\n\n /**\n * Read the current user's visibility flag from\n * `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no\n * document has been persisted (the default-visible case).\n */\n async getMyVisibility(): Promise<UserVisibility> {\n const persisted = await readUserVisibility(this.adapter, this.vaultName, this.writerKeyringId)\n return persisted ?? { hidden: false }\n }\n\n /**\n * Update the current user's visibility in the team directory.\n *\n * - `hidden: true` — opt out of the default `listUsersWithEnvelopes`\n * listing. `owner`/`admin` callers can still see the user by passing\n * `{ includeHidden: true }`.\n * - `hidden: false` — opt back in.\n *\n * Own-only by construction: the keyringId argument doesn't exist on\n * this method, so no caller can hide or unhide another principal.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext at `_users/<keyringId>` and the keyring file at\n * `_keyring/<userId>` are both still observable to anyone with direct\n * store read access. See `https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/user-envelope.md` →\n * \"Directory visibility\".\n */\n async setMyVisibility(visibility: UserVisibility): Promise<void> {\n await persistUserVisibility(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n { hidden: visibility.hidden },\n )\n }\n\n // ─── Read-anyone ─────────────────────────────────────────────────────\n\n /**\n * Read another principal's envelope by their keyringId. Returns null\n * if the principal exists but has no envelope yet, or if the\n * keyringId does not exist at all.\n *\n * Gated by `view-team-profiles` (default `minTier: 2`) — but ONLY for\n * cross-principal reads. Reading your own envelope (`keyringId ===\n * self`) is never gated; that's just `me()` written long-form.\n */\n async get<T = unknown>(\n keyringId: string,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T> | null> {\n if (this.checkGate && keyringId !== this.writerKeyringId) {\n await this.checkGate('view-team-profiles', presented)\n }\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, keyringId, dek)\n }\n\n /**\n * Read every persisted envelope in the vault. Order is store-defined.\n *\n * Gated by `view-team-profiles`. Default policy (`minTier: 2`) lets\n * any authenticated session read all envelopes. Two privacy-strict\n * opt-outs:\n *\n * - `view-team-profiles.enabled: false` → list() returns only the\n * caller's own envelope (silent self-fallback, no thrown error).\n * - `view-team-profiles.minTier: 1` + insufficient tier → throws\n * `PolicyDeniedError` with `reason: 'insufficient-tier'`. The\n * caller is expected to elevate, not silently degrade.\n *\n * The asymmetry is deliberate: `enabled: false` is a deliberate\n * design choice (\"nobody sees teammate profiles in this app\");\n * `insufficient-tier` is \"you need to authenticate further\". Different\n * UX prompts for different intents.\n */\n async list<T = unknown>(presented?: UserEnvelopePresented): Promise<UserEnvelope<T>[]> {\n if (this.checkGate) {\n try {\n await this.checkGate('view-team-profiles', presented)\n } catch (err) {\n if (err instanceof PolicyDeniedError && err.reason === 'disabled') {\n // Privacy-strict opt-out: quietly return only self.\n const me = await this.me<T>()\n return me ? [me] : []\n }\n throw err\n }\n }\n const dek = await this.getDek()\n const ids = await listUserEnvelopeIds(this.adapter, this.vaultName)\n const envelopes = await Promise.all(\n ids.map((id) => loadUserEnvelope<T>(this.adapter, this.vaultName, id, dek)),\n )\n return envelopes.filter((e): e is UserEnvelope<T> => e !== null)\n }\n\n // ─── Reactive ────────────────────────────────────────────────────────\n\n /**\n * Listen for changes to a specific keyringId's envelope. The callback\n * fires synchronously after every successful local `updateMe` /\n * `setMe` for that principal.\n *\n * Cross-instance changes (a teammate edits their profile on their\n * device, the sync engine pulls the diff onto this device) will fire\n * subscribers when the sync layer replays the write through this API.\n * In v1, subscribers do NOT fire on raw store changes — wire your sync\n * layer to call back through `vault.user.setMe` / `updateMe` if you\n * need that.\n *\n * Pass keyringId `'*'` to fire on every change in the vault.\n */\n subscribe<T = unknown>(\n keyringId: string,\n cb: (env: UserEnvelope<T> | null) => void,\n ): Unsubscribe {\n let listeners = this.listeners.get(keyringId)\n if (!listeners) {\n listeners = new Set()\n this.listeners.set(keyringId, listeners)\n }\n const wrapped: ChangeListener = cb as ChangeListener\n listeners.add(wrapped)\n return () => {\n listeners?.delete(wrapped)\n if (listeners && listeners.size === 0) {\n this.listeners.delete(keyringId)\n }\n }\n }\n\n /**\n * Reactive handle that caches the current value and re-reads on every\n * change for the given keyringId. Convenient for framework bindings:\n *\n * const live = vault.user.live<UserShape>(vault.userId)\n * live.subscribe(env => render(env?.data))\n *\n * Initial value is `null` until the first `current()` call materializes\n * it via `vault.user.get()`. Call `stop()` when done to release the\n * subscription.\n */\n live<T = unknown>(keyringId: string): LiveUserEnvelope<T> {\n let value: UserEnvelope<T> | null = null\n let primed = false\n const unsubscribe = this.subscribe<T>(keyringId, (env) => {\n value = env\n })\n\n return {\n current(): UserEnvelope<T> | null {\n if (!primed) {\n primed = true\n // First call: kick off a read but return synchronously. The\n // subscriber will be re-fired by the next write or the caller\n // can await `vault.user.get()` directly for an immediate read.\n }\n return value\n },\n subscribe: (cb) => this.subscribe<T>(keyringId, cb),\n stop: unsubscribe,\n }\n }\n\n // ─── Internal: change emission ───────────────────────────────────────\n\n private fireChange<T>(keyringId: string, env: UserEnvelope<T> | null): void {\n const targeted = this.listeners.get(keyringId)\n if (targeted) for (const l of targeted) l(env)\n const wildcard = this.listeners.get('*')\n if (wildcard) for (const l of wildcard) l(env)\n }\n}\n\n/**\n * Build the `vault.user` API implementation from its dependencies. This is\n * the real {@link UserApiFactory} — `createNoydb()` dynamically imports it\n * and stashes it on the `Noydb` instance so `Vault`'s constructor can call\n * it synchronously.\n */\nexport const createUserApi: UserApiFactory = (deps: UserApiDeps): VaultUserApi => new UserApi(deps)\n\n/**\n * Recursive plain-object deep merge with delete intent.\n *\n * Patch semantics:\n * - `undefined` — skip the key; source value preserved\n * - `null` — delete the key from output (lodash `_.merge` /\n * Firestore `FieldValue.delete()` semantics)\n * - plain object — recurse (deep merge)\n * - any other value — replace (arrays are replaced, not concatenated)\n *\n * Safe against the JS quirk where an own property explicitly set to\n * `undefined` is iterated by `Object.entries`. We dispatch on the value\n * BEFORE writing, so `{ k: undefined }` triggers the skip branch rather\n * than overwriting `out[k]` with undefined.\n */\nfunction deepMerge<T>(source: T, patch: DeepPartialOrNull<T>): T {\n if (!isPlainObject(source) || !isPlainObject(patch)) {\n // Top-level non-object replace. `null` patch at the leaf level\n // would have been caught by the parent recursion's branch table;\n // at the top level it means \"set the whole envelope to null,\"\n // which the type system already prevents (T extends object).\n return patch as unknown as T\n }\n const out: Record<string, unknown> = { ...(source as Record<string, unknown>) }\n for (const [key, patchVal] of Object.entries(patch as Record<string, unknown>)) {\n if (patchVal === undefined) {\n // Skip — preserve the source value at this key. Matches the\n // pre-existing behavior so callers who never used `null` see no diff.\n continue\n }\n if (patchVal === null) {\n // Delete intent. `delete` rather than `out[key] = undefined`\n // because JSON.stringify drops undefined fields silently and\n // we want the deletion to be visible to consumers iterating\n // the merged object (e.g. `Object.keys(merged.profile)`).\n delete out[key]\n continue\n }\n const sourceVal = (source as Record<string, unknown>)[key]\n if (isPlainObject(patchVal)) {\n // Recurse for any plain-object patch — including the \"source is\n // missing this key\" case. Without recursing through a synthetic\n // empty source, nested `null` deletions in the patch would land\n // as literal `null` values instead of triggering the delete\n // branch (e.g. `{ app: { signature: null } }` against a missing\n // `app` would emit `{ app: { signature: null } }` instead of\n // `{ app: {} }`).\n const recurseSource = isPlainObject(sourceVal) ? sourceVal : {}\n out[key] = deepMerge(recurseSource, patchVal as DeepPartialOrNull<typeof recurseSource>)\n } else {\n out[key] = patchVal\n }\n }\n return out as T\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (x === null || typeof x !== 'object') return false\n if (Array.isArray(x)) return false\n const proto = Object.getPrototypeOf(x) as object | null\n return proto === Object.prototype || proto === null\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-shape/persisted-schemas/derive.ts"],"sourcesContent":["/**\n * Derive a {@link PersistedSchemaEnvelope} from a Standard Schema v1\n * validator. Supports zod 3 (via the optional `zod-to-json-schema` peer-dep)\n * and zod 4 (via its native `z.toJSONSchema()`); both are loaded lazily so\n * hub never statically imports zod. Other validator families write a stub\n * envelope flagging the kind.\n *\n * @see design-history/2026-05-22-schema-dump-design.md\n *\n * @module\n */\n\nimport { canonicalize } from './canonicalize.js'\nimport { sha256Hex } from '../../kernel/enclave/index.js'\nimport type { PersistedSchemaEnvelope, PersistedSchemaKind } from './types.js'\n\n/**\n * Heuristic Zod detection — uses the Standard Schema v1 `~standard.vendor`\n * property (present in Zod v3.23+ and Zod v4+). Falls back to the\n * `_def.typeName` heuristic for older Zod v3 builds that predate Standard\n * Schema support.\n */\nexport function isZodSchema(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false\n // Standard Schema v1 vendor tag — most reliable across Zod majors\n const std = (value as { '~standard'?: { vendor?: unknown } })['~standard']\n if (std && typeof std === 'object' && (std as { vendor?: unknown }).vendor === 'zod') return true\n // Zod v3 fallback: _def.typeName starts with 'Zod' (e.g. 'ZodObject')\n const def = (value as { _def?: { typeName?: unknown } })._def\n if (!def || typeof def !== 'object') return false\n return typeof (def as { typeName?: unknown }).typeName === 'string'\n && ((def as { typeName: string }).typeName).startsWith('Zod')\n}\n\nfunction detectKind(validator: unknown): PersistedSchemaKind {\n if (isZodSchema(validator)) return 'Zod'\n return 'Unknown'\n}\n\n/**\n * Returns `true` when the validator was created with the Zod v4 native API\n * (i.e. `import { z } from 'zod'` where zod resolves to v4). Zod v4 native\n * schemas carry a `_zod` property whereas Zod v3 schemas (and the v3-compat\n * shim shipped inside zod@4 at `zod/v3`) use `_def.typeName`.\n *\n * Kept duck-typed so hub never statically imports zod.\n */\nexport function isZod4Schema(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false\n return '_zod' in value\n}\n\n/**\n * Lazy-require `zod-to-json-schema`. Returns the converter, or throws a\n * clear error if the peer-dep isn't installed.\n */\nasync function loadZodToJsonSchemaConverter(): Promise<(s: unknown) => object> {\n try {\n const mod = (await import('zod-to-json-schema')) as { zodToJsonSchema?: (s: unknown) => object }\n if (!mod.zodToJsonSchema) {\n throw new Error('zod-to-json-schema export missing')\n }\n return mod.zodToJsonSchema\n } catch (err) {\n throw new Error(\n 'persistJsonSchema requires the optional peer-dep `zod-to-json-schema`. '\n + 'Install it: `pnpm add zod-to-json-schema` (or npm/yarn equivalent). '\n + `Original error: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\n/**\n * Lazy-require Zod v4's built-in `toJSONSchema`. Used when the validator is a\n * Zod v4 native schema and `zod-to-json-schema` is absent or cannot handle it.\n *\n * Dynamic so hub never loads zod unless a caller actually persists a Zod v4\n * schema. ⚠️ Dynamic is NOT what keeps zod out of the tarball — that is\n * `peerDependencies` (#1227). tsup externalises DECLARED dependencies and\n * BUNDLES everything else, static or dynamic alike, so while zod was a\n * devDependency only, this line silently vendored 548 KB of zod@4.4.3 into\n * `dist/` at a build-frozen version. The comment here previously claimed the\n * dynamic import was what prevented that; it never did.\n *\n * Declaring it an OPTIONAL peer is also the correctness fix, not just a size\n * one: `toJSONSchema` reads a schema's internals, and the schema is built by\n * the CONSUMER's zod. A vendored copy meant hub inspected one zod's objects\n * with another zod's version of that reader. Now there is one zod — theirs.\n */\nasync function loadZodV4Converter(): Promise<(s: unknown) => object> {\n try {\n const mod = (await import('zod')) as { toJSONSchema?: (s: unknown) => object }\n if (typeof mod.toJSONSchema !== 'function') {\n throw new Error('zod.toJSONSchema not available (zod v4 required)')\n }\n return mod.toJSONSchema\n } catch (err) {\n throw new Error(\n 'persistJsonSchema with a Zod v4 schema requires zod@4 with toJSONSchema. '\n + `Original error: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\nexport async function derivePersistedSchema(\n validator: unknown,\n): Promise<PersistedSchemaEnvelope> {\n const kind = detectKind(validator)\n const derivedAt = new Date().toISOString()\n\n if (kind === 'Zod') {\n let jsonSchema: object\n\n if (isZod4Schema(validator)) {\n // Zod v4 native schemas: use zod's built-in toJSONSchema (available in\n // zod@4) because zod-to-json-schema@3.x parses _def.typeName which is\n // absent in native v4 schemas.\n const convert = await loadZodV4Converter()\n jsonSchema = convert(validator)\n } else {\n // Zod v3 schemas (or v3-compat shim): use the optional peer-dep\n // zod-to-json-schema for backward compatibility.\n const convert = await loadZodToJsonSchemaConverter()\n jsonSchema = convert(validator)\n }\n\n const canonical = canonicalize(jsonSchema)\n const hash = await sha256Hex(new TextEncoder().encode(canonical))\n return { _noydb_schema: 1, kind, jsonSchema, hash, derivedAt }\n }\n\n return {\n _noydb_schema: 1,\n kind,\n jsonSchema: null,\n hash: null,\n reason: `derivation not yet supported for kind=${kind} (v0 supports Zod only)`,\n derivedAt,\n }\n}\n"],"mappings":";;;;;;;;AAsBO,SAAS,YAAY,OAAyB;AACnD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AAExD,QAAM,MAAO,MAAiD,WAAW;AACzE,MAAI,OAAO,OAAO,QAAQ,YAAa,IAA6B,WAAW,MAAO,QAAO;AAE7F,QAAM,MAAO,MAA4C;AACzD,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,SAAO,OAAQ,IAA+B,aAAa,YACpD,IAA6B,SAAU,WAAW,KAAK;AAChE;AAEA,SAAS,WAAW,WAAyC;AAC3D,MAAI,YAAY,SAAS,EAAG,QAAO;AACnC,SAAO;AACT;AAUO,SAAS,aAAa,OAAyB;AACpD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,SAAO,UAAU;AACnB;AAMA,eAAe,+BAAgE;AAC7E,MAAI;AACF,UAAM,MAAO,MAAM,OAAO,oBAAoB;AAC9C,QAAI,CAAC,IAAI,iBAAiB;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AACA,WAAO,IAAI;AAAA,EACb,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,kKAEqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACvE;AAAA,EACF;AACF;AAmBA,eAAe,qBAAsD;AACnE,MAAI;AACF,UAAM,MAAO,MAAM,OAAO,KAAK;AAC/B,QAAI,OAAO,IAAI,iBAAiB,YAAY;AAC1C,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AACA,WAAO,IAAI;AAAA,EACb,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,4FACqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACvE;AAAA,EACF;AACF;AAEA,eAAsB,sBACpB,WACkC;AAClC,QAAM,OAAO,WAAW,SAAS;AACjC,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,MAAI,SAAS,OAAO;AAClB,QAAI;AAEJ,QAAI,aAAa,SAAS,GAAG;AAI3B,YAAM,UAAU,MAAM,mBAAmB;AACzC,mBAAa,QAAQ,SAAS;AAAA,IAChC,OAAO;AAGL,YAAM,UAAU,MAAM,6BAA6B;AACnD,mBAAa,QAAQ,SAAS;AAAA,IAChC;AAEA,UAAM,YAAY,aAAa,UAAU;AACzC,UAAM,OAAO,MAAM,UAAU,IAAI,YAAY,EAAE,OAAO,SAAS,CAAC;AAChE,WAAO,EAAE,eAAe,GAAG,MAAM,YAAY,MAAM,UAAU;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,eAAe;AAAA,IACf;AAAA,IACA,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,QAAQ,yCAAyC,IAAI;AAAA,IACrD;AAAA,EACF;AACF;","names":[]}
@@ -1,151 +0,0 @@
1
- import {
2
- readPath
3
- } from "./chunk-NSITFOCJ.js";
4
-
5
- // src/with-lookup/reduce/reducers.ts
6
- function count(opts) {
7
- const _seed = opts?.seed;
8
- void _seed;
9
- return {
10
- op: "count",
11
- init: () => 0,
12
- step: (state) => state + 1,
13
- remove: (state) => state - 1,
14
- finalize: (state) => state,
15
- merge: (a, b) => a + b
16
- };
17
- }
18
- function sum(field, opts) {
19
- const _seed = opts?.seed;
20
- void _seed;
21
- return {
22
- op: "sum",
23
- field,
24
- // Money-only metadata, read by `wrapMoneyReducers`. No effect on a
25
- // generic numeric sum.
26
- ...opts?.convertTo !== void 0 ? { convertTo: opts.convertTo } : {},
27
- ...opts?.fx !== void 0 ? { fx: opts.fx } : {},
28
- init: () => 0,
29
- step: (state, record) => state + readNumber(record, field),
30
- remove: (state, record) => state - readNumber(record, field),
31
- finalize: (state) => state,
32
- merge: (a, b) => a + b
33
- };
34
- }
35
- function avg(field, opts) {
36
- const _seed = opts?.seed;
37
- void _seed;
38
- return {
39
- op: "avg",
40
- field,
41
- init: () => ({ sum: 0, count: 0 }),
42
- step: (state, record) => ({
43
- sum: state.sum + readNumber(record, field),
44
- count: state.count + 1
45
- }),
46
- remove: (state, record) => ({
47
- sum: state.sum - readNumber(record, field),
48
- count: state.count - 1
49
- }),
50
- finalize: (state) => state.count === 0 ? null : state.sum / state.count,
51
- merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count })
52
- };
53
- }
54
- function pushValue(state, value) {
55
- return { values: [...state.values, value] };
56
- }
57
- function removeValue(state, value) {
58
- const idx = state.values.indexOf(value);
59
- if (idx < 0) return state;
60
- const next = state.values.slice();
61
- next.splice(idx, 1);
62
- return { values: next };
63
- }
64
- function min(field, opts) {
65
- const _seed = opts?.seed;
66
- void _seed;
67
- return {
68
- op: "min",
69
- field,
70
- init: () => ({ values: [] }),
71
- step: (state, record) => pushValue(state, readNumber(record, field)),
72
- remove: (state, record) => removeValue(state, readNumber(record, field)),
73
- finalize: (state) => {
74
- if (state.values.length === 0) return null;
75
- let out = state.values[0];
76
- for (let i = 1; i < state.values.length; i++) {
77
- const v = state.values[i];
78
- if (v < out) out = v;
79
- }
80
- return out;
81
- },
82
- merge: (a, b) => ({ values: [...a.values, ...b.values] })
83
- };
84
- }
85
- function max(field, opts) {
86
- const _seed = opts?.seed;
87
- void _seed;
88
- return {
89
- op: "max",
90
- field,
91
- init: () => ({ values: [] }),
92
- step: (state, record) => pushValue(state, readNumber(record, field)),
93
- remove: (state, record) => removeValue(state, readNumber(record, field)),
94
- finalize: (state) => {
95
- if (state.values.length === 0) return null;
96
- let out = state.values[0];
97
- for (let i = 1; i < state.values.length; i++) {
98
- const v = state.values[i];
99
- if (v > out) out = v;
100
- }
101
- return out;
102
- },
103
- merge: (a, b) => ({ values: [...a.values, ...b.values] })
104
- };
105
- }
106
- function moneySum(field, opts) {
107
- return sum(field, opts);
108
- }
109
- function moneyMin(field, opts) {
110
- return min(field, opts);
111
- }
112
- function moneyMax(field, opts) {
113
- return max(field, opts);
114
- }
115
- var reducerBuilder = {
116
- count,
117
- // The generic F parameter on sum/min/max in the interface is type-only; the
118
- // standalone factories are plain `(field: string)` functions. With M=never the
119
- // conditional return collapses to the numeric branch, matching the factories'
120
- // return type, but TypeScript cannot verify that collapse for a generic method
121
- // signature — so each factory is cast directly to the method's type.
122
- sum,
123
- avg,
124
- min,
125
- max,
126
- moneySum,
127
- moneyMin,
128
- moneyMax
129
- };
130
- function readNumber(record, field) {
131
- const value = readPath(record, field);
132
- if (typeof value === "object" && value !== null && "amount" in value && "currency" in value) {
133
- throw new Error(
134
- `aggregate: field "${field}" holds a money value but was not money-aware \u2014 declare it in the collection's moneyFields so sum/min/max stay exact`
135
- );
136
- }
137
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
138
- }
139
-
140
- export {
141
- count,
142
- sum,
143
- avg,
144
- min,
145
- max,
146
- moneySum,
147
- moneyMin,
148
- moneyMax,
149
- reducerBuilder
150
- };
151
- //# sourceMappingURL=chunk-ATL3ZVXT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-lookup/reduce/reducers.ts"],"sourcesContent":["/**\n * Reduction reducers for the query DSL.\n *\n * the reducer protocol plus five built-in factories\n * (`count`, `sum`, `avg`, `min`, `max`) consumed by `Query.aggregate()`\n * and, in the future, `Scan.aggregate()`. Every factory accepts\n * an optional `{ seed }` parameter that is plumbed through the\n * protocol but unused by the executor — that's the load-bearing\n * half of constraint #2. When partition-aware reduction\n * lands, the seed carries the previous partition's running total into\n * the next partition without requiring a protocol change.\n *\n * Reducers are intentionally generic over their internal state type\n * `S` so compound reducers (avg keeps `{sum, count}`, min/max keep a\n * value bag) can model internal bookkeeping without leaking the\n * implementation through the accumulator's public shape. `finalize`\n * collapses `S` back into the user-visible `R`.\n *\n * Reducers are pure data — `init` / `step` / `finalize` / optional\n * `remove` are stateless functions that receive and return `S`. This\n * is the shape that admits O(1) incremental maintenance in a future\n * optimization (delta-aware `LiveReduction` applies `step` or\n * `remove` per delta), without blocking the simpler \"full re-run on\n * source change\" that ships.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport type { MoneyString } from '../../via/money/branded.js'\nimport type { QueryField } from '../../kernel/types.js'\n\n/**\n * A single reducer: factory-produced, ready to plug into an\n * `.aggregate()` spec.\n *\n * Type parameters:\n * - `R` — user-visible result type (what the reduction returns\n * for this slot, e.g. `number` for `sum()`)\n * - `S` — internal state type, defaults to `R` for simple reducers\n * that don't need compound bookkeeping\n *\n * A reducer is stateless: every method is pure over `S`. `init()` is\n * called once per reduction run to build the initial state; `step()`\n * folds a record into the state; `remove()` (optional) un-folds a\n * record, enabling incremental live maintenance; `finalize()` reads\n * the final answer out of the state at the end of the run.\n */\nexport interface Reducer<R, S = R> {\n /** Build the initial state for a fresh reduction run. */\n init(): S\n /** Fold a record into the state. Returns the new state. */\n step(state: S, record: unknown): S\n /**\n * Un-fold a record from the state. Returns the new state.\n *\n * Optional — reducers without `remove` cannot be maintained\n * incrementally and must be re-run from scratch when the underlying\n * record set changes. `sum`, `count`, `avg` implement `remove` in\n * O(1); `min` and `max` implement it in O(N) worst case (when the\n * extremum itself is removed and the next extremum must be\n * recomputed from the remaining contributing values).\n */\n remove?(state: S, record: unknown): S\n /** Collapse the internal state into the user-visible result. */\n finalize(state: S): R\n /**\n * Combine two independent partial states into one (then `finalize` once).\n * Optional. MUST be associative + commutative with `init()` as identity.\n * Never merge finalized results — only states. Enables parallel /\n * hierarchical reduction (e.g. cross-shard or advisor→firm rollup).\n */\n merge?(a: S, b: S): S\n /**\n * Identifying operation tag stamped by each built-in factory.\n * Used by `summariseAggregateOp` in the introspection walker to\n * render human-readable aggregate descriptors in `dumpSchema()`.\n * Optional so third-party custom reducers are unaffected.\n */\n readonly op?: 'count' | 'sum' | 'avg' | 'min' | 'max'\n /**\n * Field name for field-based reducers (`sum`, `avg`, `min`, `max`).\n * Absent on `count` which aggregates over record count, not a field.\n */\n readonly field?: string\n /**\n * Money-only: target currency for `sum` over a multi-currency money\n * field. Consumed by `wrapMoneyReducers` to convert per-currency\n * subtotals to one figure. Ignored for non-money fields.\n */\n readonly convertTo?: string\n /**\n * Money-only: FX rate map (`'USD->EUR' → rate`) used with `convertTo`.\n */\n readonly fx?: Record<string, number | string>\n}\n\n/**\n * Common options accepted by every reducer factory.\n *\n * `seed` — optional initial value for the internal state. **Unused by\n * the executor**, plumbed through the protocol for constraint\n * #2 (partition-aware reduction seam). In, partitioned\n * reductions will pass the previous partition's carry as `seed` so\n * a long time series can be rolled forward one partition at a time\n * without re-reducing closed partitions.\n *\n * always uses `init()` with the factory's zero value, regardless\n * of whether `seed` was passed. Do not remove the parameter — that's\n * the whole point of having it exist now.\n */\nexport interface ReducerOptions<TSeed = unknown> {\n /** constraint #2 — seed is plumbed through but unused in. */\n readonly seed?: TSeed\n /**\n * Money-only (honored by `sum` over a multi-currency money field):\n * convert per-currency subtotals to this currency for a single figure.\n */\n readonly convertTo?: string\n /** Money-only: FX rate map (`'USD->EUR' → rate`) used with `convertTo`. */\n readonly fx?: Record<string, number | string>\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/**\n * Count the number of records that match the query. Ignores field\n * values entirely — the count is over the number of records, not over\n * the number of non-null field values in any column.\n */\nexport function count(opts?: ReducerOptions<number>): Reducer<number> {\n // Seed captured on the closure but unused at execution time in\n //. The reference in _seed keeps lint happy.\n const _seed = opts?.seed\n void _seed\n return {\n op: 'count',\n init: () => 0,\n step: (state) => state + 1,\n remove: (state) => state - 1,\n finalize: (state) => state,\n merge: (a, b) => a + b,\n }\n}\n\n/**\n * Sum a numeric field across all matching records. Non-number values\n * at the field path are coerced to 0 — consumers who want a different\n * behavior (throw, skip, treat as NaN) should filter upstream via\n * `.where()` or write a custom reducer.\n *\n * KNOWN LIMITATION (type imprecision): the declared result type is `number`,\n * but when `field` is a **money** field the runtime returns a decimal *string*\n * (e.g. `'0.30'`, or `{ EUR: '0.30' }` in multi-currency mode) — money sums are\n * BigInt-exact in scaled space and never collapse to a float. The `number` type\n * is therefore a lie for money fields; narrow the result yourself at the call\n * site. A precise fix requires threading money-field declarations into the type\n * system (tracked separately).\n */\nexport function sum(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'sum',\n field,\n // Money-only metadata, read by `wrapMoneyReducers`. No effect on a\n // generic numeric sum.\n ...(opts?.convertTo !== undefined ? { convertTo: opts.convertTo } : {}),\n ...(opts?.fx !== undefined ? { fx: opts.fx } : {}),\n init: () => 0,\n step: (state, record) => state + readNumber(record, field),\n remove: (state, record) => state - readNumber(record, field),\n finalize: (state) => state,\n merge: (a, b) => a + b,\n }\n}\n\n/**\n * Arithmetic mean of a numeric field across all matching records.\n *\n * Returns `null` for an empty result set (zero records is not a\n * well-defined denominator — returning NaN would poison downstream\n * arithmetic, and throwing would force every consumer to wrap in\n * try/catch just to handle \"no matches\"). Consumers who want an\n * explicit zero should coalesce with `?? 0`.\n *\n * Internal state is `{sum, count}` so the running average can be\n * maintained incrementally — on each delta, both fields update in\n * O(1) and `finalize` divides. Directly storing `avg` as state would\n * not admit incremental removal without also tracking count.\n */\nexport function avg(\n field: string,\n opts?: ReducerOptions<{ sum: number; count: number }>,\n): Reducer<number | null, { sum: number; count: number }> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'avg',\n field,\n init: () => ({ sum: 0, count: 0 }),\n step: (state, record) => ({\n sum: state.sum + readNumber(record, field),\n count: state.count + 1,\n }),\n remove: (state, record) => ({\n sum: state.sum - readNumber(record, field),\n count: state.count - 1,\n }),\n finalize: (state) => (state.count === 0 ? null : state.sum / state.count),\n merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count }),\n }\n}\n\nexport interface MinMaxState {\n /**\n * Multiset of contributing field values. Stored as a plain array\n * because we need to support `remove` and a plain array gives us\n * O(1) push + O(N) worst-case removal — which matches the\n * documented min/max removal complexity. A sorted structure would\n * let us drop the O(N) rescan but adds complexity that doesn't\n * need; consumers hitting the O(N) ceiling should file an issue.\n */\n readonly values: number[]\n}\n\nfunction pushValue(state: MinMaxState, value: number): MinMaxState {\n return { values: [...state.values, value] }\n}\n\nfunction removeValue(state: MinMaxState, value: number): MinMaxState {\n // Remove the first matching value — duplicates are fine, we only\n // need to drop one instance per `remove()` call so the multiset\n // count stays consistent with the record count.\n const idx = state.values.indexOf(value)\n if (idx < 0) return state\n const next = state.values.slice()\n next.splice(idx, 1)\n return { values: next }\n}\n\n/**\n * Smallest numeric value of a field across all matching records.\n * Returns `null` for an empty result set. See `avg()` for the\n * reasoning on `null` vs NaN vs throwing.\n *\n * Incremental complexity: O(1) for `step`, O(N) worst case for\n * `remove` when the current minimum is removed (the state holds the\n * full multiset of contributing values and `finalize` scans for the\n * new minimum). Consumers with very large result sets and frequent\n * removals of the current extremum should either accept the cost or\n * wait for a future optimization.\n */\nexport function min(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number | null, MinMaxState> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'min',\n field,\n init: () => ({ values: [] }),\n step: (state, record) => pushValue(state, readNumber(record, field)),\n remove: (state, record) => removeValue(state, readNumber(record, field)),\n finalize: (state) => {\n if (state.values.length === 0) return null\n let out = state.values[0]!\n for (let i = 1; i < state.values.length; i++) {\n const v = state.values[i]!\n if (v < out) out = v\n }\n return out\n },\n merge: (a, b) => ({ values: [...a.values, ...b.values] }),\n }\n}\n\n/**\n * Largest numeric value of a field across all matching records.\n * Mirror of `min()` — see that doc for semantics, null-on-empty\n * behavior, and the O(N) removal caveat.\n */\nexport function max(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number | null, MinMaxState> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'max',\n field,\n init: () => ({ values: [] }),\n step: (state, record) => pushValue(state, readNumber(record, field)),\n remove: (state, record) => removeValue(state, readNumber(record, field)),\n finalize: (state) => {\n if (state.values.length === 0) return null\n let out = state.values[0]!\n for (let i = 1; i < state.values.length; i++) {\n const v = state.values[i]!\n if (v > out) out = v\n }\n return out\n },\n merge: (a, b) => ({ values: [...a.values, ...b.values] }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Money-typed reducer constructors\n// ---------------------------------------------------------------------------\n\n/**\n * `sum()` for a **declared money field**, typed to match the runtime.\n *\n * `sum()` returns `Reducer<number>`, but `wrapMoneyReducers` (applied at\n * `query.aggregate()` time, once `moneyFields` is known) rewrites any\n * `sum`/`min`/`max` over a money field to a money reducer that finalizes to a\n * `MoneyString` decimal — so the `number` type is a lie for money fields and\n * consumers need a cast at every read site. `moneySum` is the same reducer with\n * the correct `Reducer<MoneyString>` return type, so no read-site cast is\n * needed. It is the caller's assertion that `field` is a money field; use plain\n * `sum()` for non-money fields. (For a multi-currency money field WITHOUT\n * `convertTo`, the runtime returns a per-currency `Record<string, MoneyString>`\n * rather than a single `MoneyString` — pass `convertTo` to collapse to one\n * currency, or read the map at the boundary.)\n */\nexport function moneySum(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString> {\n // The constructed reducer is the plain numeric `sum`; `wrapMoneyReducers`\n // swaps in the MoneyString-producing money reducer at aggregate() time.\n return sum(field, opts) as unknown as Reducer<MoneyString>\n}\n\n/**\n * `min()` for a declared money field, typed `Reducer<MoneyString | null>`\n * (null on an empty result set in fixed-currency mode, mirroring `min()`). See\n * {@link moneySum} for the late-binding rewrite. Note: `convertTo`/`fx` on\n * `opts` have no effect on min/max (cross-currency min/max is unsupported — use\n * {@link moneySum} for currency conversion). In multi-currency mode the runtime\n * returns a per-currency map and an empty result is `{}` rather than `null`.\n */\nexport function moneyMin(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString | null> {\n return min(field, opts) as unknown as Reducer<MoneyString | null>\n}\n\n/**\n * `max()` for a declared money field, typed `Reducer<MoneyString | null>`\n * (null on an empty result set in fixed-currency mode, mirroring `max()`). See\n * {@link moneyMin} for the `convertTo`/`fx` and multi-currency caveats.\n */\nexport function moneyMax(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString | null> {\n return max(field, opts) as unknown as Reducer<MoneyString | null>\n}\n\n// ---------------------------------------------------------------------------\n// Builder (typed spec-builder for aggregate())\n// ---------------------------------------------------------------------------\n\n/**\n * Typed builder passed to the `aggregate(b => spec)` overload.\n *\n * Each field-taking method narrows `field` to `QueryField<T, S>`, which\n * excludes any field listed in the collection's `sensitive` option at\n * compile time. `count()` carries no field argument and is always allowed.\n *\n * The type parameters match the `Query<T, S>` they come from:\n * - `T` — the record type of the collection\n * - `S` — the union of sensitive field keys (defaults to `never`)\n *\n * ONE shared runtime instance (`reducerBuilder`) serves all `T`/`S`\n * combinations — the field narrowing is type-only; the methods delegate\n * directly to the standalone factories.\n */\nexport interface ReducerBuilder<T, S extends keyof T = never, M extends keyof T & string = never> {\n count(opts?: ReducerOptions<number>): Reducer<number>\n sum<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString> : Reducer<number>\n avg(field: QueryField<T, S>, opts?: ReducerOptions<{ sum: number; count: number }>): ReturnType<typeof avg>\n min<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString | null> : ReturnType<typeof min>\n max<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString | null> : ReturnType<typeof max>\n moneySum(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString>\n moneyMin(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString | null>\n moneyMax(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString | null>\n}\n\n/**\n * Shared runtime instance for the `aggregate(b => spec)` builder form.\n *\n * The field-narrowing to `QueryField<T, S>` is type-only — each method\n * delegates directly to its standalone factory. A `(field: string) => R`\n * factory is assignable to a `(field: QueryField<T,S>) => R` method by\n * parameter-contravariance, so this single instance works for all `T`/`S`.\n */\nexport const reducerBuilder: ReducerBuilder<Record<string, unknown>> = {\n count,\n // The generic F parameter on sum/min/max in the interface is type-only; the\n // standalone factories are plain `(field: string)` functions. With M=never the\n // conditional return collapses to the numeric branch, matching the factories'\n // return type, but TypeScript cannot verify that collapse for a generic method\n // signature — so each factory is cast directly to the method's type.\n sum: sum as ReducerBuilder<Record<string, unknown>>['sum'],\n avg,\n min: min as ReducerBuilder<Record<string, unknown>>['min'],\n max: max as ReducerBuilder<Record<string, unknown>>['max'],\n moneySum, moneyMin, moneyMax,\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a numeric field from a record. Non-number values (null,\n * undefined, strings, objects) coerce to 0 so sum/avg/min/max don't\n * produce NaN on one bad row. Consumers who want strict typing should\n * validate upstream with Standard Schema, which NOYDB already runs on\n * every `put()`.\n */\nfunction readNumber(record: unknown, field: string): number {\n const value = readPath(record, field)\n // Defensive: a `{ amount, currency }` value means a multi-currency\n // money field reached a generic numeric reducer instead of being\n // rewritten by `wrapMoneyReducers` — that would silently produce a\n // wrong total. Surface it loudly rather than coercing to 0.\n if (\n typeof value === 'object' &&\n value !== null &&\n 'amount' in value &&\n 'currency' in value\n ) {\n throw new Error(\n `aggregate: field \"${field}\" holds a money value but was not money-aware — ` +\n `declare it in the collection's moneyFields so sum/min/max stay exact`,\n )\n }\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-commit/history/history.ts","../src/with-commit/history/strategy.ts"],"sourcesContent":["import { buildRecordEnvelope } from '../../kernel/enclave/index.js'\nimport type { RecordIdentity } from '../../kernel/enclave/index.js'\nimport type { NoydbStore, EncryptedEnvelope, HistoryOptions, PruneOptions } from '../../kernel/types.js'\nimport { isTombstone, isTombstoneShape, rewrapEnvelope, isRewrappedUnder, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n/**\n * History storage convention:\n * Collection: `_history`\n * ID format: `{collection}:{recordId}:{paddedVersion}`\n * Version is zero-padded to 10 digits for lexicographic sorting.\n */\n\nconst HISTORY_COLLECTION = '_history'\nconst VERSION_PAD = 10\n\nfunction historyId(collection: string, recordId: string, version: number): string {\n return `${collection}:${recordId}:${String(version).padStart(VERSION_PAD, '0')}`\n}\n\n/**\n * The identity a history snapshot is **sealed against** — its STORAGE location,\n * not the live record it is a copy of (#1041).\n *\n * ## Why storage, and why this is a decision rather than a detail\n *\n * A snapshot is a copy, so two identities are available: the live record\n * (`collection`/`recordId`) or where the bytes actually land\n * (`_history`/`historyId(...)`). They are not equivalent under attack.\n *\n * Binding the LIVE identity makes a snapshot's AAD **indistinguishable from the\n * live record's at the same version** — so an untrusted store could serve a\n * history entry *as the current record* and the client would accept it. It also\n * leaves entries relocatable within `_history`.\n *\n * Binding STORAGE identity closes both. The cost is that the id must be known\n * *before* the envelope is sealed, where `saveHistory` previously derived it\n * *after* (from `envelope._v`) — which is precisely the restructure this\n * function exists to make possible.\n *\n * `saveHistory` uses this too, so the sealed identity and the put location have\n * **one definition** and cannot drift apart. Changing the layout here changes\n * both at once, by construction.\n */\nexport function historyIdentity(collection: string, recordId: string, version: number): RecordIdentity {\n // The snapshot's own `_v` IS the version it preserves — the same number that\n // appears in its id. One argument feeds both, so they cannot disagree (#1093).\n return { collection: HISTORY_COLLECTION, id: historyId(collection, recordId, version), version }\n}\n\n// Unused today, kept for future history-id parsing utilities.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseHistoryId(id: string): { collection: string; recordId: string; version: number } | null {\n const lastColon = id.lastIndexOf(':')\n if (lastColon < 0) return null\n const versionStr = id.slice(lastColon + 1)\n const rest = id.slice(0, lastColon)\n const firstColon = rest.indexOf(':')\n if (firstColon < 0) return null\n return {\n collection: rest.slice(0, firstColon),\n recordId: rest.slice(firstColon + 1),\n version: parseInt(versionStr, 10),\n }\n}\n\nfunction matchesPrefix(id: string, collection: string, recordId?: string): boolean {\n if (recordId) {\n return id.startsWith(`${collection}:${recordId}:`)\n }\n return id.startsWith(`${collection}:`)\n}\n\n/** Save a history entry (a complete encrypted envelope snapshot). */\nexport async function saveHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n): Promise<void> {\n // Same derivation the caller sealed against — one definition, so the sealed\n // identity and the put location cannot drift (#1041).\n const { id } = historyIdentity(collection, recordId, envelope._v)\n await adapter.put(vault, HISTORY_COLLECTION, id, envelope)\n}\n\n/** Get history entries for a record, sorted newest-first. */\nexport async function getHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n): Promise<EncryptedEnvelope[]> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => matchesPrefix(id, collection, recordId))\n .sort()\n .reverse() // newest first\n\n const entries: EncryptedEnvelope[] = []\n\n for (const id of matchingIds) {\n const envelope = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (!envelope) continue\n\n // Apply time filters\n if (options?.from && envelope._ts < options.from) continue\n if (options?.to && envelope._ts > options.to) continue\n\n entries.push(envelope)\n\n if (options?.limit && entries.length >= options.limit) break\n }\n\n return entries\n}\n\n/** Get a specific version's envelope from history. */\nexport async function getVersionEnvelope(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n version: number,\n): Promise<EncryptedEnvelope | null> {\n const id = historyId(collection, recordId, version)\n return adapter.get(vault, HISTORY_COLLECTION, id)\n}\n\n/** Prune history entries. Returns the number of entries deleted. */\nexport async function pruneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds\n .filter(id => recordId ? matchesPrefix(id, collection, recordId) : matchesPrefix(id, collection))\n .sort()\n\n let toDelete: string[] = []\n\n if (options.keepVersions !== undefined) {\n // Keep only the N most recent, delete the rest\n const keep = options.keepVersions\n if (matchingIds.length > keep) {\n toDelete = matchingIds.slice(0, matchingIds.length - keep)\n }\n }\n\n if (options.beforeDate) {\n // Delete entries older than the specified date\n for (const id of matchingIds) {\n if (toDelete.includes(id)) continue\n const envelope = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (envelope && envelope._ts < options.beforeDate) {\n toDelete.push(id)\n }\n }\n }\n\n // Deduplicate\n const uniqueDeletes = [...new Set(toDelete)]\n\n for (const id of uniqueDeletes) {\n await adapter.delete(vault, HISTORY_COLLECTION, id)\n }\n\n return uniqueDeletes.length\n}\n\n/** Clear all history for a vault, optionally scoped to a collection or record. */\nexport async function clearHistory(\n adapter: NoydbStore,\n vault: string,\n collection?: string,\n recordId?: string,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n let toDelete: string[]\n\n if (collection && recordId) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection, recordId))\n } else if (collection) {\n toDelete = allIds.filter(id => matchesPrefix(id, collection))\n } else {\n toDelete = allIds\n }\n\n for (const id of toDelete) {\n await adapter.delete(vault, HISTORY_COLLECTION, id)\n }\n\n return toDelete.length\n}\n\n/**\n * Crypto-shred every `_history` version of a record. Each non-tombstone\n * history envelope is OVERWRITTEN in place with a tombstone\n * `{ _noydb, _v, _ts: now, _by: actor, _iv: '', _data: '' }` — dropping\n * `_iv`/`_data`/`_cek`/`_det`, so the prior ciphertext (and the wrapped CEK\n * that could decrypt it) is gone everywhere this store reaches. The version\n * counter (`_v`) is preserved so the audit trail still shows \"N versions\n * existed and were erased.\"\n *\n * Overwrite — NOT delete — so the history key itself survives as proof the\n * version existed. Already-tombstoned versions (re-run / idempotent forget)\n * are left untouched and not counted.\n *\n * Returns the number of history versions newly tombstoned.\n */\nexport async function tombstoneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n actor: string,\n encrypted: boolean,\n): Promise<number> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds.filter(id => matchesPrefix(id, collection, recordId))\n\n const now = new Date().toISOString()\n let count = 0\n for (const id of matchingIds) {\n const env = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (!env) continue\n // Already a tombstone (no body and no wrapped CEK)? Skip — idempotent.\n if (isTombstone(env, encrypted)) continue\n // `by` rides on the IDENTITY — passing it in the body went through a\n // conditional spread, which TypeScript does not excess-property-check, so\n // it was silently dropped once `_by` moved to the identity (#1041).\n const tombstone: EncryptedEnvelope = buildRecordEnvelope(\n { collection: HISTORY_COLLECTION, id, ...(actor ? { by: actor } : {}), version: env._v },\n { ts: now, iv: '', data: '' },\n )\n await adapter.put(vault, HISTORY_COLLECTION, id, tombstone)\n count++\n }\n return count\n}\n\n/**\n * Re-key every `_history` snapshot of a record from `fromDek` to `toDek`.\n * Mirrors what `rewrapBodyToDek` already does for a record's LIVE body on a\n * tier move (elevate/demote/putAtTier) — each `_history` envelope also\n * carries its own `_cek`, wrapped under the collection's tier-0 DEK at write\n * time (`record-codec.ts`), so a tier move that rewraps only the live\n * envelope leaves prior versions decryptable at rest under the tier the\n * record left. This is defense-in-depth *beneath* the read-gate\n * (`history()`/`getVersion()` already return empty for an elevated record;\n * this protects the ciphertext even if that gate is bypassed).\n *\n * Rewraps content in place — unlike `tombstoneHistory`, it does NOT blank\n * `_iv`/`_data`/`_cek` — so a subsequent `demote()` restores tier-0\n * readability. Tombstone-shaped entries (a forgotten/shredded version —\n * blanked `_data`, no `_cek`) are skipped: there is no key material left to\n * rewrap.\n *\n * **Legacy fallback.** A snapshot written before this fix stays wrapped\n * under the tier-0 DEK even after its live record has since moved tiers, so\n * a rewrap attempted with a tier-N `fromDek` fails to unwrap/decrypt. When\n * the caller supplies `tier0Dek`, a failed rewrap is retried once with\n * `tier0Dek` as `fromDek` (the only other key a pre-fix snapshot can be\n * wrapped under — history is written only by tier-0 `put()`). The output is\n * always wrapped under `toDek` regardless of which `fromDek` succeeded. A\n * rewrap that fails under BOTH keys re-throws — that is real corruption, not\n * a tier mismatch, and must not be swallowed.\n *\n * **Crash-atomicity / idempotency (#712 whole-branch-fix-3).** This loop has\n * no transaction around it: a crash after some entries have been rewritten\n * under `toDek` but before the loop finishes leaves the record's history\n * split across two keys. A retry of the SAME call must not re-fail on the\n * entries that already made it — so each entry is probed with\n * `isRewrappedUnder(env, toDek)` FIRST; a match means it's already at the\n * target key and is skipped (put nothing). This makes same-target retries\n * and demote-after-crash fully self-healing. It does NOT close every crash\n * window: a crash that lands SOME entries under `toDek` while the record's\n * NEXT move target differs from this call's `toDek` (an intermediate-tier\n * crash — e.g. elevate 0→1 crashes mid-loop, then the record is moved 1→2)\n * still finds those entries unreadable under either `fromDek` or the\n * tier-0 fallback, since `toDek` is a third key the next call never probes\n * for. That residual window is an accepted, fail-closed limitation (see\n * `.changeset/history-at-rest.md` and the design doc) — availability is\n * lost, never confidentiality.\n */\nexport async function rewrapHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n fromDek: EnclaveKey,\n toDek: EnclaveKey,\n tier0Dek?: EnclaveKey,\n): Promise<void> {\n const allIds = await adapter.list(vault, HISTORY_COLLECTION)\n const matchingIds = allIds.filter(id => matchesPrefix(id, collection, recordId))\n\n for (const id of matchingIds) {\n const env = await adapter.get(vault, HISTORY_COLLECTION, id)\n if (!env) continue\n // Already a tombstone (forgotten/shredded version)? Nothing to rewrap.\n if (isTombstoneShape(env)) continue\n // #712/whole-branch-fix-3: toDek-first idempotency skip — already at the\n // target key (a same-target retry, or demote-after-crash landing back on\n // a key it already reached)? Nothing to do; put nothing.\n // In-place re-key of a `_history` entry: the address does not move, so the\n // same identity opens and re-seals it (#1041). Built from the entry itself,\n // exactly as a reader would via `recordAadFor`.\n const identity = {\n collection: HISTORY_COLLECTION, id, version: env._v,\n ...(env._tier !== undefined ? { tier: env._tier } : {}),\n ...(env._by !== undefined ? { by: env._by } : {}),\n }\n if (await isRewrappedUnder(identity, env, toDek)) continue\n\n let next: EncryptedEnvelope\n try {\n next = await rewrapEnvelope(identity, env, fromDek, toDek)\n } catch (err) {\n if (!tier0Dek) throw err\n // Legacy fallback: retry once under the tier-0 DEK. A failure here is\n // real corruption, not a tier mismatch — let it propagate.\n next = await rewrapEnvelope(identity, env, tier0Dek, toDek)\n }\n\n await adapter.put(vault, HISTORY_COLLECTION, id, next)\n }\n}\n","/**\n * Strategy seam for the optional history + ledger + time-machine\n * service. Core imports `HistoryStrategy` type-only + `NO_HISTORY`\n * stub; real implementations of `saveHistory`, `LedgerStore`,\n * `VaultInstant`, `computePatch`, `diff` etc. are only reachable via\n * `withHistory()` in `./active.ts`.\n *\n * Applications that don't track per-record versioning, don't need the\n * hash-chained audit ledger, and don't restore to past instants ship\n * none of the ~1,880 LOC behind this seam.\n *\n * Strategy contract:\n *\n * - **saveHistory / pruneHistory / clearHistory** — no-ops under\n * NO_HISTORY. Writes still succeed; no snapshot is captured.\n * - **getHistoryEntries / getVersionEnvelope / diff** — throw under\n * NO_HISTORY. These are read APIs the consumer would only call\n * after explicitly asking for history; the throw guides them to\n * `@noy-db/hub/history`.\n * - **envelopePayloadHash / computePatch** — return empty / `[]`\n * under NO_HISTORY. These are only used inside the\n * `if (this.ledger)` branch, which is itself gated by\n * `buildLedger()` returning null.\n * - **buildLedger** — returns `null` under NO_HISTORY. The Vault's\n * public `vault.ledger()` accessor throws when null.\n * - **buildVaultInstant** — throws under NO_HISTORY. `vault.at()`\n * propagates the throw.\n *\n * @internal\n */\n\nimport type {\n EncryptedEnvelope,\n NoydbStore,\n HistoryOptions,\n PruneOptions,\n} from '../../kernel/types.js'\nimport type { LedgerStore } from './ledger/store.js'\nimport type { JsonPatch } from './ledger/patch.js'\nimport type { DiffEntry } from './diff.js'\nimport type { VaultInstant, VaultEngine } from './time-machine.js'\nimport type { EnclaveKey, RecordIdentity } from '../../kernel/enclave/index.js'\nimport { historyIdentity } from './history.js'\n\n/**\n * Options accepted by `HistoryStrategy.buildLedger`. Mirrors the\n * `LedgerStore` constructor verbatim — kept in this file so `core`\n * code never imports the LedgerStore module at runtime.\n *\n * @internal\n */\nexport interface BuildLedgerOptions {\n adapter: NoydbStore\n vault: string\n encrypted: boolean\n getDEK: (collectionName: string) => Promise<EnclaveKey>\n actor: string\n}\n\n/**\n * @internal\n */\nexport interface HistoryStrategy {\n /**\n * Persist a full encrypted envelope snapshot of the prior version\n * under `_history/{collection}:{id}:{paddedVersion}`. No-op under\n * `NO_HISTORY`.\n */\n saveHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n envelope: EncryptedEnvelope,\n ): Promise<void>\n\n /**\n * The identity a snapshot of `collection/recordId@version` must be **sealed\n * against** — its `_history` storage location (#1041).\n *\n * On the port because the kernel writes history snapshots but must not import\n * `with-commit` to learn its layout. Exposing it here keeps the derivation in\n * the module that owns the layout, and `saveHistory` uses the same function,\n * so the sealed identity and the put location cannot drift.\n */\n historyIdentity(collection: string, recordId: string, version: number): RecordIdentity\n\n /**\n * List history envelopes for a record, newest first. Throws under\n * `NO_HISTORY` — callers reach this via `collection.history()` /\n * `collection.getVersion()` / `collection.diff()`, which only work\n * with the strategy enabled.\n */\n getHistoryEntries(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n options?: HistoryOptions,\n ): Promise<EncryptedEnvelope[]>\n\n /**\n * Fetch a specific version's envelope. Throws under `NO_HISTORY`.\n */\n getVersionEnvelope(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n version: number,\n ): Promise<EncryptedEnvelope | null>\n\n /**\n * Prune history entries by retention rule. Returns `0` under\n * `NO_HISTORY`.\n */\n pruneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string | undefined,\n options: PruneOptions,\n ): Promise<number>\n\n /**\n * Clear all history for vault/collection/record. Returns `0` under\n * `NO_HISTORY`.\n */\n clearHistory(\n adapter: NoydbStore,\n vault: string,\n collection?: string,\n recordId?: string,\n ): Promise<number>\n\n /**\n * Crypto-shred (overwrite-to-tombstone) every `_history` version of a\n * record for GDPR erasure. Returns the number of versions newly\n * tombstoned, or `0` under `NO_HISTORY` (no history = nothing to shred).\n */\n tombstoneHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n actor: string,\n encrypted: boolean,\n ): Promise<number>\n\n /**\n * Re-key every `_history` snapshot of a record from `fromDek` to `toDek`\n * on a tier move (elevate/demote/putAtTier), mirroring the live-body\n * rewrap so prior versions are not left decryptable at rest under a tier\n * the record has moved away from. Optional `tier0Dek` enables the legacy\n * fallback for snapshots written before this rewrap existed (see\n * `history.ts` for the full contract). No-op under `NO_HISTORY` (no\n * history = nothing to rewrap).\n */\n rewrapHistory(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n recordId: string,\n fromDek: EnclaveKey,\n toDek: EnclaveKey,\n tier0Dek?: EnclaveKey,\n ): Promise<void>\n\n /**\n * Compute the SHA-256 hash of an envelope's encrypted payload, used\n * by `LedgerStore.append` to track tamper-evidence. Returns the\n * empty string under `NO_HISTORY` (the call site is gated by\n * `if (this.ledger)`, so the value is never observed).\n */\n envelopePayloadHash(envelope: EncryptedEnvelope | null): Promise<string>\n\n /**\n * Compute the JSON patch from `from` → `to`. Returns `[]` under\n * `NO_HISTORY`.\n */\n computePatch(from: unknown, to: unknown): JsonPatch\n\n /**\n * Compute the typed diff between two records. Throws under\n * `NO_HISTORY` — `collection.diff()` is a history-read API.\n */\n diff(recordA: unknown, recordB: unknown): DiffEntry[]\n\n /**\n * Construct (or return null) a `LedgerStore` for the vault. Returns\n * `null` under `NO_HISTORY`; the Vault treats null as \"no ledger\n * attached\" — collection write paths skip the append branch and the\n * public `vault.ledger()` accessor throws.\n */\n buildLedger(opts: BuildLedgerOptions): LedgerStore | null\n\n /**\n * Construct a `VaultInstant` for time-machine reads. Throws under\n * `NO_HISTORY`.\n */\n buildVaultInstant(engine: VaultEngine, timestamp: string): VaultInstant\n}\n\n/**\n * Error thrown when the consumer reaches a history-gated surface\n * without opting into the strategy. The message names the offending\n * operation and points to the subpath import.\n *\n * @internal\n */\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the history strategy. Import ` +\n '`{ withHistory }` from \"@noy-db/hub/history\" and pass it to ' +\n '`createNoydb({ historyStrategy: withHistory() })`.',\n )\n}\n\n/**\n * No-history stub. Snapshots and prune/clear are no-ops; reads and\n * time-machine throw with an actionable message; ledger construction\n * returns null so the write-path's `if (this.ledger)` branch is dead\n * code in the bundle.\n *\n * @internal\n */\nexport const NO_HISTORY: HistoryStrategy = {\n async saveHistory() {},\n // Unreachable in practice — `saveHistory` above is a no-op, so nothing is\n // ever sealed. Returns the real layout rather than a sentinel so a future\n // caller that reads it without writing cannot be misled.\n historyIdentity: (collection, recordId, version) => historyIdentity(collection, recordId, version),\n async getHistoryEntries() { throw notEnabled('collection.history()') },\n async getVersionEnvelope() { throw notEnabled('collection.getVersion()') },\n async pruneHistory() { return 0 },\n async clearHistory() { return 0 },\n async tombstoneHistory() { return 0 },\n async rewrapHistory() {},\n async envelopePayloadHash() { return '' },\n computePatch() { return [] },\n diff() { throw notEnabled('collection.diff()') },\n buildLedger() { return null },\n buildVaultInstant() { throw notEnabled('vault.at() / vault.timeMachine()') },\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-commit/tx/atomic-eligibility.ts","../src/kernel/best-effort-revert.ts","../src/with-commit/tx/transaction.ts"],"sourcesContent":["/**\n * #893/#906-prep — the atomic-commit eligibility gate.\n *\n * `runTransaction` (Task 5) consults `canCommitAtomically` right after the\n * body returns, to decide whether the whole staged batch can be prepared and\n * submitted as ONE `store.tx(ops)` call instead of the per-op abortable\n * path (Phase 1 pre-flight + Phase 2 Collection-layer execute + Phase 3\n * best-effort revert — see `transaction.ts`'s module doc).\n *\n * Pure decision logic: no side effects, no store reads. Every input is\n * already sitting on `db` / `ctx`.\n *\n * ALL of the following must hold. (Commit-time changeset invariants are NOT a\n * condition: that phase lives after Phase 2 in `runTransaction` and runs\n * unchanged on either path — a failing invariant reverts the atomic batch\n * through the same `ctx._executed` plan.)\n *\n * 1. The store declares `capabilities.txAtomic === true` AND implements\n * `tx()`. Both — mirrors the pairing rule `bestEffortRevert` already\n * enforces (`kernel/best-effort-revert.ts:78`): an undeclared `tx()` is\n * never used, even if present.\n * 2. Not an amendment transaction (`ctx._amendment === false`) — amendments\n * need the guard-registry change-set + invariant machinery the atomic\n * path skips entirely.\n * 3. No `(vault, collection, id)` is touched twice in the batch. The\n * abortable path's Phase 1 snapshots each key once and Phase 2 replays\n * every op against that same snapshot in order; a single `store.tx()`\n * write set can't express \"this key changes twice in sequence.\"\n * 4. Every touched collection reports `_txAtomicSafe(op.type)` — see that\n * method's doc comment on `Collection` for what it excludes and why.\n * #931: of the wrapper-level write signals, only a live `onBeforeWrite`\n * hook still excludes — it may REFUSE a write, which only\n * `Collection.put()`/`.delete()` honors. After-hooks and the\n * `afterPut`/`afterDelete` observe bus are refusal-free observers, so\n * the atomic path fires them itself per leg post-finalize\n * (`Collection._fireAtomicAfterWrite`) instead of forfeiting the batch.\n * Delete gates on the enforcer's `_deleteCascadesPossible(name)` (#922):\n * `Vault.enforceRefsOnDelete` cascades from THREE sources (lookup-ref\n * edges, classic inbound refs, managed-link `onDelete`), and the Vault\n * predicate unions exactly those three — a `getInbound`-only check would\n * be narrower and wrongly admit unsafe atomic deletes, because\n * `_prepareDelete` runs the cascades DURING prepare (see\n * `with-shape/links/vault-facade.ts` `enforceRefsOnDelete`).\n *\n * @internal\n */\n\nimport type { Noydb } from '../../kernel/noydb.js'\nimport type { TxContext } from './transaction.js'\nimport { keyOf } from './transaction.js'\n\nexport function canCommitAtomically(db: Noydb, ctx: TxContext): boolean {\n const store = db._store\n if (store.capabilities?.txAtomic !== true || typeof store.tx !== 'function') return false\n if (ctx._amendment) return false\n\n const seen = new Set<string>()\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (seen.has(key)) return false\n seen.add(key)\n }\n\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n if (!coll._txAtomicSafe(op.type)) return false\n }\n\n return true\n}\n","/**\n * Best-effort reverse-revert — the shape shared by two independent rollback\n * paths that used to duplicate it: satellite fan-out\n * (`with-shape/satellites/fanout.ts`'s `revertAndCompensate`) and\n * multi-record transactions (`with-commit/tx/transaction.ts`'s\n * `revertExecuted`). Both revert a list of already-executed writes on\n * failure by walking them in REVERSE order and, per leg, restoring the\n * captured prior envelope straight through the raw adapter — `put(prior)`\n * when one existed, `delete` when the leg was a fresh insert — bypassing\n * the Collection layer so the revert itself doesn't re-fire\n * encryption/ledger/change-event machinery. Each leg's raw-adapter revert\n * is best-effort: a throw is swallowed so a revert-path failure never masks\n * the original error that triggered the rollback, and the loop moves on to\n * the next leg.\n *\n * An optional per-leg `compensate` callback runs immediately after a\n * successful raw-adapter revert (still inside the same try, so a throwing\n * callback is swallowed the same way as a throwing raw revert). What the\n * callback does is entirely caller-defined — satellites' fan-out fires\n * `_compensateRevertedWrite` only for legs whose write actually landed\n * (#596); with-commit's transaction executor invalidates the Collection\n * cache. Neither concern belongs here: this helper only knows the generic\n * reverse/best-effort/raw-revert shape.\n *\n * Lives in `kernel/` — not a `with-*` service — so both consumers (one a\n * gated service, `with-commit`; one an always-on schema feature,\n * `with-shape/satellites`) can import it without creating a\n * `with-* → with-*` edge, which would force either family to always bundle\n * the other and defeat the opt-in tree-shaking both are built around.\n *\n * @internal — implementation-sharing only, not part of the public surface.\n */\nimport type { EncryptedEnvelope, StoreCapabilities, TxOp } from './types.js'\n\n/** One already-executed write a revert pass needs to unwind. */\nexport interface BestEffortRevertLeg {\n readonly vaultName: string\n readonly collectionName: string\n readonly id: string\n /** Envelope captured before the write, or `null` if the id didn't exist yet. */\n readonly prior: EncryptedEnvelope | null\n}\n\n/** The subset of `NoydbStore` a raw-adapter revert needs. */\nexport interface BestEffortRevertAdapter {\n put(vaultName: string, collectionName: string, id: string, envelope: EncryptedEnvelope): Promise<void>\n delete(vaultName: string, collectionName: string, id: string): Promise<void>\n /**\n * Optional storage-layer transaction (#886). When the store declares\n * `txAtomic`, the whole revert is submitted as ONE operation instead of a\n * per-leg loop, so a crash mid-revert can no longer leave the vault\n * half-unwound.\n */\n tx?(ops: readonly TxOp[]): Promise<void>\n readonly capabilities?: StoreCapabilities\n}\n\n/**\n * Revert `executed` in reverse order via the raw adapter, best-effort.\n * `compensate`, when supplied, runs once per leg immediately after that\n * leg's own raw revert succeeds.\n */\nexport async function bestEffortRevert<T extends BestEffortRevertLeg>(\n executed: readonly T[],\n adapter: BestEffortRevertAdapter,\n compensate?: (leg: T) => Promise<void> | void,\n): Promise<void> {\n const legs = [...executed].reverse()\n\n // #886 — atomic revert when the store can do it. The legs already carry RAW\n // prior envelopes (captured pre-write), which is exactly the shape `tx()`\n // wants, so this needs none of the Collection-layer machinery that makes\n // delegating the FORWARD write path a much larger job.\n //\n // Failure stays best-effort: if the store rejects the batch we fall through\n // to the per-leg loop rather than surfacing a revert-path error, because a\n // revert failure must never mask the original error that triggered it.\n if (adapter.capabilities?.txAtomic === true && typeof adapter.tx === 'function') {\n const ops: TxOp[] = legs.map(leg =>\n leg.prior !== null\n ? { type: 'put', vault: leg.vaultName, collection: leg.collectionName, id: leg.id, envelope: leg.prior }\n : { type: 'delete', vault: leg.vaultName, collection: leg.collectionName, id: leg.id },\n )\n try {\n await adapter.tx(ops)\n if (compensate) for (const leg of legs) { try { await compensate(leg) } catch { /* best-effort */ } }\n return\n } catch {\n // fall through to the per-leg path below\n }\n }\n\n for (const leg of legs) {\n try {\n if (leg.prior !== null) {\n await adapter.put(leg.vaultName, leg.collectionName, leg.id, leg.prior)\n } else {\n await adapter.delete(leg.vaultName, leg.collectionName, leg.id)\n }\n if (compensate) await compensate(leg)\n } catch {\n // best-effort — a revert-path failure must not mask the original\n // error that triggered the rollback (matches both callers' prior\n // independent implementations).\n }\n }\n}\n","/**\n * Multi-record atomic transactions.\n *\n * Lets an application stage writes across two or more collections (or\n * vaults) and commit them all-or-nothing.\n *\n * ```ts\n * await db.transaction(async (tx) => {\n * const inv = tx.vault('acme').collection<Invoice>('invoices')\n * const pay = tx.vault('acme').collection<Payment>('payments')\n * await inv.put(invoiceId, { ...invoice, status: 'paid' })\n * await pay.put(paymentId, { invoiceId, amount, paidAt })\n * })\n * // If the body throws before returning: nothing persisted.\n * // If the body returns: all puts committed; any CAS mismatch rolls\n * // the batch back and surfaces as ConflictError.\n * ```\n *\n * ## Atomicity semantics\n *\n * Ops are buffered during the body. On body-return the hub:\n *\n * 1. **Pre-flight** — re-reads every touched envelope and enforces\n * any caller-supplied `expectedVersion`. A mismatch throws\n * `ConflictError` with *no* writes performed.\n * 2. **Execute** — calls `Collection.put()` / `.delete()` for each\n * staged op in declaration order. History snapshots, ledger\n * appends, and change events fire as normal per op.\n * 3. **Unwind on failure** — if step 2 throws mid-batch, each\n * already-committed op is reverted via the raw store (restoring\n * the captured prior envelope, or deleting if none existed). The\n * ledger is NOT rewritten — audit history preserves the partial\n * commit and the revert.\n *\n * **Atomic delegation (#906).** Steps 1–3 above describe the OCC\n * fallback. When the store declares `StoreCapabilities.txAtomic` AND\n * implements `tx()`, AND the staged batch is statically safe\n * (`canCommitAtomically` — see `atomic-eligibility.ts`), the commit\n * instead: prepares every op through `Collection._preparePut` /\n * `_prepareDelete` (encrypt, resolve the prior version, mint the #589\n * marker — no observable side effect), submits the whole write set as\n * ONE `store.tx(ops)` call with a per-leg `expectedVersion`, then\n * finalizes each op in staged order. The visible difference: history\n * snapshots, ledger entries and change events all fire AFTER the bytes\n * are durable, instead of interleaving per op.\n *\n * **Crash window.** On the OCC fallback, steps 2–3 are not a\n * storage-layer transaction — if the process dies between two executed\n * ops, the on-disk state is partial. The atomic path above closes that\n * window for the write set itself (`to-memory` and the SQL stores in\n * `noy-db-to` implement `tx()`); a crash between the batch landing and\n * the finalize loop finishing still leaves per-op side effects (history,\n * ledger, cache) incomplete. Stores that cannot commit a batch\n * atomically (file, S3) omit `tx()` and keep the fallback.\n *\n * ## Not covered\n *\n * - Cross-sync-peer atomicity. Transactions commit against the\n * primary store only; the sync engine pushes on its normal\n * schedule. For cross-peer two-phase commit use `SyncTransaction`\n * via `db.transaction(vaultName)`.\n * - Read-your-writes within the body. `tx.collection().get(id)`\n * returns the most-recently-staged value for that id when one\n * exists; if no staged op has touched the id, it reads the current\n * committed state. Version numbers returned by `get` reflect the\n * pre-transaction state (staged puts have no version yet).\n *\n * @module\n */\n\nimport type { Noydb } from '../../kernel/noydb.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EncryptedEnvelope, TxOp } from '../../kernel/types.js'\nimport type { PreparedPut, PreparedDelete } from '../../kernel/prepared-write.js'\nimport { canCommitAtomically } from './atomic-eligibility.js'\nimport {\n AmendmentForbiddenError,\n ConflictError,\n InvariantError,\n ValidationError,\n} from '../../kernel/errors.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport type { GuardExecutor as GuardExecutorModule } from '../../with-audit/guards/executor.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\nimport type { TransactionInvariant } from './invariants.js'\nimport type { GuardChange, GuardContext, ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport { bestEffortRevert } from '../../kernel/best-effort-revert.js'\n\n/** One op buffered inside a running `TxContext`. @internal */\nexport interface StagedOp {\n type: 'put' | 'delete'\n vaultName: string\n collectionName: string\n id: string\n record?: unknown\n expectedVersion?: number\n /**\n * Optional human-readable tag forwarded to the resulting ledger\n * entry's `reason` field. Set by callers via\n * `tx.vault(v).collection(c).put(id, record, { reason })`.\n */\n reason?: string\n}\n\n/**\n * One executed op (main staged op or recursive side-effect like a\n * derivation output) paired with the envelope captured before the write.\n * `revertExecuted` walks this array in reverse on rollback.\n * @internal\n */\nexport interface ExecutedOp {\n op: StagedOp\n priorEnvelope: EncryptedEnvelope | null\n}\n\n/**\n * Options accepted by `db.transaction({ amendment, reason }, fn)`.\n * Only the amendment variant uses these — a plain `db.transaction(fn)`\n * never sees this shape.\n */\nexport interface AmendmentTxOptions {\n /** Opt into amendment mode. Required to be `true`. */\n readonly amendment: true\n /** Human-readable rationale recorded in the ledger entry. Required. */\n readonly reason: string\n}\n\n/**\n * Transaction handle passed to the user's body. Use\n * `tx.vault(name).collection<T>(name)` to get a per-collection\n * facade; its `put`/`delete`/`get` calls stage ops against the tx.\n */\nexport class TxContext {\n /** Stable id for this transaction; shared by all writes it performs. */\n readonly txId: string = generateULID()\n /** @internal */\n readonly _ops: StagedOp[] = []\n /**\n * @internal — write log built up in Phase 2. Each entry records the\n * envelope captured BEFORE the write so a mid-batch failure can\n * restore prior state via `revertExecuted`. Side-effect writes (e.g.\n * recursive derivation outputs fired inside `Collection.put`) are\n * appended here in execution order so they roll back alongside the\n * main staged ops.\n */\n readonly _executed: ExecutedOp[] = []\n /** @internal */\n readonly _db: Noydb\n /**\n * @internal — true when this TxContext was opened in amendment\n * mode. Toggles the lazy-`beginAmendment` + role-check path on first\n * `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.\n */\n readonly _amendment: boolean\n /** @internal — vaults that have already had `beginAmendment` called. */\n readonly _amendmentVaults = new Map<string, Vault>()\n\n /** @internal */\n constructor(db: Noydb, amendment = false) {\n this._db = db\n this._amendment = amendment\n }\n\n /** Scope subsequent `collection()` calls to the named vault. */\n vault(name: string): TxVault {\n const v = this._db.vault(name)\n if (this._amendment && !this._amendmentVaults.has(name)) {\n // Role check is per-vault. The task spec (\"only admin or owner\n // can open an amendment\") is implemented lazy-on-first-touch\n // because the role lives on the vault's keyring, and `tx.vault()`\n // is the first place we know which vault we're addressing. The\n // observable effect is identical to an eager check in the single-\n // vault case the tests exercise; multi-vault amendments check\n // each touched vault as they first appear.\n const role = v.role\n // FR-6: custodian is admin-rank for operational mutations, and an\n // amendment is an operational (data-correcting) act — not an ownership\n // meta-capability — so custodian is allowed alongside owner/admin.\n if (role !== 'admin' && role !== 'owner' && role !== 'custodian') {\n throw new AmendmentForbiddenError(v.userId, role)\n }\n // Amendments require an initialised guard registry — they\n // produce a structured invariant + change-set audit. A vault\n // opened without `guardStrategies` (or via the sync fallback\n // path) has a null registry and cannot run an amendment.\n const reg = v._getGuardRegistry()\n if (reg === null) {\n throw new ValidationError(\n `Vault \"${name}\": amendment mode requires at least one ` +\n `guardStrategy registered via createNoydb({ guardStrategies }). ` +\n `Open the vault with guardStrategies before calling ` +\n `db.transaction({ amendment: true }).`,\n )\n }\n reg.beginAmendment()\n this._amendmentVaults.set(name, v)\n }\n return new TxVault(this, v)\n }\n}\n\n/** Per-vault facade inside a running transaction. */\nexport class TxVault {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault) {\n this._ctx = ctx\n this._vault = vault\n }\n\n /** Scope subsequent op calls to the named collection. */\n collection<T>(name: string): TxCollection<T> {\n const c = this._vault.collection<T>(name)\n return new TxCollection<T>(this._ctx, this._vault, c, name)\n }\n}\n\n/** Per-collection facade inside a running transaction. */\nexport class TxCollection<T> {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n /** @internal */\n readonly _coll: Collection<T>\n /** @internal */\n readonly _name: string\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault, coll: Collection<T>, name: string) {\n this._ctx = ctx\n this._vault = vault\n this._coll = coll\n this._name = name\n }\n\n /**\n * Read the current committed value, or the most-recently-staged\n * value from the same transaction if one exists.\n */\n async get(id: string): Promise<T | null> {\n for (let i = this._ctx._ops.length - 1; i >= 0; i--) {\n const op = this._ctx._ops[i]!\n if (\n op.vaultName === this._vault.name &&\n op.collectionName === this._name &&\n op.id === id\n ) {\n if (op.type === 'delete') return null\n return op.record as T\n }\n }\n return this._coll.get(id)\n }\n\n /**\n * Stage a put. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n put(id: string, record: T, options?: { expectedVersion?: number; reason?: string }): void {\n const op: StagedOp = {\n type: 'put',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n record,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n if (options?.reason !== undefined) op.reason = options.reason\n this._ctx._ops.push(op)\n }\n\n /**\n * Stage a delete. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n delete(id: string, options?: { expectedVersion?: number }): void {\n const op: StagedOp = {\n type: 'delete',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n this._ctx._ops.push(op)\n }\n}\n\n/**\n * Commit plan: pre-flight check + execution + revert plan.\n *\n * @internal — driven by `withTransactions()` (via `tx/active.ts`) for\n * user-facing `db.transaction(...)` calls and by the `amendment` path\n * in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop\n * but shares the `_activeTxContext` mechanism (and the `revertExecuted`\n * helper) so nested side-effect derivation writes get registered for\n * revert alongside the bulk-put source ops.\n */\nexport async function runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n txInvariants?: ReadonlyArray<TransactionInvariant>,\n): Promise<T> {\n // ─── Amendment-mode pre-flight ───────────────────────────────\n // `reason` is the only thing we can validate before the body runs;\n // the per-vault role check happens lazily on first `tx.vault(name)`\n // because we don't know which vaults the body will touch ahead of\n // time. Throwing here keeps the failure mode close to the call site\n // so the developer doesn't have to walk an async stack to find the\n // missing-reason mistake.\n if (options?.amendment) {\n if (typeof options.reason !== 'string' || options.reason.trim().length === 0) {\n throw new ValidationError(\n 'db.transaction({ amendment: true }) requires a non-empty `reason` string.',\n )\n }\n }\n\n const ctx = new TxContext(db, options?.amendment === true)\n const bodyResult = await fn(ctx)\n\n if (ctx._ops.length === 0) {\n // Body produced no ops. If amendment mode was active we still\n // need to close any opened windows so a subsequent (unrelated)\n // write doesn't surprise-collect into a stale change-set. Each\n // `beginAmendment` is matched by exactly one `consumeChanges`.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n // Registry is guaranteed non-null here — `tx.vault(name)`\n // threw above if it was null before adding to\n // `_amendmentVaults`.\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n return bodyResult\n }\n\n // Phase 1 — pre-flight: snapshot every touched envelope and enforce\n // any caller-supplied expectedVersion. Same (vault, coll, id) touched\n // more than once in one tx snapshots only the *initial* committed\n // state; the in-order replay in Phase 2 takes care of successor ops.\n const priorEnvelopes = new Map<string, EncryptedEnvelope | null>()\n const store = db._store\n\n // Commit-time changeset invariants need PLAINTEXT prior records\n // for `before`, but `priorEnvelopes` holds ENCRYPTED envelopes. So for\n // ops in a watched scope we additionally decrypt the prior record here,\n // in Phase 1, BEFORE Phase 2 overwrites it. Snapshots only the initial\n // committed state per (vault, coll, id), matching the envelope snapshot.\n const invariants = txInvariants ?? []\n const watchedScopes = new Set(invariants.map(i => i.scope))\n const plainBefore = new Map<string, unknown>()\n\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!priorEnvelopes.has(key)) {\n const env = await store.get(op.vaultName, op.collectionName, op.id)\n priorEnvelopes.set(key, env)\n }\n if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {\n const prior = await db\n .vault(op.vaultName)\n .collection(op.collectionName)\n .get(op.id)\n plainBefore.set(key, prior ?? null)\n }\n if (op.expectedVersion !== undefined) {\n const env = priorEnvelopes.get(key) ?? null\n const actual = env?._v ?? 0\n if (actual !== op.expectedVersion) {\n throw new ConflictError(\n actual,\n `Transaction pre-flight: ${op.vaultName}/${op.collectionName}/${op.id} ` +\n `expected v${op.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n\n // Phase 2 — execute via the Collection layer so history snapshots,\n // ledger entries, and change events fire normally. We capture each\n // successful op so a mid-batch throw can revert in Phase 3.\n //\n // `_activeTxContext` is published on the Noydb instance for the\n // duration of Phase 2 so recursive writes triggered inside\n // `Collection.put` (today: eager derivation outputs) can register\n // their own envelopes onto `ctx._executed` and roll back alongside\n // the main staged ops. The `finally` clears it before the\n // amendment commit phase runs.\n db._setActiveTxContext(ctx)\n try {\n if (canCommitAtomically(db, ctx)) {\n // #906 — the whole batch goes to the store as ONE `tx()` write set.\n // Tracked here because the atomic path bypasses `Collection.put()`,\n // which is where an ordinary write enters the queue: without this,\n // `hub.writeQueue.pending` would read false for the entire commit.\n // One tracked unit, not one per op — the batch IS one logical write.\n await db._writeQueueTracker.track(() => commitAtomicBatch(db, ctx, priorEnvelopes))\n } else {\n try {\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n const key = keyOf(op)\n const prior = priorEnvelopes.get(key) ?? null\n // Record the revert plan BEFORE the call so a mid-`coll.put` throw\n // (e.g. strict-mode derivation failure firing after `store.put`\n // has already committed the envelope) still has its source write\n // reverted. `revertExecuted` is best-effort: putting prior back is\n // idempotent when the failing op never actually wrote, and\n // `_invalidateCacheEntry` is a no-op when the collection isn't\n // hydrated.\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(op.id, op.record as any, op.reason !== undefined ? { reason: op.reason } : undefined)\n } else {\n await coll.delete(op.id)\n }\n }\n } catch (err) {\n // Phase 3 — best-effort revert. See helper docstring.\n await revertExecuted(ctx._executed, store, db)\n // Drain amendment windows so the next transaction starts clean.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n throw err\n }\n }\n } finally {\n db._clearActiveTxContext(ctx)\n }\n\n // ─── Amendment commit phase (only if amendment === true) ────\n // Body succeeded — now run each touched vault's invariants over the\n // collected change-set, then append a structured ledger entry. If\n // any invariant throws, treat it exactly like a mid-Phase-2 failure:\n // revert every executed op and re-throw the InvariantError.\n if (ctx._amendment) {\n // Lazy-load GuardExecutor at the dispatch site — keeps the floor\n // bundle free of the guards service when amendments aren't used.\n // Mirrors the deferred-load pattern from elsewhere in this module.\n const { GuardExecutor } = (await import('../../with-audit/guards/executor.js')) as {\n GuardExecutor: typeof GuardExecutorModule\n }\n try {\n for (const [vaultName, v] of ctx._amendmentVaults) {\n const registry = v._getGuardRegistry()\n // Registry is guaranteed non-null at this point — the\n // `tx.vault(name)` path that populates `_amendmentVaults`\n // throws if the registry is null. The defensive check here\n // is for TypeScript's narrowing.\n if (registry === null) continue\n const changesByCollection = registry.consumeChanges()\n const meta = registry.consumeMeta()\n if (changesByCollection.size === 0) continue\n\n const readOnlyVault = v._getReadOnlyFacade()\n if (readOnlyVault === null) continue\n\n // Build the invariant ctx once per vault — it's the same shape\n // every guard sees on the normal `check` path, just with a\n // synthetic `existing: null` (invariants get the full change\n // set in their first parameter; `existing` is a per-record\n // concept that doesn't apply here).\n const invariantsPassed: string[] = []\n for (const [collection, changes] of changesByCollection) {\n const guards = registry.guardsFor(collection).filter(g => g.amendment !== undefined)\n for (const guard of guards) {\n await GuardExecutor.runInvariant(guard, changes, {\n existing: null,\n vault: readOnlyVault,\n userId: v.userId,\n role: v.role,\n })\n }\n if (guards.length > 0) invariantsPassed.push(collection)\n }\n\n // Append the audit ledger entry. Silent no-op when the\n // history strategy isn't configured — the records still\n // committed, only the multi-record summary is unavailable.\n const ledger = v._getLedgerOrNull()\n if (ledger) {\n const role = v.role as 'admin' | 'owner'\n const amendment: NonNullable<LedgerEntry['amendment']> = {\n reason: options!.reason,\n role,\n changes: meta,\n invariantsPassed,\n }\n await ledger.append({\n op: 'amendment',\n collection: '',\n id: '',\n version: 0,\n actor: v.userId,\n // No payload to hash — the per-record entries already\n // captured `payloadHash` at their own append time. We use\n // a sha256 of the canonical reason string so the field is\n // populated with something deterministic and non-empty.\n payloadHash: '',\n amendment,\n })\n }\n void vaultName\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n // ─── Commit-time changeset invariant phase ───────────\n // Runs for BOTH ordinary and amendment transactions (placed after the\n // amendment phase so an amendment commit is still subject to these\n // set-level constraints). Assemble the changeset from the executed\n // staged ops, deduped to the LAST write per (vault, coll, id) while\n // preserving write order, then group `GuardChange` by collection\n // (scope) and run each matching invariant. A throw mirrors the\n // amendment-phase failure mode exactly: revert every executed op and\n // re-throw as `InvariantError`.\n if (invariants.length > 0) {\n // Dedup ctx._ops to the last write per key, preserving first-seen\n // (write) order so the changeset is stable and order-meaningful.\n const lastOp = new Map<string, StagedOp>()\n const order: string[] = []\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!lastOp.has(key)) order.push(key)\n lastOp.set(key, op)\n }\n\n // Group {before, after} pairs by collection name (the invariant\n // scope). `before` is the plaintext prior captured in Phase 1 (null\n // for inserts / unwatched — only watched scopes were captured, and\n // every grouped key belongs to a watched scope). `after` is the\n // written record, null for a delete.\n const changesByScope = new Map<string, GuardChange<unknown>[]>()\n // Parallel map: scope → the vault name of its (last-seen) op, used to\n // build the per-invariant read-only ctx (facade + userId + role).\n const scopeVault = new Map<string, string>()\n for (const key of order) {\n const op = lastOp.get(key)!\n if (!watchedScopes.has(op.collectionName)) continue\n const before = plainBefore.get(key) ?? null\n const after = op.type === 'delete' ? null : (op.record ?? null)\n const change = { before, after } as GuardChange<unknown>\n const arr = changesByScope.get(op.collectionName)\n if (arr) arr.push(change)\n else changesByScope.set(op.collectionName, [change])\n\n // Stash the vault name alongside so we can build a per-vault ctx.\n // (All ops in a scope group could span vaults; we resolve the\n // vault per change below via a parallel map keyed the same way.)\n scopeVault.set(op.collectionName, op.vaultName)\n }\n\n try {\n for (const inv of invariants) {\n const changes = changesByScope.get(inv.scope)\n if (changes === undefined || changes.length === 0) continue\n const vaultName = scopeVault.get(inv.scope)!\n const v = db.vault(vaultName)\n // Prefer the real read-only facade so the invariant can read\n // sibling collections; fall back to a minimal read-only stub.\n const facade: ReadOnlyVaultFacade =\n v._getReadOnlyFacade() ?? {\n collection<R = unknown>(name: string) {\n const c = v.collection<R>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n query: () => c.query(),\n }\n },\n }\n const ctxForInv: GuardContext<unknown> = {\n existing: null,\n vault: facade,\n userId: v.userId,\n role: v.role,\n }\n await inv.check(changes, ctxForInv)\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n return bodyResult\n}\n\n/**\n * One prepared leg of an atomic batch: the `store.tx()` op to submit plus the\n * carrier its finalize half needs.\n * @internal\n */\ninterface AtomicLeg {\n readonly op: StagedOp\n readonly coll: Collection<unknown>\n readonly txOp: TxOp\n readonly prepared: PreparedPut<unknown> | PreparedDelete<unknown>\n}\n\n/**\n * #906 — the atomic commit path: prepare every staged op, submit the whole\n * write set as ONE `store.tx()` call, then finalize each op in staged order.\n *\n * Only ever reached through `canCommitAtomically` (`atomic-eligibility.ts`),\n * whose exclusions are what make this safe — no CRDT, no refs, no\n * derivation/MV source, no unique constraints, no amendment, no id touched\n * twice. Do not call it without that blessing.\n *\n * @internal\n */\nasync function commitAtomicBatch(\n db: Noydb,\n ctx: TxContext,\n priorEnvelopes: ReadonlyMap<string, EncryptedEnvelope | null>,\n): Promise<void> {\n const store = db._store\n const legs: AtomicLeg[] = []\n\n // ─── Prepare ───────────────────────────────────────────────────\n // Encrypt, resolve the prior version, mint the #589 marker — with zero\n // observable side effects on the collections the gate admits. A throw here\n // therefore needs no unwind: nothing has been written yet, and the caller\n // sees the refusal exactly as it would from `Collection.put()`. The revert\n // plan is recorded all the same, because a FINALIZE failure below does need\n // it (same record-before-the-call convention as the OCC loop).\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n // The two refusals `Collection.put()` / `.delete()` assert before anything\n // else — schema-update gate + schema fence. They live in those wrappers,\n // not in the prepare halves this path calls, so assert them here, per op.\n await coll._assertWriteGates()\n const prior = priorEnvelopes.get(keyOf(op)) ?? null\n // Every leg carries CAS against the Phase-1 snapshot, so a writer landing\n // between the body returning and the batch reaching the store loses.\n const base = { vault: op.vaultName, collection: op.collectionName, id: op.id, expectedVersion: prior?._v ?? 0 }\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n const prepared = await coll._preparePut(op.id, op.record, op.reason !== undefined ? { reason: op.reason } : undefined)\n legs.push({ op, coll, prepared, txOp: { type: 'put', ...base, envelope: prepared.envelope } })\n } else {\n const prepared = await coll._prepareDelete(op.id, false)\n if (prepared === null) {\n // Nothing to delete (no live record / already a marker / shredded) —\n // the case `_doDelete` answers with `false`. Drop the op and its\n // revert entry rather than sending a leg for it.\n ctx._executed.pop()\n continue\n }\n legs.push({\n op, coll, prepared,\n // Under sync the delete is a marker PUT at `live._v + 1` (#589);\n // without sync it is a physical removal.\n txOp: prepared.marker ? { type: 'put', ...base, envelope: prepared.marker } : { type: 'delete', ...base },\n })\n }\n }\n\n // Every op turned out to be a no-op delete — same as the OCC loop, which\n // would have written nothing either. Don't hand the store an empty batch.\n if (legs.length === 0) return\n\n // ─── The batch ─────────────────────────────────────────────────\n // Deliberately NOT inside a revert-guarded try: a rejection means the store\n // applied nothing (all-or-nothing is the capability we gated on), so there\n // is nothing to unwind and a revert pass would write over records this\n // transaction never touched. No ledger entry, history snapshot or change\n // event has fired either — every one of those lives in finalize below. The\n // error (a `ConflictError` when a leg lost its CAS) surfaces unwrapped.\n await store.tx!(legs.map(l => l.txOp))\n\n // ─── Finalize ──────────────────────────────────────────────────\n // History snapshot, ledger entry, cache/index update, change event — per op,\n // in staged order, all AFTER the bytes are durable (where the OCC loop\n // interleaves them per op). #931: then the after-write observers (user\n // `onAfterWrite` hooks + the `afterPut`/`afterDelete` observe bus), which\n // no longer gate eligibility — they cannot refuse a completed write.\n try {\n for (const leg of legs) {\n if (leg.op.type === 'put') await leg.coll._finalizePut(leg.prepared as PreparedPut<unknown>)\n else await leg.coll._finalizeDelete(leg.prepared as PreparedDelete<unknown>)\n await leg.coll._fireAtomicAfterWrite(leg.op.type, leg.prepared)\n }\n } catch (err) {\n // The bytes ARE durable now, so this is today's best-effort unwind over\n // the plan recorded above. No amendment window to drain — the gate\n // excludes amendment transactions.\n await revertExecuted(ctx._executed, store, db)\n throw err\n }\n}\n\n/**\n * Phase 3 helper — restore captured prior envelopes via the raw store\n * to avoid re-firing Collection-level side effects (we don't want a\n * cascade of change events undoing themselves). The ledger is left\n * as-is: each committed op appended an entry; the revert is\n * deliberately NOT recorded as a compensating entry because the\n * caller-facing contract is \"atomic or not at all,\" not \"every write\n * visible in the audit trail.\" Auditors who need the intermediate\n * state can still reconstruct it by walking the ledger through the\n * failed-tx timestamp.\n *\n * Delegates the reverse/best-effort/raw-revert shape to the shared\n * `bestEffortRevert` helper (`kernel/best-effort-revert.ts`); the cache\n * invalidation below rides along as that helper's per-leg `compensate`\n * callback, gated on `db` exactly as before.\n *\n * @internal — shared between `runTransaction` and\n * `Collection.putManyAtomic`. Both register source ops + nested\n * derivation side-effect ops onto `_executed`; this helper unwinds the\n * combined list in reverse on rollback.\n */\nexport async function revertExecuted(\n executed: ReadonlyArray<ExecutedOp>,\n store: Noydb['_store'],\n db?: Noydb,\n): Promise<void> {\n const legs = executed.map(({ op, priorEnvelope }) => ({\n vaultName: op.vaultName,\n collectionName: op.collectionName,\n id: op.id,\n prior: priorEnvelope,\n }))\n await bestEffortRevert(\n legs,\n store,\n db\n ? async (leg) => {\n // Sync the Collection-layer cache with what we just wrote at\n // the raw store. Without this, eager-mode `get` would still\n // return the rolled-back record from its in-memory map. The\n // Collection's `_invalidateCacheEntry` is a no-op when the\n // collection hasn't yet been hydrated.\n const coll = db.vault(leg.vaultName).collection(leg.collectionName)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (coll as any)._invalidateCacheEntry(leg.id)\n }\n : undefined,\n )\n}\n\n/** @internal — shared (vault, collection, id) key shape; also used by `atomic-eligibility.ts`'s duplicate-key scan. */\nexport function keyOf(op: StagedOp): string {\n return `${op.vaultName}\\x00${op.collectionName}\\x00${op.id}`\n}\n"],"mappings":"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","names":[]}
@@ -1,95 +0,0 @@
1
- import {
2
- base64ToBuffer,
3
- decryptBytesWithAAD,
4
- encryptBytesWithAAD
5
- } from "./chunk-HPVZCW43.js";
6
- import {
7
- TamperedError
8
- } from "./chunk-7O3Y4PPD.js";
9
-
10
- // src/kernel/enclave/classify/compare.ts
11
- var subtle = globalThis.crypto.subtle;
12
- function ctEqualTags(a, b) {
13
- if (a.length !== 32 || b.length !== 32) {
14
- throw new Error(
15
- `ctEqualTags: tags must be exactly 32 bytes (got ${a.length}/${b.length}) \u2014 caller bug; reduce comparands with blindedEqual first`
16
- );
17
- }
18
- let diff = 0;
19
- for (let i = 0; i < 32; i++) diff |= a[i] ^ b[i];
20
- return diff === 0;
21
- }
22
- async function blindedEqual(a, b) {
23
- const ke = await subtle.generateKey({ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
24
- const tagA = new Uint8Array(await subtle.sign("HMAC", ke, a));
25
- const tagB = new Uint8Array(await subtle.sign("HMAC", ke, b));
26
- return ctEqualTags(tagA, tagB);
27
- }
28
-
29
- // src/kernel/enclave/classify/vdig.ts
30
- var subtle2 = globalThis.crypto.subtle;
31
- var VDIG_SALT_DOMAIN = "noydb-classify-vdig";
32
- function buildVdigAad(collection, recordId, field) {
33
- return new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, recordId, field]));
34
- }
35
- async function deriveVdigSlotKey(cek, collection, field) {
36
- const raw = await subtle2.exportKey("raw", cek);
37
- const hkdf = await subtle2.importKey("raw", raw, "HKDF", false, ["deriveBits"]);
38
- const salt = new TextEncoder().encode(VDIG_SALT_DOMAIN);
39
- const info = new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, field]));
40
- const bits = await subtle2.deriveBits({ name: "HKDF", hash: "SHA-256", salt, info }, hkdf, 256);
41
- return subtle2.importKey("raw", bits, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
42
- }
43
- async function sealVdigPayload(payload, cek, collection, recordId, field) {
44
- const key = await deriveVdigSlotKey(cek, collection, field);
45
- const { iv, data } = await encryptBytesWithAAD(
46
- new TextEncoder().encode(JSON.stringify(payload)),
47
- key,
48
- buildVdigAad(collection, recordId, field)
49
- );
50
- return `${iv}:${data}`;
51
- }
52
- async function openVdigPayload(blob, cek, collection, recordId, field) {
53
- const sep = blob.indexOf(":");
54
- if (sep === -1) throw new TamperedError();
55
- const ivPart = blob.slice(0, sep);
56
- const dataPart = blob.slice(sep + 1);
57
- try {
58
- base64ToBuffer(ivPart);
59
- base64ToBuffer(dataPart);
60
- } catch {
61
- throw new TamperedError();
62
- }
63
- const key = await deriveVdigSlotKey(cek, collection, field);
64
- const bytes = await decryptBytesWithAAD(
65
- ivPart,
66
- dataPart,
67
- key,
68
- buildVdigAad(collection, recordId, field)
69
- );
70
- return JSON.parse(new TextDecoder().decode(bytes));
71
- }
72
-
73
- // src/kernel/enclave/classify/kofn.ts
74
- function evaluateKofN(results, min) {
75
- if (!Number.isInteger(min) || min < 1 || min > results.length) {
76
- throw new Error(
77
- `evaluateKofN: min ${min} out of range 1..${results.length} \u2014 caller bug (matchGroup validates bounds up front, before any PBKDF2)`
78
- );
79
- }
80
- let count = 0;
81
- for (const r of results) {
82
- if (r) count += 1;
83
- }
84
- return count >= min;
85
- }
86
-
87
- export {
88
- ctEqualTags,
89
- blindedEqual,
90
- deriveVdigSlotKey,
91
- sealVdigPayload,
92
- openVdigPayload,
93
- evaluateKofN
94
- };
95
- //# sourceMappingURL=chunk-BRLKZUBT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/enclave/classify/compare.ts","../src/kernel/enclave/classify/vdig.ts","../src/kernel/enclave/classify/kofn.ts"],"sourcesContent":["/**\n * C2 — fixed-length-tag constant-time comparison (hub-portable; no\n * Node timingSafeEqual). Two mandatory rules (spec §3):\n * 1. Only fixed 32-byte tags are ever compared — ctEqualTags throws on\n * anything else (tag length is structural, never secret-dependent).\n * 2. Every comparand is reduced to a 32-byte tag under a FRESH ephemeral\n * HMAC-SHA256 key before comparison — keyed blinding makes compare\n * timing uncorrelated with underlying values; rule 1 (not blinding)\n * is what removes input-length timing. Length inequality folds into\n * unequal tags — never an early return.\n * @module\n */\nconst subtle = globalThis.crypto.subtle\n\n/** Compare exactly-32-byte tags. XOR-accumulate over all 32 bytes, no early exit. */\nexport function ctEqualTags(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== 32 || b.length !== 32) {\n throw new Error(\n `ctEqualTags: tags must be exactly 32 bytes (got ${a.length}/${b.length}) — caller bug; ` +\n `reduce comparands with blindedEqual first`,\n )\n }\n let diff = 0\n for (let i = 0; i < 32; i++) diff |= (a[i]! ^ b[i]!)\n return diff === 0\n}\n\n/**\n * Blinded equality of arbitrary-length byte strings: fresh K_e per\n * comparison (never stored, never reused), HMAC both sides to 32-byte\n * tags, then ctEqualTags. On the digest path the inputs are already\n * 32-byte PBKDF2 outputs but still route through this reduction so\n * there is exactly ONE comparison construction (spec §3 rule 2).\n */\nexport async function blindedEqual(a: Uint8Array, b: Uint8Array): Promise<boolean> {\n const ke = await subtle.generateKey({ name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const tagA = new Uint8Array(await subtle.sign('HMAC', ke, a as BufferSource))\n const tagB = new Uint8Array(await subtle.sign('HMAC', ke, b as BufferSource))\n return ctEqualTags(tagA, tagB)\n}\n","/**\n * The `_vdig` slot payload + crypto (stage 2, spec §2).\n *\n * A `_vdig[field]` blob is AES-256-GCM `\"iv:data\"` sealed under an\n * HKDF(CEK) slot key with AAD = ['noydb-classify-vdig', collection,\n * recordId, field] (C1 rollback-splice hardening). The AAD is NOT stored —\n * readers reconstruct it. It is deliberately `_v`-independent: C6's\n * carry-forward copies blob bytes verbatim across version bumps; the\n * same-record same-field TEMPORAL rollback residual is detected by the\n * ledger's conditional `_vdig` binding (envelope-body.ts).\n *\n * CEK-ONLY (I3): there is no DEK derivation and no\n * 'noydb-classify-vdig-dek' salt domain — every vdig slot dies with the\n * record's `_cek` (forget() shreds it totally; no vdig-dekResidue class).\n * @module\n */\nimport { encryptBytesWithAAD, decryptBytesWithAAD, base64ToBuffer, type EnclaveKey } from '../crypto.js'\nimport { TamperedError } from '../../errors.js'\n\nconst subtle = globalThis.crypto.subtle\n\nexport const VDIG_SALT_DOMAIN = 'noydb-classify-vdig'\n\nexport interface VdigDigestEntry {\n readonly salt: string // base64 32-byte per-write random salt\n readonly hash: string // base64 32-byte pbkdf2VerifyDigest output\n}\n\nexport interface VdigPayload {\n readonly v: 1\n readonly alg: 'PBKDF2-SHA256'\n readonly iter: number\n readonly cur: VdigDigestEntry & { readonly at: string } // ISO write-time\n /** Previous digests, oldest-first, length ≤ notLastN (cap 8). */\n readonly ring?: readonly VdigDigestEntry[]\n}\n\n/** Injective JSON-array AAD — same collision argument as deriveSealedFieldKey. */\nexport function buildVdigAad(collection: string, recordId: string, field: string): Uint8Array {\n return new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, recordId, field]))\n}\n\n/** HKDF(CEK) → non-extractable AES-256-GCM vdig slot key. CEK-only (I3). */\nexport async function deriveVdigSlotKey(\n cek: EnclaveKey,\n collection: string,\n field: string,\n): Promise<EnclaveKey> {\n const raw = await subtle.exportKey('raw', cek)\n const hkdf = await subtle.importKey('raw', raw, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode(VDIG_SALT_DOMAIN)\n const info = new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, field]))\n const bits = await subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info }, hkdf, 256)\n return subtle.importKey('raw', bits, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])\n}\n\nexport async function sealVdigPayload(\n payload: VdigPayload,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<string> {\n const key = await deriveVdigSlotKey(cek, collection, field)\n const { iv, data } = await encryptBytesWithAAD(\n new TextEncoder().encode(JSON.stringify(payload)),\n key,\n buildVdigAad(collection, recordId, field),\n )\n return `${iv}:${data}`\n}\n\n/** Throws TamperedError on any AAD / auth-tag mismatch (C1). */\nexport async function openVdigPayload(\n blob: string,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<VdigPayload> {\n const sep = blob.indexOf(':')\n if (sep === -1) throw new TamperedError()\n const ivPart = blob.slice(0, sep)\n const dataPart = blob.slice(sep + 1)\n try {\n base64ToBuffer(ivPart)\n base64ToBuffer(dataPart)\n } catch {\n throw new TamperedError()\n }\n const key = await deriveVdigSlotKey(cek, collection, field)\n const bytes = await decryptBytesWithAAD(\n ivPart,\n dataPart,\n key,\n buildVdigAad(collection, recordId, field),\n )\n return JSON.parse(new TextDecoder().decode(bytes)) as VdigPayload\n}\n","/**\n * k-of-n aggregate for matchGroup (§3). Deliberately a full scan that\n * never breaks: per-member results must never influence control flow in\n * a way an observer could time or decompose. Returns ONLY the aggregate.\n * @module\n */\nexport function evaluateKofN(results: readonly boolean[], min: number): boolean {\n if (!Number.isInteger(min) || min < 1 || min > results.length) {\n throw new Error(\n `evaluateKofN: min ${min} out of range 1..${results.length} — caller bug ` +\n `(matchGroup validates bounds up front, before any PBKDF2)`,\n )\n }\n let count = 0\n for (const r of results) {\n if (r) count += 1 // no short-circuit — collect, never break\n }\n return count >= min\n}\n"],"mappings":"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","names":["subtle"]}