@noy-db/hub 0.8.0-pre.0 → 0.9.0-pre.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 (706) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +8 -8
  3. package/codemods/0.4.0-pre.json +1 -1
  4. package/codemods/0.8.0-pre.json +1 -1
  5. package/dist/api-MSJXVYQG.js +18 -0
  6. package/dist/as/index.js +34 -25
  7. package/dist/as/index.js.map +1 -1
  8. package/dist/at/index.js +9 -10
  9. package/dist/attestation/index.js +39 -28
  10. package/dist/attestation/index.js.map +1 -1
  11. package/dist/{backup-7TFRVFLH.js → backup-7RKNZMQO.js} +15 -16
  12. package/dist/backup-7RKNZMQO.js.map +1 -0
  13. package/dist/blobs/index.js +13 -14
  14. package/dist/blobs/index.js.map +1 -1
  15. package/dist/broker/index.js +20 -13
  16. package/dist/broker/index.js.map +1 -1
  17. package/dist/by/index.js +2 -2
  18. package/dist/capsule/contract.d.ts +159 -0
  19. package/dist/capsule/contract.js +95 -0
  20. package/dist/capsule/enclave-aes/bound.d.ts +52 -0
  21. package/dist/{kernel/enclave → capsule/enclave-aes}/broker/proof.d.ts +12 -3
  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 +361 -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 +62 -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-57BDK7LG.js +209 -0
  50. package/dist/cargo/index.js +36 -36
  51. package/dist/{chunk-BUDAGWL5.js → chunk-226V6XXJ.js} +41 -6
  52. package/dist/chunk-226V6XXJ.js.map +1 -0
  53. package/dist/{chunk-QWGU66MP.js → chunk-2A56DSRI.js} +5 -7
  54. package/dist/chunk-2A56DSRI.js.map +1 -0
  55. package/dist/chunk-2DX4JCRI.js +1 -0
  56. package/dist/{chunk-ODVPTDPK.js → chunk-2ELOCEXE.js} +1 -1
  57. package/dist/chunk-2ELOCEXE.js.map +1 -0
  58. package/dist/{chunk-CKNGS5MH.js → chunk-2WJKJZSO.js} +3 -5
  59. package/dist/chunk-2WJKJZSO.js.map +1 -0
  60. package/dist/{chunk-ZSX4S5XB.js → chunk-2ZFD2UJC.js} +9 -9
  61. package/dist/{chunk-YSK2CU2Q.js → chunk-3V6LZHUO.js} +3 -3
  62. package/dist/chunk-3V6LZHUO.js.map +1 -0
  63. package/dist/{chunk-HT2AHGQI.js → chunk-3Y5JLSXS.js} +2 -2
  64. package/dist/{chunk-M7NI3IWE.js → chunk-45VHWE4C.js} +67 -60
  65. package/dist/chunk-45VHWE4C.js.map +1 -0
  66. package/dist/{chunk-LGNDFRYC.js → chunk-4CFHL5RY.js} +1 -1
  67. package/dist/chunk-4CFHL5RY.js.map +1 -0
  68. package/dist/{enclave-27SMF6GF.js → chunk-4J3MNVVX.js} +83 -56
  69. package/dist/chunk-4J3MNVVX.js.map +1 -0
  70. package/dist/{chunk-SI3V7D4G.js → chunk-4UIP3QNW.js} +2 -2
  71. package/dist/{chunk-WNJ4AVGE.js → chunk-55P6OBB7.js} +2 -2
  72. package/dist/{chunk-6LNL42F5.js → chunk-5HNW4ZTA.js} +12 -15
  73. package/dist/chunk-5HNW4ZTA.js.map +1 -0
  74. package/dist/{chunk-SYVZW77S.js → chunk-5LMCVBMY.js} +6 -8
  75. package/dist/chunk-5LMCVBMY.js.map +1 -0
  76. package/dist/{chunk-MKXXMNJH.js → chunk-5UUJE4FP.js} +3 -3
  77. package/dist/chunk-5XONHAFD.js +12 -0
  78. package/dist/chunk-5XONHAFD.js.map +1 -0
  79. package/dist/{chunk-X4773RBR.js → chunk-6QLCS7ET.js} +2 -2
  80. package/dist/chunk-6QLCS7ET.js.map +1 -0
  81. package/dist/{chunk-FW22ZCXT.js → chunk-6WS35E6X.js} +2 -2
  82. package/dist/{chunk-FCKWQE2H.js → chunk-6Y5E4SJU.js} +7 -8
  83. package/dist/chunk-6Y5E4SJU.js.map +1 -0
  84. package/dist/{chunk-4AF3TUMP.js → chunk-72XBVN4J.js} +8 -2
  85. package/dist/{chunk-4AF3TUMP.js.map → chunk-72XBVN4J.js.map} +1 -1
  86. package/dist/{chunk-QBBUZ3I3.js → chunk-7BMOS4JQ.js} +11 -11
  87. package/dist/chunk-7BMOS4JQ.js.map +1 -0
  88. package/dist/{chunk-5FDLC6G7.js → chunk-7CT6SX4M.js} +3 -3
  89. package/dist/chunk-7CT6SX4M.js.map +1 -0
  90. package/dist/{chunk-LQ4ZHOKP.js → chunk-ABU6A4Z5.js} +36 -27
  91. package/dist/chunk-ABU6A4Z5.js.map +1 -0
  92. package/dist/{chunk-WX5ADGTZ.js → chunk-AJFPAHMW.js} +3 -3
  93. package/dist/chunk-AJFPAHMW.js.map +1 -0
  94. package/dist/{chunk-ZE4CHIED.js → chunk-APFFJN5T.js} +39 -37
  95. package/dist/chunk-APFFJN5T.js.map +1 -0
  96. package/dist/{chunk-6OWXYZK2.js → chunk-B65CC6Z6.js} +2 -2
  97. package/dist/{chunk-F2TDU7MQ.js → chunk-BJH77UY2.js} +2 -2
  98. package/dist/{chunk-L7DOS3TE.js → chunk-BMHQHH7Q.js} +3 -3
  99. package/dist/chunk-BMHQHH7Q.js.map +1 -0
  100. package/dist/{chunk-BJ2IXQ5Q.js → chunk-BNFNQNEP.js} +2 -2
  101. package/dist/chunk-BU2ABHFW.js +74 -0
  102. package/dist/chunk-BU2ABHFW.js.map +1 -0
  103. package/dist/{chunk-HLEK5VAN.js → chunk-BUE7R3B5.js} +2 -2
  104. package/dist/{chunk-ISZQJBML.js → chunk-BVF3SXD7.js} +6 -6
  105. package/dist/chunk-BVF3SXD7.js.map +1 -0
  106. package/dist/{chunk-M522UYZ2.js → chunk-BYHCKX73.js} +6 -10
  107. package/dist/chunk-BYHCKX73.js.map +1 -0
  108. package/dist/{chunk-EOVMT6TB.js → chunk-C53ER7AG.js} +2 -2
  109. package/dist/{chunk-K6W7BVZ6.js → chunk-CAD7N6HQ.js} +3 -3
  110. package/dist/chunk-CB4467G3.js +565 -0
  111. package/dist/chunk-CB4467G3.js.map +1 -0
  112. package/dist/{chunk-NWHBJQAL.js → chunk-D4V5JJAK.js} +4 -4
  113. package/dist/chunk-D4V5JJAK.js.map +1 -0
  114. package/dist/{chunk-QWFHIOD2.js → chunk-D5A3GCZ4.js} +11 -12
  115. package/dist/chunk-D5A3GCZ4.js.map +1 -0
  116. package/dist/{chunk-5O7D6ZTW.js → chunk-DHU2AQ6T.js} +2 -2
  117. package/dist/{chunk-DC5QVPV7.js → chunk-DL6BZKP3.js} +3 -3
  118. package/dist/chunk-DL6BZKP3.js.map +1 -0
  119. package/dist/{chunk-FQ54DWUQ.js → chunk-EA4VDI5D.js} +4 -4
  120. package/dist/chunk-EA4VDI5D.js.map +1 -0
  121. package/dist/{chunk-IT22PWKV.js → chunk-ECGXA4LT.js} +2 -2
  122. package/dist/{chunk-BP6DARYA.js → chunk-EMAWOGVH.js} +4 -4
  123. package/dist/chunk-EMAWOGVH.js.map +1 -0
  124. package/dist/{chunk-3F7GLNAE.js → chunk-EUD3HQM4.js} +7 -11
  125. package/dist/chunk-EUD3HQM4.js.map +1 -0
  126. package/dist/{chunk-T2LBQ5DV.js → chunk-FSQNGPHH.js} +335 -250
  127. package/dist/chunk-FSQNGPHH.js.map +1 -0
  128. package/dist/{chunk-7VCXZDA4.js → chunk-FZXRLNJO.js} +7 -11
  129. package/dist/chunk-FZXRLNJO.js.map +1 -0
  130. package/dist/{chunk-6LMQ5R26.js → chunk-G46JSSVX.js} +7 -7
  131. package/dist/{chunk-6LMQ5R26.js.map → chunk-G46JSSVX.js.map} +1 -1
  132. package/dist/{chunk-GF5BZ7D4.js → chunk-G5EDYVSE.js} +11 -13
  133. package/dist/chunk-G5EDYVSE.js.map +1 -0
  134. package/dist/chunk-G66YXUTY.js +60 -0
  135. package/dist/chunk-G66YXUTY.js.map +1 -0
  136. package/dist/{chunk-FFWI7UH7.js → chunk-GAGT3YZN.js} +1 -1
  137. package/dist/chunk-GAGT3YZN.js.map +1 -0
  138. package/dist/{chunk-4MXI3ODD.js → chunk-GCUMOKTN.js} +3 -3
  139. package/dist/{chunk-FH2L2E34.js → chunk-GETUTTZU.js} +4 -4
  140. package/dist/{chunk-FH2L2E34.js.map → chunk-GETUTTZU.js.map} +1 -1
  141. package/dist/chunk-GMKGGY7V.js +234 -0
  142. package/dist/chunk-GMKGGY7V.js.map +1 -0
  143. package/dist/{chunk-B6SJIBQK.js → chunk-GOJQ5DZA.js} +4 -4
  144. package/dist/{chunk-EIPVYUEP.js → chunk-GQ4MF3CM.js} +3 -3
  145. package/dist/chunk-GQ4MF3CM.js.map +1 -0
  146. package/dist/{chunk-PDCG6PR5.js → chunk-GRS2HIV6.js} +2 -2
  147. package/dist/chunk-GRS2HIV6.js.map +1 -0
  148. package/dist/{chunk-PEOZ34EM.js → chunk-GSAK56VF.js} +1 -1
  149. package/dist/chunk-GSAK56VF.js.map +1 -0
  150. package/dist/{chunk-3H5KLDZN.js → chunk-H3YM66EI.js} +4 -8
  151. package/dist/chunk-H3YM66EI.js.map +1 -0
  152. package/dist/{chunk-NRT6QZQB.js → chunk-IVMFIKNA.js} +6 -6
  153. package/dist/chunk-IVMFIKNA.js.map +1 -0
  154. package/dist/{chunk-PSIV7CJV.js → chunk-J4KAPKBJ.js} +2 -2
  155. package/dist/chunk-J4KAPKBJ.js.map +1 -0
  156. package/dist/{chunk-N5DNADLQ.js → chunk-JA25QG5M.js} +224 -34
  157. package/dist/chunk-JA25QG5M.js.map +1 -0
  158. package/dist/{chunk-T7IR4N2S.js → chunk-JBMMWYOH.js} +3 -1
  159. package/dist/{chunk-T7IR4N2S.js.map → chunk-JBMMWYOH.js.map} +1 -1
  160. package/dist/{chunk-YNIUIERI.js → chunk-JPSL5GIZ.js} +18 -59
  161. package/dist/chunk-JPSL5GIZ.js.map +1 -0
  162. package/dist/{chunk-TKUOEU2F.js → chunk-JSWKVZCR.js} +2 -2
  163. package/dist/chunk-JZ4EDEIP.js +22 -0
  164. package/dist/chunk-JZ4EDEIP.js.map +1 -0
  165. package/dist/{chunk-QOOUUR4A.js → chunk-K673OO6B.js} +10 -4
  166. package/dist/chunk-K673OO6B.js.map +1 -0
  167. package/dist/{chunk-NNARWO72.js → chunk-K7RW7FI4.js} +13 -12
  168. package/dist/chunk-K7RW7FI4.js.map +1 -0
  169. package/dist/{chunk-ZXABPVM6.js → chunk-L2F3CFCH.js} +9 -13
  170. package/dist/chunk-L2F3CFCH.js.map +1 -0
  171. package/dist/{chunk-X25NWUZB.js → chunk-L5A3KR7R.js} +3 -3
  172. package/dist/{chunk-T52GX4SA.js → chunk-LGGIFKJP.js} +4 -4
  173. package/dist/{chunk-ID6Z6E66.js → chunk-MQ7YWBQ5.js} +2 -2
  174. package/dist/{chunk-FXQBGFOX.js → chunk-MRJDLXI3.js} +14 -8
  175. package/dist/{chunk-FXQBGFOX.js.map → chunk-MRJDLXI3.js.map} +1 -1
  176. package/dist/chunk-NH2UOV3B.js +18 -0
  177. package/dist/chunk-NH2UOV3B.js.map +1 -0
  178. package/dist/{chunk-FN5EBVTQ.js → chunk-NIXRB5BP.js} +2 -2
  179. package/dist/{chunk-RHVMZSUU.js → chunk-NV4OESHC.js} +1 -1
  180. package/dist/chunk-NV4OESHC.js.map +1 -0
  181. package/dist/{chunk-HBRLNOHL.js → chunk-NYJBYVBG.js} +4 -4
  182. package/dist/{chunk-DF73H4UN.js → chunk-NZULV7LK.js} +5 -5
  183. package/dist/chunk-NZULV7LK.js.map +1 -0
  184. package/dist/{chunk-HK67PX4J.js → chunk-OFD2HD6F.js} +1 -1
  185. package/dist/chunk-OFD2HD6F.js.map +1 -0
  186. package/dist/{chunk-WY2XXTET.js → chunk-OJU67C23.js} +4 -4
  187. package/dist/{chunk-WY2XXTET.js.map → chunk-OJU67C23.js.map} +1 -1
  188. package/dist/{chunk-ZUVBC32X.js → chunk-OK57EATL.js} +2 -2
  189. package/dist/{chunk-KJFW62P4.js → chunk-OM3SEDI4.js} +652 -662
  190. package/dist/chunk-OM3SEDI4.js.map +1 -0
  191. package/dist/{chunk-TOPYQ6GA.js → chunk-PCYPXLEX.js} +6 -6
  192. package/dist/{chunk-GYGLAVCX.js → chunk-PFCTSXHU.js} +6 -2
  193. package/dist/chunk-PFCTSXHU.js.map +1 -0
  194. package/dist/{chunk-VNNRUGMM.js → chunk-PGC35KXE.js} +2 -2
  195. package/dist/{chunk-4I522ERM.js → chunk-PT2YCQVK.js} +4 -4
  196. package/dist/{chunk-Z5HTYMMQ.js → chunk-PTJY5R3O.js} +2 -2
  197. package/dist/chunk-PUBI455O.js +157 -0
  198. package/dist/chunk-PUBI455O.js.map +1 -0
  199. package/dist/{chunk-MTL7MXGR.js → chunk-QDHHHI5Q.js} +16 -17
  200. package/dist/chunk-QDHHHI5Q.js.map +1 -0
  201. package/dist/{chunk-F24ZZTHQ.js → chunk-QGFEBNHO.js} +4 -4
  202. package/dist/{chunk-A6IJU4TK.js → chunk-QGSNJD3B.js} +2 -2
  203. package/dist/chunk-QJNAHPVT.js +91 -0
  204. package/dist/chunk-QJNAHPVT.js.map +1 -0
  205. package/dist/{chunk-BK2HHQGN.js → chunk-QSHATAXJ.js} +2 -2
  206. package/dist/{chunk-ANWPSTZU.js → chunk-QXJXG7DN.js} +57 -30
  207. package/dist/chunk-QXJXG7DN.js.map +1 -0
  208. package/dist/{chunk-HBPPGWUM.js → chunk-RDOEOPOI.js} +14 -9
  209. package/dist/chunk-RDOEOPOI.js.map +1 -0
  210. package/dist/{chunk-JCN44NCY.js → chunk-RIZTZJX6.js} +2 -2
  211. package/dist/{chunk-MVZ2TPVP.js → chunk-RNACRT5H.js} +2 -2
  212. package/dist/chunk-RWFQVNXR.js +24 -0
  213. package/dist/chunk-RWFQVNXR.js.map +1 -0
  214. package/dist/{chunk-TROR3ALR.js → chunk-SFCD6OU7.js} +2 -2
  215. package/dist/{chunk-S57HTZ6X.js → chunk-SNM7LSYG.js} +5 -8
  216. package/dist/chunk-SNM7LSYG.js.map +1 -0
  217. package/dist/chunk-SPPOUJLN.js +40 -0
  218. package/dist/chunk-SPPOUJLN.js.map +1 -0
  219. package/dist/{chunk-3ELRMVJK.js → chunk-SWXLZ4HX.js} +31 -35
  220. package/dist/chunk-SWXLZ4HX.js.map +1 -0
  221. package/dist/{chunk-6B3VNR4P.js → chunk-TFWFPZRU.js} +26 -35
  222. package/dist/chunk-TFWFPZRU.js.map +1 -0
  223. package/dist/{chunk-F7XGHL5O.js → chunk-TMIJAR2X.js} +3 -3
  224. package/dist/{chunk-F7XGHL5O.js.map → chunk-TMIJAR2X.js.map} +1 -1
  225. package/dist/{chunk-I3JDRCMW.js → chunk-TZYTMZZY.js} +10 -10
  226. package/dist/{chunk-AOWGXU6D.js → chunk-UAKY6E4N.js} +2 -2
  227. package/dist/chunk-UHFGK6YA.js +40 -0
  228. package/dist/chunk-UHFGK6YA.js.map +1 -0
  229. package/dist/{chunk-3JWHAR6E.js → chunk-UNSQZSZP.js} +2 -2
  230. package/dist/{chunk-FQ5VRHIB.js → chunk-UOZOWN73.js} +5 -9
  231. package/dist/chunk-UOZOWN73.js.map +1 -0
  232. package/dist/{chunk-GHLTDZRS.js → chunk-V55JASXO.js} +25 -4
  233. package/dist/chunk-V55JASXO.js.map +1 -0
  234. package/dist/{chunk-244VXYPG.js → chunk-VSUQI5Q6.js} +45 -4
  235. package/dist/chunk-VSUQI5Q6.js.map +1 -0
  236. package/dist/{chunk-6K7DPM7D.js → chunk-WFWFXDNS.js} +2 -2
  237. package/dist/{chunk-CF5UU4S7.js → chunk-WHOKGHJ3.js} +7 -7
  238. package/dist/{chunk-LGRLME4M.js → chunk-X2RP4TO5.js} +16 -16
  239. package/dist/chunk-X2RP4TO5.js.map +1 -0
  240. package/dist/{chunk-VNPEWFPD.js → chunk-XA34THLF.js} +8 -6
  241. package/dist/chunk-XA34THLF.js.map +1 -0
  242. package/dist/{chunk-HVSCEIYL.js → chunk-XITHENBI.js} +12 -14
  243. package/dist/chunk-XITHENBI.js.map +1 -0
  244. package/dist/{chunk-ENAG3IY7.js → chunk-XKBTJGFP.js} +26 -28
  245. package/dist/chunk-XKBTJGFP.js.map +1 -0
  246. package/dist/{chunk-32YV3TJT.js → chunk-XXNWU5QZ.js} +2 -2
  247. package/dist/{chunk-B57AFBY2.js → chunk-XXQ5XIYY.js} +2 -2
  248. package/dist/{chunk-ORDURZBU.js → chunk-YFFMPZPY.js} +2 -2
  249. package/dist/{chunk-MDWKM2XC.js → chunk-YKGJ44Z7.js} +3 -3
  250. package/dist/{chunk-IUWCVHQ7.js → chunk-YQFBH5ZJ.js} +4 -4
  251. package/dist/{chunk-IUWCVHQ7.js.map → chunk-YQFBH5ZJ.js.map} +1 -1
  252. package/dist/{chunk-UPD5NWI5.js → chunk-Z3ZXVB2Y.js} +29 -16
  253. package/dist/chunk-Z3ZXVB2Y.js.map +1 -0
  254. package/dist/chunk-ZBUAX4EF.js +27 -0
  255. package/dist/chunk-ZBUAX4EF.js.map +1 -0
  256. package/dist/{chunk-MLPHLJIQ.js → chunk-ZHQJAV6D.js} +2 -2
  257. package/dist/{chunk-UD5EVGZ7.js → chunk-ZMVSKYTJ.js} +56 -96
  258. package/dist/chunk-ZMVSKYTJ.js.map +1 -0
  259. package/dist/{chunk-7JE7BDYJ.js → chunk-ZVSKSUGI.js} +5 -5
  260. package/dist/{chunk-J6UOHJY5.js → chunk-ZWF3EWZA.js} +4 -8
  261. package/dist/chunk-ZWF3EWZA.js.map +1 -0
  262. package/dist/classified/index.js +5 -5
  263. package/dist/{classified-marker-W3USLL5M.js → classified-marker-KHDOYIJB.js} +3 -3
  264. package/dist/classified-marker-KHDOYIJB.js.map +1 -0
  265. package/dist/collection-facade-LZIF7GWD.js +41 -0
  266. package/dist/{computed-TPFBX7U4.js → computed-XMFTHAXV.js} +3 -3
  267. package/dist/consent/index.js +10 -11
  268. package/dist/consent/index.js.map +1 -1
  269. package/dist/cover/index.js +9 -10
  270. package/dist/coverage/index.js +1 -1
  271. package/dist/custody/index.js +13 -13
  272. package/dist/{dead-filter-U2UYKSZL.js → dead-filter-CXV5NS67.js} +11 -3
  273. package/dist/dead-filter-CXV5NS67.js.map +1 -0
  274. package/dist/debug/index.js +11 -2
  275. package/dist/debug/index.js.map +1 -1
  276. package/dist/{delegation-S3AEPW2T.js → delegation-ONLM4JFI.js} +52 -31
  277. package/dist/delegation-ONLM4JFI.js.map +1 -0
  278. package/dist/derivations/index.js +25 -25
  279. package/dist/derive-3YSIS36W.js +20 -0
  280. package/dist/directory/index.js +10 -11
  281. package/dist/{dispatch-RH4LHKBH.js → dispatch-2MQFMEGY.js} +22 -23
  282. package/dist/dispatch-2MQFMEGY.js.map +1 -0
  283. package/dist/{dispatch-3CQDJV5Q.js → dispatch-OFDA4WQV.js} +5 -5
  284. package/dist/executor-E6YDJ7YA.js +8 -0
  285. package/dist/executor-NJX3YQQ4.js +35 -0
  286. package/dist/executor-WWM3DD4P.js +8 -0
  287. package/dist/export-accessible-RLHU5G4W.js +22 -0
  288. package/dist/extract-partition-QII2EDCS.js +35 -0
  289. package/dist/{fanout-sidecar-OT6RYLOL.js → fanout-sidecar-BO67PCPO.js} +13 -15
  290. package/dist/fanout-sidecar-BO67PCPO.js.map +1 -0
  291. package/dist/find-6M7GYDUZ.js +10 -0
  292. package/dist/forget/index.js +26 -10
  293. package/dist/forget/index.js.map +1 -1
  294. package/dist/guards/index.js +4 -4
  295. package/dist/history/index.js +13 -14
  296. package/dist/history/index.js.map +1 -1
  297. package/dist/i18n/index.js +14 -15
  298. package/dist/i18n/index.js.map +1 -1
  299. package/dist/index.d.ts +15 -13
  300. package/dist/index.js +198 -187
  301. package/dist/index.js.map +1 -1
  302. package/dist/indexing/index.js +6 -6
  303. package/dist/introspection/index.js +16 -13
  304. package/dist/issue-LPEEEMPP.js +19 -0
  305. package/dist/kernel/attestation-types.d.ts +62 -0
  306. package/dist/kernel/cache/index.d.ts +1 -1
  307. package/dist/kernel/collection-config.d.ts +1 -48
  308. package/dist/kernel/collection.d.ts +2 -1
  309. package/dist/kernel/cutover-transform.d.ts +1 -1
  310. package/dist/kernel/errors.d.ts +63 -6
  311. package/dist/kernel/export-scope.d.ts +40 -0
  312. package/dist/kernel/merge-authority.d.ts +1 -1
  313. package/dist/kernel/noydb.d.ts +3 -1
  314. package/dist/kernel/paths.d.ts +29 -5
  315. package/dist/kernel/prepared-write.d.ts +1 -1
  316. package/dist/kernel/put-many-atomic.d.ts +1 -1
  317. package/dist/kernel/query/live/live.d.ts +2 -1
  318. package/dist/kernel/query/sort-key.d.ts +0 -2
  319. package/dist/kernel/secret-mode.d.ts +1 -1
  320. package/dist/kernel/tx-write-gate.d.ts +2 -4
  321. package/dist/kernel/types.d.ts +135 -11
  322. package/dist/kernel/validation.d.ts +0 -10
  323. package/dist/kernel/vault.d.ts +6 -2
  324. package/dist/kernel/via/reconcile.d.ts +1 -42
  325. package/dist/kernel/write-queue.d.ts +17 -0
  326. package/dist/{ledger-KZPCMVZY.js → ledger-MSJGWKGW.js} +11 -12
  327. package/dist/liberate-LEKDYKTC.js +25 -0
  328. package/dist/link-set-QXXSPPHL.js +29 -0
  329. package/dist/{managed-secret-S3MQ6SH6.js → managed-secret-VSK7ANQI.js} +10 -11
  330. package/dist/materialized-views/index.js +27 -28
  331. package/dist/{migrate-cek-V7GKJZ7O.js → migrate-cek-GEBFYBDC.js} +2 -2
  332. package/dist/money/index.js +5 -5
  333. package/dist/noydb-F2AUF6KL.js +98 -0
  334. package/dist/on/index.js +25 -0
  335. package/dist/overlay-views/index.js +4 -4
  336. package/dist/periods/index.js +12 -13
  337. package/dist/pod/index.js +76 -74
  338. package/dist/{pod-handle-WGDFB4C2.js → pod-handle-YE7CT66T.js} +10 -11
  339. package/dist/pod-handle-YE7CT66T.js.map +1 -0
  340. package/dist/policy/index.js +10 -11
  341. package/dist/port/as/active.d.ts +4 -0
  342. package/dist/port/as/index.d.ts +1 -0
  343. package/dist/port/by/index.d.ts +14 -5
  344. package/dist/port/on/index.d.ts +27 -1
  345. package/dist/port/to/index.d.ts +1 -1
  346. package/dist/port/with/blob-strategy.d.ts +1 -1
  347. package/dist/port/with/broker-strategy.d.ts +14 -0
  348. package/dist/port/with/classified-strategy.d.ts +1 -1
  349. package/dist/port/with/collection-options.d.ts +1 -1
  350. package/dist/port/with/collection-registries.d.ts +1 -1
  351. package/dist/port/with/coverage-strategy.d.ts +17 -1
  352. package/dist/port/with/formats-strategy.d.ts +34 -1
  353. package/dist/port/with/lookup-strategy.d.ts +87 -6
  354. package/dist/port/with/service-bus.d.ts +2 -1
  355. package/dist/portability/index.js +8 -8
  356. package/dist/{post-register-SLSXIPBV.js → post-register-QC5TEAW3.js} +6 -6
  357. package/dist/post-register-QC5TEAW3.js.map +1 -0
  358. package/dist/query/all/index.js +16 -16
  359. package/dist/query/index.js +8 -8
  360. package/dist/query/live/index.js +7 -7
  361. package/dist/query/reduce/index.js +10 -10
  362. package/dist/query/relate/index.js +10 -10
  363. package/dist/reduce/index.js +8 -8
  364. package/dist/register-SN72QALV.js +30 -0
  365. package/dist/registry-253FF7C6.js +35 -0
  366. package/dist/registry-3AZWQTDT.js +23 -0
  367. package/dist/registry-7UEBTHWG.js +8 -0
  368. package/dist/registry-IVJ67NFQ.js +8 -0
  369. package/dist/request-withdrawal-UKVJZKX2.js +28 -0
  370. package/dist/{reveal-4BCKPEC2.js → reveal-YQTRNQFH.js} +10 -9
  371. package/dist/reveal-YQTRNQFH.js.map +1 -0
  372. package/dist/revoke-G2KXJQA5.js +24 -0
  373. package/dist/satellites/index.js +1 -1
  374. package/dist/schema-update/index.js +2 -2
  375. package/dist/sealed-record/index.js +16 -18
  376. package/dist/sealed-record/index.js.map +1 -1
  377. package/dist/search/index.js +4 -4
  378. package/dist/{seed-4JS2RJ4A.js → seed-JMQTRKRE.js} +125 -34
  379. package/dist/seed-JMQTRKRE.js.map +1 -0
  380. package/dist/sequence/index.js +9 -10
  381. package/dist/session/index.js +9 -10
  382. package/dist/session/index.js.map +1 -1
  383. package/dist/shadow/index.js +2 -2
  384. package/dist/signer-IUIYR3CX.js +23 -0
  385. package/dist/snapshots/index.js +11 -11
  386. package/dist/stale-SKV33PBU.js +28 -0
  387. package/dist/storage-NPBYP2SM.js +21 -0
  388. package/dist/store/index.js +1 -1
  389. package/dist/{store-coordination-provider-2O2VUHKW.js → store-coordination-provider-Z6HKPOGM.js} +11 -12
  390. package/dist/store-coordination-provider-Z6HKPOGM.js.map +1 -0
  391. package/dist/sync/index.js +18 -13
  392. package/dist/sync/index.js.map +1 -1
  393. package/dist/team/index.js +18 -17
  394. package/dist/tiers/index.js +11 -12
  395. package/dist/to/index.js +2 -2
  396. package/dist/transactions/index.js +3 -3
  397. package/dist/util/index.js +1 -1
  398. package/dist/vault-head/index.js +11 -14
  399. package/dist/vault-head/index.js.map +1 -1
  400. package/dist/{verify-RVT67IMH.js → verify-KWQIWLGH.js} +14 -12
  401. package/dist/verify-KWQIWLGH.js.map +1 -0
  402. package/dist/via/classified/binding.d.ts +2 -2
  403. package/dist/via/classified/config-drift.d.ts +2 -2
  404. package/dist/via/classified/errors.d.ts +1 -1
  405. package/dist/via/classified/presets.d.ts +29 -0
  406. package/dist/via/classified/resolve.d.ts +7 -0
  407. package/dist/via/geo/binding.d.ts +0 -4
  408. package/dist/via/lookup/handle.d.ts +4 -4
  409. package/dist/via/lookup/index.d.ts +32 -0
  410. package/dist/via/money/binding.d.ts +0 -9
  411. package/dist/walk-F4OLXI5H.js +20 -0
  412. package/dist/with-audit/attestation/index.d.ts +2 -1
  413. package/dist/with-audit/attestation/issue.d.ts +2 -2
  414. package/dist/with-audit/attestation/revoke.d.ts +2 -2
  415. package/dist/with-audit/attestation/scheme.d.ts +7 -0
  416. package/dist/with-audit/attestation/signer.d.ts +1 -1
  417. package/dist/with-audit/attestation/strategy.d.ts +4 -3
  418. package/dist/with-audit/attestation/types.d.ts +10 -0
  419. package/dist/with-audit/attestation/vault-facade.d.ts +9 -2
  420. package/dist/with-audit/attestation/verifier.d.ts +6 -0
  421. package/dist/with-audit/consent/consent.d.ts +1 -1
  422. package/dist/with-audit/consent/strategy.d.ts +1 -1
  423. package/dist/with-audit/forget/completeness.d.ts +66 -0
  424. package/dist/with-audit/forget/index.d.ts +2 -0
  425. package/dist/with-audit/forget/strategy.d.ts +65 -0
  426. package/dist/with-audit/forget/subject-index.d.ts +1 -1
  427. package/dist/with-audit/periods/periods.d.ts +0 -9
  428. package/dist/with-audit/periods/reserved-collections.d.ts +7 -0
  429. package/dist/with-audit/periods/vault-facade.d.ts +1 -1
  430. package/dist/with-audit/portability/request-withdrawal.d.ts +0 -1
  431. package/dist/with-audit/portability/withdraw-accessible.d.ts +0 -1
  432. package/dist/with-audit/sealed-record/index.d.ts +2 -2
  433. package/dist/with-audit/sealed-record/strategy.d.ts +1 -1
  434. package/dist/with-audit/tiers/index.d.ts +3 -3
  435. package/dist/with-audit/tiers/strategy.d.ts +19 -0
  436. package/dist/with-cargo/adopt-partition.d.ts +1 -1
  437. package/dist/with-cargo/extract-partition.d.ts +1 -47
  438. package/dist/with-cargo/floor.d.ts +1 -1
  439. package/dist/with-commit/history/history.d.ts +2 -2
  440. package/dist/with-commit/history/index.d.ts +1 -1
  441. package/dist/with-commit/history/ledger/store.d.ts +1 -1
  442. package/dist/with-commit/history/strategy.d.ts +1 -1
  443. package/dist/with-commit/history/time-machine.d.ts +1 -1
  444. package/dist/with-commit/numbering/index.d.ts +1 -3
  445. package/dist/with-commit/sequence/index.d.ts +1 -1
  446. package/dist/with-commit/tx/transaction.d.ts +2 -24
  447. package/dist/with-commit/vault-head/head.d.ts +1 -1
  448. package/dist/with-commit/vault-head/index.d.ts +1 -1
  449. package/dist/with-commit/vault-head/strategy.d.ts +8 -1
  450. package/dist/with-commit/vault-head/verify.d.ts +1 -1
  451. package/dist/with-fork/snapshots/noydb-facade.d.ts +7 -0
  452. package/dist/with-formula/derivations/dispatch.d.ts +1 -1
  453. package/dist/with-formula/derivations/fanout-sidecar.d.ts +1 -1
  454. package/dist/with-formula/derivations/trigger-match.d.ts +8 -5
  455. package/dist/with-formula/materialized-views/emit-memo.d.ts +0 -2
  456. package/dist/with-lookup/embeddings/index.d.ts +1 -2
  457. package/dist/with-lookup/indexing/collection-facade.d.ts +2 -16
  458. package/dist/with-lookup/indexing/eager-indexes.d.ts +2 -1
  459. package/dist/with-lookup/search/collection-facade.d.ts +1 -13
  460. package/dist/with-party/broker/index.d.ts +4 -4
  461. package/dist/with-party/broker/seed.d.ts +25 -10
  462. package/dist/with-party/directory/user-envelope/storage.d.ts +1 -1
  463. package/dist/with-party/policy/noydb-facade.d.ts +1 -31
  464. package/dist/with-party/team/delegation.d.ts +59 -3
  465. package/dist/with-party/team/echo-secret.d.ts +2 -4
  466. package/dist/with-party/team/index.d.ts +1 -1
  467. package/dist/with-party/team/keyring.d.ts +2 -22
  468. package/dist/with-party/team/magic-link-grant.d.ts +12 -2
  469. package/dist/with-party/team/noydb-facade.d.ts +5 -0
  470. package/dist/with-party/team/recovery.d.ts +1 -1
  471. package/dist/with-party/team/reserved-secret-collections.d.ts +10 -8
  472. package/dist/with-party/team/roster-tag.d.ts +43 -2
  473. package/dist/with-party/team/rotate-recover.d.ts +1 -1
  474. package/dist/with-party/team/tiers.d.ts +36 -5
  475. package/dist/with-party/team/wrapped-deks.d.ts +1 -1
  476. package/dist/with-pod/index.d.ts +1 -1
  477. package/dist/with-shape/blobs/blob-compaction.d.ts +1 -1
  478. package/dist/with-shape/blobs/blob-intent.d.ts +1 -1
  479. package/dist/with-shape/blobs/blob-set.d.ts +25 -2
  480. package/dist/with-shape/blobs/legacy-sweep.d.ts +17 -1
  481. package/dist/with-shape/introspection/describe.d.ts +2 -3
  482. package/dist/with-shape/introspection/index.d.ts +1 -0
  483. package/dist/with-shape/introspection/types.d.ts +2 -1
  484. package/dist/with-shape/introspection/walk.d.ts +1 -1
  485. package/dist/with-shape/links/lazy-handle.d.ts +1 -1
  486. package/dist/with-shape/links/link-set.d.ts +1 -1
  487. package/dist/with-shape/manifest/derive.d.ts +1 -1
  488. package/dist/with-shape/manifest/storage.d.ts +1 -1
  489. package/dist/with-shape/persisted-schemas/register.d.ts +1 -1
  490. package/dist/with-shape/persisted-schemas/storage.d.ts +1 -1
  491. package/dist/with-shape/satellites/dead-filter.d.ts +1 -1
  492. package/dist/with-shape/satellites/declare.d.ts +1 -1
  493. package/dist/with-shape/satellites/marker.d.ts +1 -1
  494. package/dist/with-shape/satellites/post-register.d.ts +1 -1
  495. package/dist/with-shape/schema-update/fence.d.ts +7 -0
  496. package/dist/with-sync/engine.d.ts +30 -1
  497. package/dist/with-sync/presence.d.ts +5 -10
  498. package/dist/with-sync/reserved-mirror.d.ts +89 -0
  499. package/dist/with-sync/strategy.d.ts +7 -0
  500. package/dist/with-sync/tab-write-relay.d.ts +0 -10
  501. package/dist/withdraw-accessible-ZKPLZ66E.js +25 -0
  502. package/dist/withdraw-accessible-ZKPLZ66E.js.map +1 -0
  503. package/package.json +14 -5
  504. package/dist/api-GVYEKSJE.js +0 -19
  505. package/dist/backup-7TFRVFLH.js.map +0 -1
  506. package/dist/chunk-244VXYPG.js.map +0 -1
  507. package/dist/chunk-3ELRMVJK.js.map +0 -1
  508. package/dist/chunk-3F7GLNAE.js.map +0 -1
  509. package/dist/chunk-3H5KLDZN.js.map +0 -1
  510. package/dist/chunk-5FDLC6G7.js.map +0 -1
  511. package/dist/chunk-6B3VNR4P.js.map +0 -1
  512. package/dist/chunk-6LNL42F5.js.map +0 -1
  513. package/dist/chunk-6QSQQLSE.js +0 -95
  514. package/dist/chunk-6QSQQLSE.js.map +0 -1
  515. package/dist/chunk-7VCXZDA4.js.map +0 -1
  516. package/dist/chunk-ANWPSTZU.js.map +0 -1
  517. package/dist/chunk-BP6DARYA.js.map +0 -1
  518. package/dist/chunk-BUDAGWL5.js.map +0 -1
  519. package/dist/chunk-CKNGS5MH.js.map +0 -1
  520. package/dist/chunk-DC5QVPV7.js.map +0 -1
  521. package/dist/chunk-DE4DH2RZ.js +0 -29
  522. package/dist/chunk-DE4DH2RZ.js.map +0 -1
  523. package/dist/chunk-DF73H4UN.js.map +0 -1
  524. package/dist/chunk-EIPVYUEP.js.map +0 -1
  525. package/dist/chunk-ENAG3IY7.js.map +0 -1
  526. package/dist/chunk-FCKWQE2H.js.map +0 -1
  527. package/dist/chunk-FFWI7UH7.js.map +0 -1
  528. package/dist/chunk-FQ54DWUQ.js.map +0 -1
  529. package/dist/chunk-FQ5VRHIB.js.map +0 -1
  530. package/dist/chunk-GF5BZ7D4.js.map +0 -1
  531. package/dist/chunk-GHLTDZRS.js.map +0 -1
  532. package/dist/chunk-GYGLAVCX.js.map +0 -1
  533. package/dist/chunk-HBPPGWUM.js.map +0 -1
  534. package/dist/chunk-HK67PX4J.js.map +0 -1
  535. package/dist/chunk-HVSCEIYL.js.map +0 -1
  536. package/dist/chunk-IEX7NK2K.js +0 -125
  537. package/dist/chunk-IEX7NK2K.js.map +0 -1
  538. package/dist/chunk-ISZQJBML.js.map +0 -1
  539. package/dist/chunk-J6UOHJY5.js.map +0 -1
  540. package/dist/chunk-KJFW62P4.js.map +0 -1
  541. package/dist/chunk-L7DOS3TE.js.map +0 -1
  542. package/dist/chunk-LGNDFRYC.js.map +0 -1
  543. package/dist/chunk-LGRLME4M.js.map +0 -1
  544. package/dist/chunk-LP7BEXCT.js +0 -32
  545. package/dist/chunk-LP7BEXCT.js.map +0 -1
  546. package/dist/chunk-LQ4ZHOKP.js.map +0 -1
  547. package/dist/chunk-M522UYZ2.js.map +0 -1
  548. package/dist/chunk-M7NI3IWE.js.map +0 -1
  549. package/dist/chunk-MTL7MXGR.js.map +0 -1
  550. package/dist/chunk-N5DNADLQ.js.map +0 -1
  551. package/dist/chunk-NNARWO72.js.map +0 -1
  552. package/dist/chunk-NRT6QZQB.js.map +0 -1
  553. package/dist/chunk-NWHBJQAL.js.map +0 -1
  554. package/dist/chunk-ODVPTDPK.js.map +0 -1
  555. package/dist/chunk-PDCG6PR5.js.map +0 -1
  556. package/dist/chunk-PEOZ34EM.js.map +0 -1
  557. package/dist/chunk-PSIV7CJV.js.map +0 -1
  558. package/dist/chunk-QBBUZ3I3.js.map +0 -1
  559. package/dist/chunk-QOOUUR4A.js.map +0 -1
  560. package/dist/chunk-QWFHIOD2.js.map +0 -1
  561. package/dist/chunk-QWGU66MP.js.map +0 -1
  562. package/dist/chunk-RHVMZSUU.js.map +0 -1
  563. package/dist/chunk-S57HTZ6X.js.map +0 -1
  564. package/dist/chunk-SEE332EV.js +0 -66
  565. package/dist/chunk-SEE332EV.js.map +0 -1
  566. package/dist/chunk-SOIQ4MM4.js +0 -20
  567. package/dist/chunk-SOIQ4MM4.js.map +0 -1
  568. package/dist/chunk-SYVZW77S.js.map +0 -1
  569. package/dist/chunk-T2LBQ5DV.js.map +0 -1
  570. package/dist/chunk-UBSFPGVJ.js +0 -1
  571. package/dist/chunk-UD5EVGZ7.js.map +0 -1
  572. package/dist/chunk-UPD5NWI5.js.map +0 -1
  573. package/dist/chunk-UYB234ZR.js +0 -479
  574. package/dist/chunk-UYB234ZR.js.map +0 -1
  575. package/dist/chunk-VNPEWFPD.js.map +0 -1
  576. package/dist/chunk-WX5ADGTZ.js.map +0 -1
  577. package/dist/chunk-X4773RBR.js.map +0 -1
  578. package/dist/chunk-YNIUIERI.js.map +0 -1
  579. package/dist/chunk-YSK2CU2Q.js.map +0 -1
  580. package/dist/chunk-ZE4CHIED.js.map +0 -1
  581. package/dist/chunk-ZXABPVM6.js.map +0 -1
  582. package/dist/classified-marker-W3USLL5M.js.map +0 -1
  583. package/dist/collection-facade-QU6IJBN3.js +0 -48
  584. package/dist/dead-filter-U2UYKSZL.js.map +0 -1
  585. package/dist/delegation-S3AEPW2T.js.map +0 -1
  586. package/dist/derive-OX7F6TGG.js +0 -21
  587. package/dist/dispatch-RH4LHKBH.js.map +0 -1
  588. package/dist/executor-B6SRO2QS.js +0 -36
  589. package/dist/executor-BI5CHWRY.js +0 -8
  590. package/dist/executor-RBKM5W5K.js +0 -8
  591. package/dist/export-accessible-VMA3YM5V.js +0 -22
  592. package/dist/extract-partition-NF7PXGPP.js +0 -37
  593. package/dist/fanout-sidecar-OT6RYLOL.js.map +0 -1
  594. package/dist/find-LZMP26KF.js +0 -10
  595. package/dist/issue-IHARXWW5.js +0 -18
  596. package/dist/kernel/enclave/classify/compare.d.ts +0 -10
  597. package/dist/kernel/enclave/index.d.ts +0 -87
  598. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +0 -124
  599. package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +0 -88
  600. package/dist/kernel/enclave/record-keys/rekey.d.ts +0 -91
  601. package/dist/kernel/enclave/record-keys/sealed-slot.d.ts +0 -14
  602. package/dist/kernel/enclave/record-keys/sealed-slots.d.ts +0 -123
  603. package/dist/kernel/enclave/record-keys/sealing.d.ts +0 -70
  604. package/dist/liberate-GRLCZMW4.js +0 -24
  605. package/dist/link-set-JCMJ7ULP.js +0 -30
  606. package/dist/noydb-SWQJ3MZS.js +0 -97
  607. package/dist/pod-handle-WGDFB4C2.js.map +0 -1
  608. package/dist/post-register-SLSXIPBV.js.map +0 -1
  609. package/dist/register-F3OYE7MJ.js +0 -31
  610. package/dist/registry-CILQFXFK.js +0 -8
  611. package/dist/registry-XNNYQVKV.js +0 -8
  612. package/dist/registry-YCDZV3VW.js +0 -35
  613. package/dist/registry-ZGYS33TE.js +0 -24
  614. package/dist/request-withdrawal-Z5NC6TXT.js +0 -30
  615. package/dist/reveal-4BCKPEC2.js.map +0 -1
  616. package/dist/revoke-ZL62WC2D.js +0 -23
  617. package/dist/seed-4JS2RJ4A.js.map +0 -1
  618. package/dist/signer-MBZZMXTC.js +0 -24
  619. package/dist/stale-TJ3DYZMQ.js +0 -29
  620. package/dist/storage-SVMKRGCW.js +0 -22
  621. package/dist/store-coordination-provider-2O2VUHKW.js.map +0 -1
  622. package/dist/verify-RVT67IMH.js.map +0 -1
  623. package/dist/walk-HJ42EISE.js +0 -21
  624. package/dist/withdraw-accessible-5NZUPAVA.js +0 -27
  625. /package/dist/{api-GVYEKSJE.js.map → api-MSJXVYQG.js.map} +0 -0
  626. /package/dist/{collection-facade-QU6IJBN3.js.map → capsule/contract.js.map} +0 -0
  627. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/bidx.d.ts +0 -0
  628. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/digest.d.ts +0 -0
  629. /package/dist/{kernel/enclave → capsule/enclave-aes}/classify/vdig.d.ts +0 -0
  630. /package/dist/{kernel/enclave/classify → capsule/plumbing}/kofn.d.ts +0 -0
  631. /package/dist/{kernel/enclave/classify → capsule/plumbing}/normalize.d.ts +0 -0
  632. /package/dist/{kernel/enclave → capsule/plumbing}/record-aad.d.ts +0 -0
  633. /package/dist/{chunk-UBSFPGVJ.js.map → capsule-57BDK7LG.js.map} +0 -0
  634. /package/dist/{computed-TPFBX7U4.js.map → chunk-2DX4JCRI.js.map} +0 -0
  635. /package/dist/{chunk-ZSX4S5XB.js.map → chunk-2ZFD2UJC.js.map} +0 -0
  636. /package/dist/{chunk-HT2AHGQI.js.map → chunk-3Y5JLSXS.js.map} +0 -0
  637. /package/dist/{chunk-SI3V7D4G.js.map → chunk-4UIP3QNW.js.map} +0 -0
  638. /package/dist/{chunk-WNJ4AVGE.js.map → chunk-55P6OBB7.js.map} +0 -0
  639. /package/dist/{chunk-MKXXMNJH.js.map → chunk-5UUJE4FP.js.map} +0 -0
  640. /package/dist/{chunk-FW22ZCXT.js.map → chunk-6WS35E6X.js.map} +0 -0
  641. /package/dist/{chunk-6OWXYZK2.js.map → chunk-B65CC6Z6.js.map} +0 -0
  642. /package/dist/{chunk-F2TDU7MQ.js.map → chunk-BJH77UY2.js.map} +0 -0
  643. /package/dist/{chunk-BJ2IXQ5Q.js.map → chunk-BNFNQNEP.js.map} +0 -0
  644. /package/dist/{chunk-HLEK5VAN.js.map → chunk-BUE7R3B5.js.map} +0 -0
  645. /package/dist/{chunk-EOVMT6TB.js.map → chunk-C53ER7AG.js.map} +0 -0
  646. /package/dist/{chunk-K6W7BVZ6.js.map → chunk-CAD7N6HQ.js.map} +0 -0
  647. /package/dist/{chunk-5O7D6ZTW.js.map → chunk-DHU2AQ6T.js.map} +0 -0
  648. /package/dist/{chunk-IT22PWKV.js.map → chunk-ECGXA4LT.js.map} +0 -0
  649. /package/dist/{chunk-4MXI3ODD.js.map → chunk-GCUMOKTN.js.map} +0 -0
  650. /package/dist/{chunk-B6SJIBQK.js.map → chunk-GOJQ5DZA.js.map} +0 -0
  651. /package/dist/{chunk-TKUOEU2F.js.map → chunk-JSWKVZCR.js.map} +0 -0
  652. /package/dist/{chunk-X25NWUZB.js.map → chunk-L5A3KR7R.js.map} +0 -0
  653. /package/dist/{chunk-T52GX4SA.js.map → chunk-LGGIFKJP.js.map} +0 -0
  654. /package/dist/{chunk-ID6Z6E66.js.map → chunk-MQ7YWBQ5.js.map} +0 -0
  655. /package/dist/{chunk-FN5EBVTQ.js.map → chunk-NIXRB5BP.js.map} +0 -0
  656. /package/dist/{chunk-HBRLNOHL.js.map → chunk-NYJBYVBG.js.map} +0 -0
  657. /package/dist/{chunk-ZUVBC32X.js.map → chunk-OK57EATL.js.map} +0 -0
  658. /package/dist/{chunk-TOPYQ6GA.js.map → chunk-PCYPXLEX.js.map} +0 -0
  659. /package/dist/{chunk-VNNRUGMM.js.map → chunk-PGC35KXE.js.map} +0 -0
  660. /package/dist/{chunk-4I522ERM.js.map → chunk-PT2YCQVK.js.map} +0 -0
  661. /package/dist/{chunk-Z5HTYMMQ.js.map → chunk-PTJY5R3O.js.map} +0 -0
  662. /package/dist/{chunk-F24ZZTHQ.js.map → chunk-QGFEBNHO.js.map} +0 -0
  663. /package/dist/{chunk-A6IJU4TK.js.map → chunk-QGSNJD3B.js.map} +0 -0
  664. /package/dist/{chunk-BK2HHQGN.js.map → chunk-QSHATAXJ.js.map} +0 -0
  665. /package/dist/{chunk-JCN44NCY.js.map → chunk-RIZTZJX6.js.map} +0 -0
  666. /package/dist/{chunk-MVZ2TPVP.js.map → chunk-RNACRT5H.js.map} +0 -0
  667. /package/dist/{chunk-TROR3ALR.js.map → chunk-SFCD6OU7.js.map} +0 -0
  668. /package/dist/{chunk-I3JDRCMW.js.map → chunk-TZYTMZZY.js.map} +0 -0
  669. /package/dist/{chunk-AOWGXU6D.js.map → chunk-UAKY6E4N.js.map} +0 -0
  670. /package/dist/{chunk-3JWHAR6E.js.map → chunk-UNSQZSZP.js.map} +0 -0
  671. /package/dist/{chunk-6K7DPM7D.js.map → chunk-WFWFXDNS.js.map} +0 -0
  672. /package/dist/{chunk-CF5UU4S7.js.map → chunk-WHOKGHJ3.js.map} +0 -0
  673. /package/dist/{chunk-32YV3TJT.js.map → chunk-XXNWU5QZ.js.map} +0 -0
  674. /package/dist/{chunk-B57AFBY2.js.map → chunk-XXQ5XIYY.js.map} +0 -0
  675. /package/dist/{chunk-ORDURZBU.js.map → chunk-YFFMPZPY.js.map} +0 -0
  676. /package/dist/{chunk-MDWKM2XC.js.map → chunk-YKGJ44Z7.js.map} +0 -0
  677. /package/dist/{chunk-MLPHLJIQ.js.map → chunk-ZHQJAV6D.js.map} +0 -0
  678. /package/dist/{chunk-7JE7BDYJ.js.map → chunk-ZVSKSUGI.js.map} +0 -0
  679. /package/dist/{derive-OX7F6TGG.js.map → collection-facade-LZIF7GWD.js.map} +0 -0
  680. /package/dist/{enclave-27SMF6GF.js.map → computed-XMFTHAXV.js.map} +0 -0
  681. /package/dist/{executor-B6SRO2QS.js.map → derive-3YSIS36W.js.map} +0 -0
  682. /package/dist/{dispatch-3CQDJV5Q.js.map → dispatch-OFDA4WQV.js.map} +0 -0
  683. /package/dist/{executor-BI5CHWRY.js.map → executor-E6YDJ7YA.js.map} +0 -0
  684. /package/dist/{executor-RBKM5W5K.js.map → executor-NJX3YQQ4.js.map} +0 -0
  685. /package/dist/{export-accessible-VMA3YM5V.js.map → executor-WWM3DD4P.js.map} +0 -0
  686. /package/dist/{extract-partition-NF7PXGPP.js.map → export-accessible-RLHU5G4W.js.map} +0 -0
  687. /package/dist/{find-LZMP26KF.js.map → extract-partition-QII2EDCS.js.map} +0 -0
  688. /package/dist/{issue-IHARXWW5.js.map → find-6M7GYDUZ.js.map} +0 -0
  689. /package/dist/{ledger-KZPCMVZY.js.map → issue-LPEEEMPP.js.map} +0 -0
  690. /package/dist/{liberate-GRLCZMW4.js.map → ledger-MSJGWKGW.js.map} +0 -0
  691. /package/dist/{link-set-JCMJ7ULP.js.map → liberate-LEKDYKTC.js.map} +0 -0
  692. /package/dist/{managed-secret-S3MQ6SH6.js.map → link-set-QXXSPPHL.js.map} +0 -0
  693. /package/dist/{noydb-SWQJ3MZS.js.map → managed-secret-VSK7ANQI.js.map} +0 -0
  694. /package/dist/{migrate-cek-V7GKJZ7O.js.map → migrate-cek-GEBFYBDC.js.map} +0 -0
  695. /package/dist/{register-F3OYE7MJ.js.map → noydb-F2AUF6KL.js.map} +0 -0
  696. /package/dist/{registry-CILQFXFK.js.map → register-SN72QALV.js.map} +0 -0
  697. /package/dist/{registry-XNNYQVKV.js.map → registry-253FF7C6.js.map} +0 -0
  698. /package/dist/{registry-YCDZV3VW.js.map → registry-3AZWQTDT.js.map} +0 -0
  699. /package/dist/{registry-ZGYS33TE.js.map → registry-7UEBTHWG.js.map} +0 -0
  700. /package/dist/{request-withdrawal-Z5NC6TXT.js.map → registry-IVJ67NFQ.js.map} +0 -0
  701. /package/dist/{revoke-ZL62WC2D.js.map → request-withdrawal-UKVJZKX2.js.map} +0 -0
  702. /package/dist/{signer-MBZZMXTC.js.map → revoke-G2KXJQA5.js.map} +0 -0
  703. /package/dist/{stale-TJ3DYZMQ.js.map → signer-IUIYR3CX.js.map} +0 -0
  704. /package/dist/{storage-SVMKRGCW.js.map → stale-SKV33PBU.js.map} +0 -0
  705. /package/dist/{walk-HJ42EISE.js.map → storage-NPBYP2SM.js.map} +0 -0
  706. /package/dist/{withdraw-accessible-5NZUPAVA.js.map → walk-F4OLXI5H.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/capsule/enclave-aes/classify/compare.ts","../src/capsule/enclave-aes/crypto.ts","../src/capsule/enclave-aes/classify/vdig.ts","../src/capsule/enclave-aes/classify/write.ts","../src/capsule/enclave-aes/primitives.ts","../src/capsule/enclave-aes/bound.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 */\n\nimport { ctEqualTags } from '../../plumbing/ct-equal.js'\nconst subtle = globalThis.crypto.subtle\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 * Cryptographic primitives — thin wrappers around the Web Crypto API.\n *\n * ## Design principle\n *\n * **Zero npm crypto dependencies.** Every operation uses `globalThis.crypto.subtle`,\n * which is available natively in Node.js ≥ 18, all modern browsers, and\n * Deno/Bun. This avoids supply-chain risk from third-party crypto packages and\n * ensures the library stays auditable.\n *\n * ## Algorithms\n *\n * | Use case | Algorithm | Parameters |\n * |----------|-----------|------------|\n * | Key derivation | PBKDF2-SHA256 | 600,000 iterations, 32-byte salt |\n * | Record encryption | AES-256-GCM | 12-byte random IV per operation |\n * | DEK wrapping | AES-KW (RFC 3394) | 256-bit KEK |\n * | Binary encrypt | AES-256-GCM | same as record encryption |\n * | Integrity | HMAC-SHA256 | for presence channels |\n * | Content hash | SHA-256 | for ledger and bundle integrity |\n *\n * ## Key lifecycle\n *\n * ```\n * secret + salt\n * └─► deriveKey() → KEK (CryptoKey, extractable: false)\n * └─► wrapKey() → wrapped DEK bytes [stored in keyring]\n * └─► unwrapKey() → DEK (CryptoKey) [memory only during session]\n * └─► encrypt() / decrypt() → ciphertext / plaintext\n * ```\n *\n * IVs are generated fresh by {@link generateIV} on every encrypt call.\n * Reusing an IV with the same key would break GCM's authentication guarantee —\n * this function should be the only place IVs are produced.\n *\n * @module\n */\n\nimport { DecryptionError, InvalidKeyError, TamperedError, ValidationError } from '../../kernel/errors.js'\nimport {\n KEY_BITS,\n IV_BYTES,\n bufferToBase64,\n base64ToBuffer,\n generateIV,\n encodeEchoParts,\n type EchoSecretParts,\n} from '../plumbing/digest.js'\n\n/**\n * **EnclaveKey** — the opaque key type at the enclave seam.\n *\n * In noy-db's reference enclave this is `CryptoKey` (the Web Crypto API's\n * key handle). A fork's enclave redefines this alias to its own key\n * representation (e.g. `type EnclaveKey = null` for a keyless HSM-backed\n * fork) — outside `capsule/enclave-aes/**`, every consumer must treat it as\n * opaque: never construct, inspect, or serialize it directly, only pass it\n * between barrel functions (`encrypt`/`decrypt`/`wrapKey`/`unwrapKey`/…).\n */\nexport type EnclaveKey = CryptoKey\n\n/** Opaque asymmetric pair at the enclave seam — `CryptoKeyPair` in the reference enclave. */\nexport type EnclaveKeyPair = CryptoKeyPair\n\nconst PBKDF2_ITERATIONS = 600_000\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a secret and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n secret: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n return deriveKekFromMaterial(new TextEncoder().encode(secret), salt)\n}\n\n/**\n * Shared PBKDF2 → AES-KW derivation core. `material` is the raw\n * pre-KDF input bytes (a UTF-8 phrase for standard mode, an\n * AG-1-encoded 3-part structure for echo mode).\n */\nasync function deriveKekFromMaterial(\n material: Uint8Array,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey('raw', material as BufferSource, 'PBKDF2', false, ['deriveKey'])\n return subtle.deriveKey(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n/** One three-part echo secret: prompt (typed) → echo (revealed) → key (typed). */\n\n/**\n * Domain context for the AG-1 encoding. The leading `0xFF` is the load-bearing\n * byte: it is not a legal UTF-8 lead byte, so `TextEncoder` can never emit it —\n * no string's UTF-8 encoding can equal an AG-1 encoding, whatever the string.\n */\n\nexport async function deriveEchoKey(\n parts: EchoSecretParts,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n return deriveKekFromMaterial(encodeEchoParts(parts), salt)\n}\n\n/**\n * Which AES algorithm/usage a {@link deriveSecretKey} result is for:\n * `'aes-kw'` mints a key-wrapping key (`wrapKey`/`unwrapKey`, e.g. for\n * KEK derivation); `'aes-gcm'` mints a direct encrypt/decrypt key (e.g.\n * for the wrap-DEKs primitive in `with-party/team/wrapped-deks.ts`).\n */\nexport type SecretKeyUsage = 'aes-kw' | 'aes-gcm'\n\n/**\n * Derive a non-extractable AES key from a secret/credential and salt\n * via PBKDF2-SHA256, generalizing {@link deriveKey}'s AES-KW derivation to\n * also cover AES-GCM-usage keys. Callers pin their own `iterations` and\n * `keyUsage` so an existing call site's exact parameters (and thus its\n * derived-key bytes) are preserved verbatim when migrated onto this\n * shared primitive — this is call-site consolidation, not a KDF change.\n */\nexport async function deriveSecretKey(\n secret: string,\n salt: Uint8Array,\n params: { iterations: number; keyUsage: SecretKeyUsage },\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return params.keyUsage === 'aes-gcm'\n ? subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n : subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Generate a NON-extractable AES-256-GCM key for in-memory-only use — a\n * session key, a device seal. Unlike {@link generateDEK} it can never be\n * wrapped or exported: WebCrypto enforces that, and it is the tab-scope\n * invariant `with-party/session` and `device-seal` rely on.\n */\nexport async function generateEphemeralKey(): Promise<CryptoKey> {\n return subtle.generateKey({ name: 'AES-GCM', length: KEY_BITS }, false, ['encrypt', 'decrypt'])\n}\n\n// ─── DEK-Set Codec ─────────────────────────────────────────────────────\n//\n// The portable form of a DEK set — `{ collection: base64(rawKey) }` — is the\n// plaintext body of every persisted WrappedDeksBlob (recovery-paper,\n// recovery-shamir, tier-2 password slots). It is an enclave door because it\n// is the one place a key's REPRESENTATION crosses into bytes: a fork with its\n// own EnclaveKey (hardware-backed, post-quantum, no encryption) decides here\n// what \"the bytes of a key\" means, and nothing outside capsule/enclave-aes has to\n// know. Byte-for-byte what wrapped-deks.ts did inline before #1317.\n\n/** Serialize a DEK set to `{ collection: base64(rawKey) }`. */\nexport async function exportDekSet(deks: Map<string, CryptoKey>): Promise<Record<string, string>> {\n const out: Record<string, string> = {}\n for (const [coll, dek] of deks) {\n out[coll] = bufferToBase64(await subtle.exportKey('raw', dek))\n }\n return out\n}\n\n/**\n * Reverse of {@link exportDekSet}. Keys come back extractable AES-GCM\n * (`encrypt`/`decrypt`), matching what `generateDEK` mints.\n */\nexport async function importDekSet(exported: Record<string, string>): Promise<Map<string, CryptoKey>> {\n const deks = new Map<string, CryptoKey>()\n for (const [coll, b64] of Object.entries(exported)) {\n const key = await subtle.importKey(\n 'raw',\n base64ToBuffer(b64) as BufferSource,\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n deks.set(coll, key)\n }\n return deks\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Canary ────────────────────────────────────────────────────────────\n//\n// \"Is this KEK the right one?\" is answered by wrapping a CONSTANT key under\n// it and seeing whether the wrap opens later. AES-KW is deterministic\n// (RFC 3394 fixed IV), so the same constant under the same KEK always yields\n// the same bytes — every write site can re-mint on persist. The keyring\n// canary (32 zero bytes) and the echo prompt/echo verifiers (32 × 0x5a) are\n// both this primitive with a different constant.\n\n/** Wrap the constant AES-GCM key built from `plaintext` under `kek`. */\nexport async function mintCanary(kek: CryptoKey, plaintext: Uint8Array): Promise<string> {\n const constant = await subtle.importKey(\n 'raw', plaintext as BufferSource, { name: 'AES-GCM', length: KEY_BITS }, true, ['encrypt', 'decrypt'],\n )\n return wrapKey(constant, kek)\n}\n\n/** True iff `wrapped` unwraps under `kek` — the KEK is right AND the bytes are intact. */\nexport async function checkCanary(wrapped: string, kek: CryptoKey): Promise<boolean> {\n try {\n await unwrapKey(wrapped, kek)\n return true\n } catch {\n return false\n }\n}\n\n// ─── Per-record CEK wrapping ───────────────────────────────────────────\n\n/**\n * Derive a **dedicated** AES-KW wrapping key from a collection/tier DEK via\n * HKDF-SHA256, used to wrap/unwrap a per-record CEK.\n *\n * The collection/tier DEKs are AES-256-**GCM** keys (usages\n * `encrypt`/`decrypt`) and cannot themselves act as an AES-KW KEK. We do NOT\n * re-import the raw DEK bytes directly as an AES-KW key — that would reuse one\n * key across two primitives (AES-GCM for `_det`/presence, AES-KW for CEK\n * wrapping), violating key separation. Instead we HKDF-derive a domain-\n * separated KW key (salt `noydb-cek-wrap`), exactly as `derivePresenceKey`\n * derives a separate presence key. The derivation is deterministic, so\n * wrapping the same CEK under the same DEK always yields identical ciphertext\n * — which is what lets an update reuse a record's stable CEK and produce a\n * byte-identical `_cek`. The KW key is independent of the DEK's GCM key.\n *\n * The DEK must be extractable (it is — `generateDEK`/`unwrapKey` mint\n * extractable keys).\n */\nasync function asKwKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-cek-wrap')\n const info = new TextEncoder().encode('v1')\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey('raw', bits, 'AES-KW', false, ['wrapKey', 'unwrapKey'])\n}\n\n/**\n * AES-KW-wrap a per-record CEK under a collection/tier DEK. Returns base64.\n * Deterministic over `(cek, dek)`.\n */\nexport async function wrapCek(cek: CryptoKey, dek: CryptoKey): Promise<string> {\n const kw = await asKwKey(dek)\n const wrapped = await subtle.wrapKey('raw', cek, kw, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/**\n * Unwrap a per-record CEK from base64 under a collection/tier DEK. Throws\n * `InvalidKeyError` if the wrapped bytes do not authenticate under the DEK.\n */\nexport async function unwrapCek(wrappedBase64: string, dek: CryptoKey): Promise<CryptoKey> {\n const kw = await asKwKey(dek)\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kw,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n/**\n * Import a raw 32-byte AES-256-GCM record CEK (e.g. from a sealed-CEK\n * delivery binding). Non-extractable, decrypt-only — the imported key is\n * used solely to open the record's `_iv`/`_data` body under `decrypt()`.\n */\nexport async function importCek(rawKey: Uint8Array): Promise<CryptoKey> {\n return subtle.importKey('raw', rawKey as BufferSource, { name: 'AES-GCM', length: KEY_BITS }, false, ['decrypt'])\n}\n\n/**\n * Import a raw 32-byte pre-shared key as a NON-extractable AES-256-GCM key\n * (encrypt + decrypt). The transfer seal (`with-cargo`) and the RSA-OAEP\n * TLV's per-blob CEK (`managed-secret`) both hand raw bytes to the enclave\n * here; the bytes never become a key anywhere else.\n */\nexport async function importTransferKey(raw: Uint8Array): Promise<CryptoKey> {\n if (raw.byteLength !== 32) {\n throw new ValidationError(`transfer key must be 32 bytes, got ${raw.byteLength}.`)\n }\n return subtle.importKey('raw', raw as BufferSource, { name: 'AES-GCM', length: KEY_BITS }, false, ['encrypt', 'decrypt'])\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/**\n * Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call.\n *\n * `aad` binds the record's identity into the auth tag (#1041) — see\n * `record-aad.ts` for what is bound and why `_v` is not. A body sealed with\n * AAD can only be opened by a reader supplying the identical bytes, which is\n * what stops an untrusted store from relocating or re-tiering an envelope.\n *\n * Optional **only while the call-site sweep is in progress**. It becomes\n * mandatory once every `encrypt`/`decrypt` pair supplies it, at which point\n * the format version bumps and the reader rejects anything unbound. Until\n * then an omitted `aad` reproduces the previous, unauthenticated behaviour.\n */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n aad?: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n ...(aad !== undefined && { additionalData: aad as BufferSource }),\n },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data.\n *\n * `aad` must be byte-identical to what {@link encrypt} was given, or the auth\n * tag fails and this throws `TamperedError` (#1041). Note the asymmetry that\n * makes this useful: a mismatched identity is indistinguishable from a\n * corrupted body, so relocation fails **closed** rather than returning a\n * plausible record in the wrong place.\n */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad?: Uint8Array,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n ...(aad !== undefined && { additionalData: aad as BufferSource }),\n },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError(...(await classifyTagFailure(iv, ciphertext, dek, aad)))\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n\n/**\n * Distinguish a FORMAT TRANSITION from an attack, when the enclave can (#1103).\n *\n * ## The problem\n *\n * Since #1041 switched identity AAD on, one `TamperedError` covers at least\n * four situations: legitimate pre-binding data, a store that reconstructs an\n * envelope and drops a bound field, a CEK/rotation mismatch, and actual\n * tampering. `SECURITY.md` and the subsystem docs teach the LAST reading — so a\n * user who upgrades, hits this on their own honest data, and consults the docs\n * is told their store has been compromised.\n *\n * That is a false positive on the product's central security claim, and the\n * cost is not merely a confusing message: **a tamper alarm that cries wolf is\n * one users learn to ignore.**\n *\n * ## Why the probe is sound, and why it is NOT a downgrade\n *\n * A body sealed before #1041 carries no AAD, so it opens under an empty one.\n * Retrying that way is **positive evidence** of the legacy format rather than a\n * guess: forging a body that decrypts under this DEK requires the DEK, and an\n * untrusted store does not hold one.\n *\n * ⚠️ **Classification ONLY — the plaintext is discarded and the caller still\n * throws.** That is the entire safety argument: the outcome is identical either\n * way, so this cannot become the lenient path #1041 exists to remove. A variant\n * that RETURNED the retry's plaintext would be exactly the accept-without-AAD\n * downgrade the binding was built to prevent. Do not \"optimise\" it into one.\n *\n * Note what the honest message concedes: pre-#1041 data had no authenticated\n * metadata, so a legacy record that was ALSO tampered with reads as legacy.\n * That is not a weakening — it is an accurate statement about data that\n * genuinely never carried a binding.\n */\nasync function classifyTagFailure(\n iv: Uint8Array,\n ciphertext: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array | undefined,\n): Promise<[] | [string, 'unbound-legacy-format']> {\n // Only meaningful when AAD was supplied: with none there is no second\n // interpretation to test, and the failure is a plain tag mismatch.\n if (aad === undefined) return []\n try {\n await subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, dek, ciphertext as BufferSource)\n } catch {\n return [] // no benign explanation — the security alert stands\n }\n return [\n 'This record was sealed BEFORE record-identity binding (#1041) and cannot be read by this ' +\n 'version. Its body decrypts correctly under the record key, so this is a data-format ' +\n 'transition, not tampering — but it also means the record predates authenticated metadata. ' +\n 'There is no migration path; see noy-db #1100.',\n 'unbound-legacy-format',\n ]\n}\n\n// ─── Binary Encrypt / Decrypt ────────\n\n/**\n * Encrypt raw bytes with AES-256-GCM using a fresh random IV.\n * Used by the attachment store so binary blobs avoid double base64 encoding\n * (the existing `encrypt()` function calls `TextEncoder` on a string — here\n * we pass the `Uint8Array` directly to `subtle.encrypt`).\n */\nexport async function encryptBytes(\n data: Uint8Array,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext back to raw bytes.\n * Counterpart to `encryptBytes`. Throws `TamperedError` on auth-tag failure.\n */\nexport async function decryptBytes(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n\nexport async function encryptBytesWithAAD(\n data: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext with AAD verification.\n *\n * If the AAD does not match the one used at encryption time (e.g. because\n * a chunk was reordered or substituted from another blob), the GCM auth\n * tag fails and this throws `TamperedError`.\n */\nexport async function decryptBytesWithAAD(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Presence Key Derivation ──────────────────────────────\n\n\nexport async function deriveSealedFieldKey(\n dek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed')\n // JSON-array encoding is injective: each element is separately quoted/\n // escaped, so collection `a/sealed/b` + field `c` cannot collide with\n // collection `a` + field `b/sealed/c`.\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a per-record sealed-field key from the record's **per-record CEK**\n * rather than the collection DEK. Same HKDF construction as\n * {@link deriveSealedFieldKey} but with salt `'noydb-sealed-cek'` (distinct\n * from `'noydb-sealed'`), so a CEK-derived key can never collide with a\n * DEK-derived one for the same `{collection, field}`.\n *\n * The point: the sealed ciphertext's only key now flows from the record's CEK.\n * Dropping the record's wrapped `_cek` (crypto-shred / `forget`) makes the key\n * — and thus `_sealed[field]` — unrecoverable, giving sealed fields the same\n * erasure guarantee `_data` already has. The CEK must be extractable (it is —\n * `unwrapCek`/`generateDEK` mint extractable keys).\n *\n * @param cek The record's AES-256-GCM CEK (extractable).\n * @param collectionName Part of the HKDF `info` for domain separation.\n * @param field Sensitive field name — the rest of the `info`.\n * @returns A non-extractable AES-256-GCM key for that field's sealed slot.\n */\nexport async function deriveSealedFieldKeyFromCek(\n cek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawCek = await subtle.exportKey('raw', cek)\n const hkdfKey = await subtle.importKey('raw', rawCek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed-cek')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed-cek', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Deterministic Encryption ────────────────────────────\n\n/**\n * Derive the collection's **dedicated deterministic-index key** from its DEK\n * via HKDF-SHA256 (L-1, #554).\n *\n * `_det` slots previously encrypted under the raw collection DEK — the same\n * key `_data` uses with a *randomized*-IV regime while `_det` uses a\n * *deterministic*-IV regime. Two IV regimes on one key is avoidable hygiene\n * debt (~2^-96 theoretical collision), so the deterministic index now gets its\n * own HKDF-separated key: salt `'noydb-det'`, info\n * `JSON.stringify(['noydb-det'])` (the injective JSON-array domain tag, same\n * convention as {@link deriveSealedFieldKey}). Per-field separation still\n * comes from the `'<collection>/<field>'` context inside\n * {@link encryptDeterministic}, exactly as before.\n *\n * The derived key stays **DEK-derived (collection-level)** on purpose: `_det`\n * is carried verbatim through per-record CEK rotation (see\n * `record-keys/sealing.ts`), so its key must not depend on the CEK. It rotates\n * with the collection DEK, like the presence key.\n *\n * The key is minted **extractable** — {@link encryptDeterministic} derives its\n * per-value IV via HKDF over the raw key bytes (an `exportKey` under the\n * hood), which a non-extractable key would forbid. Same in-memory posture as\n * the DEK itself (`generateDEK` mints extractable keys).\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @returns A dedicated AES-256-GCM key for the `_det` deterministic index.\n */\nexport async function deriveDeterministicKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-det')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-det']))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a deterministic 12-byte IV from `{ DEK, context, plaintext }`\n * via HKDF-SHA256. Given the same three inputs, the IV is identical, so\n * `encryptDeterministic` produces the same ciphertext on every call —\n * which is precisely what enables blind equality search on encrypted\n * fields.\n *\n * **The side channel this opens.** Two records whose field value is the\n * same produce the same ciphertext. An observer with store access can\n * therefore tell which records share a value — not *what* the value is,\n * but the equivalence class. This is the well-known trade-off of\n * deterministic encryption and is why the feature is strictly opt-in\n * per field, guarded by `acknowledgeDeterministicRisk: true` at\n * collection creation.\n *\n * The context string MUST include the collection name and field name,\n * so:\n * - The same plaintext in two different fields encrypts differently\n * (no cross-field equality leak).\n * - The same plaintext in two different collections (different DEKs)\n * encrypts differently by virtue of the key, even before HKDF\n * domain separation kicks in.\n */\nasync function deriveDeterministicIV(\n dek: CryptoKey,\n context: string,\n plaintext: string,\n): Promise<Uint8Array> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-deterministic-v1')\n const info = new TextEncoder().encode(`${context}\\x00${plaintext}`)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n IV_BYTES * 8,\n )\n return new Uint8Array(bits)\n}\n\n/**\n * Encrypt a plaintext string with AES-256-GCM and a deterministic,\n * HKDF-derived IV.\n *\n * The same `{ dek, context, plaintext }` triple always produces the\n * same `{ iv, data }` — call this twice and you can string-compare the\n * ciphertexts to check equality of the inputs without decrypting them.\n *\n * @param context Domain-separation string — by convention\n * `'<collection>/<field>'`. Different contexts encrypt\n * the same plaintext to different ciphertexts, so\n * `email` in collection `users` does not collide with\n * `email` in collection `customers`.\n */\nexport async function encryptDeterministic(\n plaintext: string,\n dek: CryptoKey,\n context: string,\n): Promise<EncryptResult> {\n const iv = await deriveDeterministicIV(dek, context, plaintext)\n const encoded = new TextEncoder().encode(plaintext)\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Counterpart to {@link encryptDeterministic}. The IV is stored\n * alongside the ciphertext (exactly like the randomized path), so\n * decrypt uses the stored IV and verifies the GCM auth tag — a tampered\n * ciphertext throws `TamperedError` just like randomized AES-GCM.\n */\nexport async function decryptDeterministic(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n return decrypt(ivBase64, dataBase64, dek)\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n\nexport async function generateRecipientKeyPair(): Promise<CryptoKeyPair> {\n return subtle.generateKey(\n { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },\n true,\n ['encrypt', 'decrypt'],\n )\n}\n\nexport async function exportRecipientPublicKeySpki(pair: CryptoKeyPair): Promise<Uint8Array> {\n return new Uint8Array(await subtle.exportKey('spki', pair.publicKey))\n}\n\nexport async function importRecipientPublicKeySpki(spki: Uint8Array): Promise<CryptoKey> {\n return subtle.importKey('spki', spki as BufferSource, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt'])\n}\n\n/** RSA-OAEP-encrypt `bytes` (a 32-byte CEK) to the recipient's public key. */\nexport async function recipientWrap(pub: CryptoKey, bytes: Uint8Array): Promise<Uint8Array> {\n return new Uint8Array(await subtle.encrypt({ name: 'RSA-OAEP' }, pub, bytes as BufferSource))\n}\n\n/**\n * RSA-OAEP-decrypt with the pair's private key. Propagates WebCrypto's own\n * error on failure — callers that need a typed error map it themselves.\n */\nexport async function recipientUnwrap(pair: CryptoKeyPair, wrapped: Uint8Array): Promise<Uint8Array> {\n return new Uint8Array(await subtle.decrypt({ name: 'RSA-OAEP' }, pair.privateKey, wrapped as BufferSource))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\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, type EnclaveKey } from '../crypto.js'\nimport { base64ToBuffer } from '../../plumbing/digest.js'\nimport { TamperedError } from '../../../kernel/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 * Digest-only write engine (C6 rotate branch + notLastN ring, spec §2/§4).\n * Called ONLY by RecordCodec.encryptRecord — both live inside the enclave.\n * @module\n */\nimport { type EnclaveKey } from '../crypto.js'\nimport { generateSalt, bufferToBase64, base64ToBuffer } from '../../plumbing/digest.js'\nimport { pbkdf2VerifyDigest, VDIG_ITERATIONS } from './digest.js'\nimport { normalizeForVerify } from '../../plumbing/normalize.js'\nimport { blindedEqual } from './compare.js'\nimport { sealVdigPayload, openVdigPayload, type VdigPayload, type VdigDigestEntry } from './vdig.js'\nimport { ClassifiedRotationError, TamperedError } from '../../../kernel/errors.js'\nimport type { VdigFieldPolicy } from '../../../kernel/types.js'\n\nexport async function mintVdigSlot(\n rawValue: string,\n policy: VdigFieldPolicy,\n prevBlob: string | undefined,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<string> {\n const normalized = normalizeForVerify(policy.normalize, rawValue)\n\n let prev: VdigPayload | null = null\n if (prevBlob !== undefined) {\n try {\n prev = await openVdigPayload(prevBlob, cek, collection, recordId, field)\n } catch {\n // Any failure opening a prior _vdig slot (auth-tag mismatch, malformed\n // JSON, wrong AAD) is a tamper/false condition — normalize to\n // TamperedError so the write path never sees a distinct error shape.\n throw new TamperedError()\n }\n if (!Number.isInteger(prev.iter) || prev.iter < 1) {\n // A corrupted/tampered iter count would silently weaken (or break)\n // the reuse-check PBKDF2 pass — floor/validate before trusting it.\n throw new TamperedError()\n }\n }\n\n // notLastN reuse refusal: candidate vs cur + every ring entry, each a full\n // PBKDF2 at the payload's own iteration count (n × 600K is the documented\n // write-time cost ceiling, cap 8 — spec Q4).\n if (prev !== null && policy.notLastN > 0) {\n const history: readonly VdigDigestEntry[] = [prev.cur, ...(prev.ring ?? [])]\n for (const entry of history) {\n const digest = await pbkdf2VerifyDigest(normalized, base64ToBuffer(entry.salt), prev.iter)\n if (await blindedEqual(digest, base64ToBuffer(entry.hash))) {\n throw new ClassifiedRotationError(collection, field, 'password was used recently')\n }\n }\n }\n\n const salt = generateSalt()\n const hash = await pbkdf2VerifyDigest(normalized, salt, VDIG_ITERATIONS)\n const ring = prev !== null && policy.notLastN > 0\n ? [...(prev.ring ?? []), { salt: prev.cur.salt, hash: prev.cur.hash }].slice(-policy.notLastN)\n : undefined\n\n const payload: VdigPayload = {\n v: 1,\n alg: 'PBKDF2-SHA256',\n iter: VDIG_ITERATIONS,\n cur: { salt: bufferToBase64(salt), hash: bufferToBase64(hash), at: new Date().toISOString() },\n ...(ring !== undefined && ring.length > 0 ? { ring } : {}),\n }\n return sealVdigPayload(payload, cek, collection, recordId, field)\n}\n","/**\n * `enclave-aes` as a {@link CapsulePrimitives} — the 13 functions the shared\n * plumbing binds to.\n *\n * ⭐ THE ASSIGNMENT BELOW IS A TEST, and it is the reason this file exists as\n * a file rather than an inline object at the `makeCapsule()` call. Declaring\n * the seam type and implementing it are two different acts; nothing checks a\n * hand-written interface against the module it claims to describe unless\n * something binds one to the other. This binding caught two errors in the\n * declaration within minutes of it being written:\n *\n * - `deriveDeterministicKey` was declared `(dek, context)`. It takes ONE\n * argument — the AES capsule derives the context into the IV, not the key.\n * - `bufferToBase64` was declared `(bytes: Uint8Array)`. It also accepts an\n * `ArrayBuffer`, which is what its callers actually pass.\n *\n * Both would have compiled here and failed at the far end of the refactor,\n * where the cause is no longer visible. (The same lesson as `#16`: a\n * structural declaration drifts silently from its implementation until an\n * assignment pins the two together.)\n *\n * ⚠️ Method shorthand in `CapsulePrimitives` is checked BIVARIANTLY, so this\n * assignment does NOT catch parameter-arity drift — that is exactly how the\n * `deriveDeterministicKey` mistake survived the type and was caught by a call\n * site instead. Keep the conformance kit's call-site coverage; this assignment\n * is necessary, not sufficient.\n */\nimport type { CapsulePrimitives } from '../contract.js'\nimport { encrypt, decrypt, encryptBytesWithAAD, decryptBytesWithAAD, encryptDeterministic, deriveDeterministicKey, deriveSealedFieldKey, deriveSealedFieldKeyFromCek, generateDEK, wrapCek, unwrapCek } from './crypto.js'\nimport { bufferToBase64 } from '../plumbing/digest.js'\nimport { mintVdigSlot } from './classify/write.js'\nimport { mintBidxTag } from './classify/bidx.js'\nimport { openVdigPayload, sealVdigPayload } from './classify/vdig.js'\n\nexport const aesPrimitives: CapsulePrimitives = {\n encrypt,\n decrypt,\n encryptBytesWithAAD,\n decryptBytesWithAAD,\n encryptDeterministic,\n deriveDeterministicKey,\n deriveSealedFieldKey,\n deriveSealedFieldKeyFromCek,\n generateDEK,\n wrapCek,\n unwrapCek,\n bufferToBase64,\n mintVdigSlot,\n mintBidxTag,\n openVdigPayload,\n sealVdigPayload,\n}\n","/**\n * The AES capsule, assembled: hub's shared plumbing bound to `aesPrimitives`.\n *\n * `classify/**` stays capsule-private (it is an optional group an exclave\n * refuses) but still needs `dualReadSealedSlot`, which is plumbing. Binding it\n * once here keeps `reveal.ts`/`verify.ts` from each building their own copy —\n * two bindings of the same primitives would work, but they would be two\n * objects, and `instanceof` across them is the class of bug the Stage B door\n * test caught by asserting identity rather than names.\n */\nimport { makeCapsule } from '../plumbing/index.js'\nimport { aesPrimitives } from './primitives.js'\n\nexport const aesCapsule = makeCapsule(aesPrimitives)\nexport const { dualReadSealedSlot } = aesCapsule\n"],"mappings":"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","names":["subtle","subtle"]}
@@ -3,15 +3,15 @@ import {
3
3
  } from "./chunk-XBKH4FOC.js";
4
4
  import {
5
5
  normalizeJoinOn
6
- } from "./chunk-MKXXMNJH.js";
6
+ } from "./chunk-5UUJE4FP.js";
7
7
  import {
8
8
  sha256Hex
9
- } from "./chunk-UYB234ZR.js";
9
+ } from "./chunk-4J3MNVVX.js";
10
10
  import {
11
11
  MaterializedViewConfigError,
12
12
  MaterializedViewSourceUnknownError,
13
13
  RefNotDeclaredError
14
- } from "./chunk-244VXYPG.js";
14
+ } from "./chunk-VSUQI5Q6.js";
15
15
 
16
16
  // src/with-formula/materialized-views/dependency-analyzer.ts
17
17
  function analyzeDependencies(query) {
@@ -574,4 +574,4 @@ export {
574
574
  ungatedMvContext,
575
575
  wrapDbWithPredicates
576
576
  };
577
- //# sourceMappingURL=chunk-NWHBJQAL.js.map
577
+ //# sourceMappingURL=chunk-D4V5JJAK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-formula/materialized-views/dependency-analyzer.ts","../src/with-formula/materialized-views/query-hash.ts","../src/with-formula/materialized-views/projection-legs.ts","../src/with-formula/materialized-views/registry.ts"],"sourcesContent":["import { describeGroupKey, type GroupKey } from '../../kernel/query/reduce/date-trunc.js'\nimport type { Query, QueryPlan } from '../../kernel/query/builder.js'\nimport type { JoinContext, JoinLeg } from '../../kernel/query/relate/join.js'\nimport type { MaterializedViewSpec } from './types.js'\nimport { normalizeJoinOn } from '../../kernel/query/relate/join-on.js'\n\n/**\n * Walks a `Query<T>` plan and returns the set of source collection\n * names that any source-write should trigger a refresh on.\n *\n * Handles:\n * - root collection (the one the query was built from)\n * - FK join targets (`.join(field, { as })`)\n *\n * Also handles:\n * - cross-join targets (`.crossJoin(target, { as })`) — v3\n *\n * Deferred:\n * - `.wherePredicate(name)` — v2 predicate primitive\n * - Overlay-name expansion to {base, overlay}\n *\n * The set is materialized at MV registration time. The MV registry\n * uses it to (a) dispatch `onSourceWrite` only to MVs that actually\n * care, and (b) contribute edges to the shared cycle-detection graph.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function analyzeDependencies(query: Query<any>): Set<string> {\n const deps = new Set<string>()\n const plan = query._plan()\n const ctx = query._joinContext()\n\n // The root collection is always a dependency.\n if (ctx?.leftCollection) {\n deps.add(ctx.leftCollection)\n }\n\n // FK join targets contribute additional sources.\n for (const leg of plan.joins) {\n deps.add(leg.target)\n }\n\n // Cross-join targets are also dependency sources — writes to either side\n // must trigger MV refresh. Symmetric with FK-join target handling above.\n for (const clause of plan.clauses) {\n if (clause.type === 'crossJoin') {\n deps.add(clause.target)\n }\n }\n\n // Sub-plans inside OR clauses can carry nested joins. Walk them.\n // (Today only top-level `.join()` populates `plan.joins`, but the\n // OR-group machinery permits sub-plans, so we recurse defensively.)\n walkClausesForJoins(plan, deps, ctx)\n\n return deps\n}\n\nfunction walkClausesForJoins(\n plan: QueryPlan,\n deps: Set<string>,\n ctx: JoinContext | undefined,\n): void {\n void ctx\n // Today `plan.joins` carries all join legs at top level. Sub-plans\n // inside OR groups don't currently support nested joins, so the loop\n // below is a no-op safety net for future builder extensions.\n for (const clause of plan.clauses) {\n if (clause.type === 'group') {\n // Group clauses don't (yet) carry their own joins; this is a\n // forward-compat anchor for when OR-groups support nested\n // sources.\n }\n }\n}\n\n/**\n * Convenience: produce a stable string summary of the query plan\n * suitable for `queryHash` derivation. Captures everything the\n * dependency analyzer reads + the where/orderBy/limit/offset\n * structure that affects materialized rows.\n *\n * `joinContext` is intentionally NOT included — the join-resolution\n * function references would defeat hash determinism. The set of join\n * TARGETS (collection names) IS included via the plan.joins legs.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function summarizeQueryPlan(query: Query<any>): string {\n const plan = query._plan()\n const ctx = query._joinContext()\n return JSON.stringify({\n root: ctx?.leftCollection ?? null,\n clauses: plan.clauses.map(c => {\n if (c.type === 'crossJoin') {\n return {\n type: 'crossJoin',\n target: c.target,\n as: c.as,\n // Inline on: callback: use sentinel — drift detection disabled for this MV\n onPredicateName: c.onPredicateName ?? (c.on ? '[inline]' : null),\n maxRows: c.maxRows ?? null,\n // `outer` changes the row set, so it is part of the plan's identity\n // for drift detection, not decoration (#1130).\n outer: c.outer ?? false,\n }\n }\n return c\n }),\n orderBy: plan.orderBy,\n limit: plan.limit ?? null,\n offset: plan.offset,\n joins: plan.joins.map(j => summarizeJoinLeg(j)),\n })\n}\n\n/**\n * The per-leg half of {@link summarizeQueryPlan}, split out so the property\n * test in `__tests__/query-join-summary-hash.test.ts` can address it directly.\n *\n * ⭐ **THE RULE FOR GROWING THIS FUNCTION (#1389).** Every key here is inside\n * an already-stored `queryHash`. A key that is emitted unconditionally can\n * never be added, removed, or renamed without moving EVERY joined MV's hash\n * and silently recomputing every one of them. A key that is OMITTED AT ITS\n * DEFAULT can: `JSON.stringify` drops an `undefined` value, so a leg that does\n * not use the feature summarises byte-identically to its pre-change self and\n * only the MVs genuinely using the feature recompute — once, correctly.\n * So: **new keys are omitted at their default, always.** Pinned by the\n * byte-identity test in that file.\n *\n * ⛔ And the converse duty, which is what #1389 actually was: a `JoinLeg` field\n * that changes WHICH ROWS the leg produces and is NOT emitted here makes two\n * semantically different MVs share a hash, so drift detection goes blind and\n * nothing reports it. `direction`, `inner` and `isDictJoin` were all in that\n * state. The exclusion list below is deliberate and each entry states why;\n * the property test fails if a new `JoinLeg` key appears in neither.\n */\nfunction summarizeJoinLeg(j: JoinLeg): Record<string, unknown> {\n return {\n field: j.field,\n as: j.as,\n target: j.target,\n mode: j.mode,\n // #1339 — the declared `on` IS the join's identity: two `.joinOn()`\n // plans differing only here select different rows, and without this key\n // they would hash identically and neither MV would ever be seen as\n // stale. `undefined` for every other leg, and `JSON.stringify` drops an\n // undefined value, so a plan carrying no `joinOn` summarises\n // byte-identically to its pre-#1339 self and no stored hash moves.\n on: j.on,\n // #1389 — `direction` (#1289) decides which SIDE is preserved: a right or\n // full leg emits rows a left leg never produces. Omitted for a left leg,\n // which is `undefined` on the leg itself, so a plain `.join()` plan is\n // byte-identical to its pre-#1389 summary.\n direction: j.direction,\n // #1389 — `inner` (#1361) DROPS every unmatched left row. Only ever\n // `true`; `undefined` on every other leg, so again nothing moves for a\n // plan that does not use it.\n inner: j.inner,\n // #1389 — a dict join attaches `{ ...labels, key }` from the dictionary\n // snapshot instead of a right-side record, and `target` names a\n // dictionary rather than a collection. Two legs can otherwise agree on\n // all four base keys (a dict leg's `target` IS its `field`, which a ref\n // join to a like-named collection reproduces), so without this key they\n // hash identically while materializing different rows. Only ever `true`.\n isDictJoin: j.isDictJoin,\n // ── EXCLUDED, deliberately (#1389). Read before adding one. ──────────\n //\n // `partitionScope` — NOT emitted, and this is the load-bearing one. It is\n // `'all'` on every leg ever built and the executor never reads it\n // (#1342), so it cannot change a row. It is also present unconditionally\n // rather than omitted-at-default, so starting to emit it would move\n // EVERY joined MV's hash at once for zero semantic gain. If a future\n // change ever makes it narrowable, emit it only when it is not `'all'`.\n //\n // `strategy` — NOT emitted: a manual planner override picks HOW the same\n // rows are produced (hash vs lookup), never which. Emitting it would\n // force a full recompute for a pure performance tweak.\n //\n // `maxRows` — NOT emitted: a per-side ceiling that throws\n // `JoinTooLargeError` when crossed. A query that succeeds returns the\n // same rows at any ceiling above its size, so it is not part of the\n // plan's row identity.\n }\n}\n\n/**\n * Canonical string description of a UNION MV's plan, used as input to\n * `computeQueryHash`.\n *\n * Asymmetry note:\n * - Arm collection names are NOT sorted. Declaration order is\n * semantically meaningful for the dedup-only UNION path —\n * `materializeUnionResult` iterates `spec.unionSources` in\n * declaration order and keeps the first-seen row per composite key\n * (tie-break precedence). If we sorted arms here, a consumer who\n * reordered `unionSources` to change precedence would compute the\n * same `queryHash`, refresh would be a no-op, and stale MV rows\n * would persist. Hashing in declaration order makes any reorder\n * trigger a refresh.\n * - `groupBy` fields ARE sorted, by their canonical `describeGroupKey`\n * description (which is the field name itself for a plain key). Multi-key\n * groupBy buckets are commutative (`canonicalGroupKey` produces the same\n * composite key regardless of field order in the input spec).\n * - `aggregate` keys ARE sorted. Reducer-spec keys are independent\n * of each other — order of declaration doesn't change output.\n *\n * Per-arm `map` functions are NOT fingerprinted; consumers must bump\n * the MV's `name` (or rely on application-level cache busting) when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeUnionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return `union(${summarizeUnionArms(spec)})${summarizeGroupingTail(spec)}`\n}\n\n/**\n * Canonical string description of a projection MV's plan (#810), used\n * as input to `computeQueryHash`.\n *\n * Leg descriptors ARE sorted (contrast with UNION arm order): every\n * leg attaches under a distinct `as` alias, forward legs are\n * one-to-one attachments and collect legs are independent\n * hash-grouped passes, so declaration order never changes the\n * materialized rows — a pure reorder must NOT force a refresh.\n * `from` (#1140) constrains the legal orderings (a leg must follow the\n * alias it attaches to) without making order meaningful: each descriptor\n * carries its own `@alias`, and aliases are unique, so the sorted set\n * still determines the leg graph exactly.\n *\n * The projection `map` is NOT fingerprinted (identical limitation as\n * the UNION arm `map`); consumers must bump the MV's `name` when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeProjectionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n const projection = spec.projection!\n const legs = projection.joins\n .map(leg => {\n // #1140 — `from` changes which rows a leg attaches to, so it is part of\n // the plan's identity. Omitting it would let two structurally different\n // projections share a queryHash and silently skip a refresh.\n const at = leg.from !== undefined ? `@${leg.from}` : ''\n return 'collect' in leg\n ? `collect${at}:${leg.collect}.${leg.on}→${leg.as}`\n : `field${at}:${leg.field}→${leg.as}`\n })\n .sort()\n const body = JSON.stringify({ source: projection.source, legs })\n return `projection(${body})${summarizeGroupingTail(spec)}`\n}\n\n/** Arm descriptors for `summarizeUnionPlan` — declaration order preserved (see its doc). */\nfunction summarizeUnionArms<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return (spec.unionSources ?? [])\n .map(s => {\n // Fold each arm's join legs into the summary so adding or\n // reordering joins (which changes the materialized rows) bumps\n // queryHash. Leg order is declaration-significant (legs chain), so\n // it is NOT sorted — same rationale as arm declaration order.\n // #1411 — a declared leg folds its NORMALISED predicate in (the normal\n // form is what makes two logically identical `on`s hash identically, and\n // `normalizeJoinOn` throwing here is what makes a malformed leg fail at\n // registration rather than at the first refresh).\n const joins = s.join?.length\n ? `[${s.join.map(j => 'on' in j\n ? `${j.target}?${JSON.stringify(normalizeJoinOn(j.on, j.target))}${j.mode === 'inner' ? '!' : ''}→${j.as}`\n : `${j.field}→${j.as}`).join(';')}]`\n : ''\n return `${s.collection}${joins}`\n })\n .join(',')\n}\n\n\n/**\n * #1411 — the window's contribution to `queryHash`.\n *\n * ⚠️ **`partitionBy` and `orderBy` are NOT sorted, unlike the groupBy and\n * aggregate keys beside them.** Group buckets are commutative, so sorting them\n * is what stops a re-ordered declaration from forcing a pointless refresh. A\n * window's keys are not: `orderBy: ['a','b']` walks a partition differently\n * from `['b','a']` and produces different running totals, and a partition list\n * feeds the same key-building order. Sorting them here would let a real\n * semantic change reuse a stored `queryHash`, refresh would be a no-op, and\n * the view would serve numbers computed under the old ordering.\n *\n * ⭐ **Returns the EMPTY STRING when no window is declared**, which is the same\n * omitted-at-default discipline `summarizeJoinLeg` states above and for the\n * same reason: every existing MV's stored `queryHash` must stay byte-identical\n * across this change, so only a view that actually declares a window\n * recomputes — once, correctly.\n *\n * `select` KEYS are sorted — they are independent output columns, same\n * rationale as the aggregate keys. The slot VALUES are functions and cannot be\n * hashed; renaming a column or adding one is what this can see, which is the\n * same limit `aggregate` has.\n */\nfunction summarizeWindow<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n if (!spec.window) return ''\n const list = (v: unknown): string => {\n if (v === undefined) return ''\n const arr = Array.isArray(v) ? v : [v]\n return arr\n .map((k) =>\n typeof k === 'object' && k !== null && 'field' in (k as Record<string, unknown>)\n ? `${describeGroupKey((k as { field: GroupKey }).field)}:${String((k as { direction?: string }).direction ?? 'asc')}`\n : describeGroupKey(k as GroupKey),\n )\n .join(',')\n }\n const selectKeys = Object.keys(spec.window.select).sort().join(',')\n return `|window(partition(${list(spec.window.partitionBy)})order(${list(spec.window.orderBy)})select(${selectKeys}))`\n}\n\n/**\n * Shared `|groupBy(…)|aggregate(…)|money(…)` tail for the UNION and\n * projection summaries — both feed the same post-map grouping pipeline,\n * so the same spec fields fold into both hashes with the same rules.\n */\nfunction summarizeGroupingTail<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n // `describeGroupKey` is the identity of a group key: a plain field name maps\n // to itself, and a `dateTrunc()` key (#1350) to a canonical string carrying\n // every parameter that decides which bucket a row lands in. Changing a unit,\n // a timezone or the week start therefore bumps the hash and forces a refresh.\n const groupBy: string = Array.isArray(spec.groupBy)\n ? spec.groupBy.map(describeGroupKey).sort().join(',')\n : spec.groupBy === undefined\n ? ''\n : describeGroupKey(spec.groupBy as GroupKey)\n const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(',') : ''\n // `moneyFields` changes reducer semantics (float → exact BigInt), so\n // declaring / removing / re-keying it must bump queryHash. Keys are\n // sorted — they're independent of each other (one descriptor per\n // output field), same rationale as aggregate keys.\n const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(',') : ''\n return `|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})${summarizeWindow(spec)}`\n}\n","/**\n * Deterministic hash of a materialized view strategy's \"shape\": MV\n * name + canonical query-plan summary + sorted dependency-set.\n *\n * Used to detect strategy drift: a row whose `_materializedFrom.queryHash`\n * doesn't match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps. Mirrors the v1\n * `computeStrategyHash` pattern.\n */\nimport { sha256Hex } from '../../capsule/index.js'\n\nexport async function computeQueryHash(\n mvName: string,\n /**\n * Source-collection set the query depends on. Sorted before\n * canonicalization so set iteration order doesn't affect the hash.\n */\n dependencies: ReadonlySet<string>,\n /**\n * Stringified query-plan summary. The caller produces this from the\n * `Query<T>` builder — concretely: a JSON serialization of clauses +\n * orderBy + limit + offset + joins. Function bodies inside\n * `wherePredicate` are NOT included here (those carry their own\n * `predicateHash` to be folded in by a later sub-issue).\n */\n queryPlanSummary: string,\n): Promise<string> {\n const canonical = JSON.stringify({\n mvName,\n dependencies: [...dependencies].sort(),\n queryPlanSummary,\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n\n/**\n * Canonicalize a query plan for hashing. Walks the plan structure\n * with sorted keys so insertion order doesn't perturb the result.\n * Lives here rather than in `query/builder.ts` to keep that module\n * stable across MV-specific evolutions.\n *\n * @internal exported for testing\n */\nexport function canonicalizeQueryPlan(plan: unknown): string {\n return JSON.stringify(plan, (_key, value) => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n}\n","/**\n * Projection-leg topology (#810, extended by #1140).\n *\n * A projection MV's legs originally all attached to the PRIMARY row: a forward\n * leg named an FK field on `projection.source`, and a reverse `collect` leg's\n * `on` field had to `ref()` the source itself. Anything two FKs away was\n * therefore inexpressible — `bill → entity → client` has no leg shape, because\n * the bill has no `clientId` and `clients.entityId` refs `entities`, not\n * `bills`.\n *\n * `from` closes that: a leg may attach to a previously-declared ALIAS instead of\n * to the primary row. This module owns the two questions that follow from it —\n * *is this leg graph well-formed?* and *which collection does a leg's rows come\n * from?* — so neither the registry nor the executor has to re-derive them.\n *\n * **Why cycles are impossible by construction, and there is no depth cap.**\n * `from` may only name an alias declared EARLIER in `joins`. A leg can therefore\n * never reach forward, the graph is a forest rooted at the primary row, and its\n * depth is bounded by the number of legs. Validation refuses an unknown or\n * not-yet-declared alias, which is the same check.\n */\n\nimport { MaterializedViewConfigError } from '../../kernel/errors.js'\nimport type { ProjectionJoinLeg, ProjectionSpec } from './types.js'\n\n/** Reverse (one-to-many) leg — discriminated on `collect`, as the type is. */\nexport function isCollectLeg(\n leg: ProjectionJoinLeg,\n): leg is Extract<ProjectionJoinLeg, { collect: string }> {\n return 'collect' in leg\n}\n\n/**\n * Refuse a malformed leg graph where `withMaterializedView()` checks the rest of\n * the leg shape — at spec CONSTRUCTION, not at vault open and not at first\n * materialization. Alias wiring is pure configuration: it needs no refs, no\n * records and no store, so the earliest failure is the best one.\n * (Ref EXISTENCE still resolves at first materialization, unchanged — refs\n * cannot exist until after `openVault()` returns.)\n */\nexport function validateProjectionLegs<T extends Record<string, unknown>>(\n mvName: string,\n projection: ProjectionSpec<T>,\n): void {\n // Non-empty and unique `as` are already refused by `withMaterializedView()`\n // itself, earlier and with its own message — not re-checked here. This\n // function owns the `from` wiring and nothing else.\n const seen = new Map<string, ProjectionJoinLeg>()\n for (const leg of projection.joins) {\n if (leg.from !== undefined) {\n const parent = seen.get(leg.from)\n if (!parent) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection leg \"${leg.as}\" declares from: \"${leg.from}\", which is not an ` +\n `alias declared EARLIER in \\`joins\\`. A leg may only attach to the primary row or to a ` +\n `preceding leg — that ordering rule is what makes a cycle impossible. Known so far: ` +\n `${[...seen.keys()].map(a => `\"${a}\"`).join(', ') || '(none)'}`,\n )\n }\n if (isCollectLeg(parent)) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection leg \"${leg.as}\" declares from: \"${leg.from}\", but \"${leg.from}\" ` +\n `is a collect leg and holds an ARRAY of rows, not one record. A leg can only hang off a ` +\n `forward leg (record | null). Attach it to the primary row instead, or collect the ` +\n `far side directly if its ref() reaches \"${projection.source}\".`,\n )\n }\n }\n seen.set(leg.as, leg)\n }\n}\n\n/**\n * The collection a leg's rows come from — the primary source for a root leg, the\n * `from` leg's own target otherwise.\n *\n * Returns `null` when a forward `ref()` somewhere along the chain is not\n * declared yet, which at registration time is the normal case rather than an\n * error: refs are declared after `openVault()` returns, so the registry retries\n * this on every dispatch until it resolves (`_pendingForwardDeps`).\n *\n * `resolveRef` is passed in rather than imported so this module stays free of\n * vault wiring — the registry hands it the ref registry's `getOutbound`, the\n * executor hands it a join context lookup. Both answer the same question.\n */\nexport type RefLookup = (collection: string, field: string) => { target: string } | null\n\nexport function resolveLegOwner(\n leg: ProjectionJoinLeg,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n source: string,\n resolveRef: RefLookup,\n): string | null {\n if (leg.from === undefined) return source\n const parent = joins.find(l => l.as === leg.from)\n if (!parent) return null // refused by validateProjectionLegs; defensive here\n if (isCollectLeg(parent)) return parent.collect // refused too, same reason\n const parentOwner = resolveLegOwner(parent, joins, source, resolveRef)\n if (parentOwner === null) return null\n return resolveRef(parentOwner, parent.field)?.target ?? null\n}\n\n/**\n * The collection a FORWARD leg resolves INTO — i.e. its dependency. `null` while\n * any ref in the chain (including this leg's own) is still undeclared.\n */\nexport function resolveForwardLegTarget(\n leg: Extract<ProjectionJoinLeg, { field: string }>,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n source: string,\n resolveRef: RefLookup,\n): string | null {\n const owner = resolveLegOwner(leg, joins, source, resolveRef)\n if (owner === null) return null\n return resolveRef(owner, leg.field)?.target ?? null\n}\n","import { MaterializedViewSourceUnknownError, RefNotDeclaredError } from '../../kernel/errors.js'\nimport type { ViaGraph, FieldRef, EdgeKind, Grain } from '../../kernel/via/graph.js'\nimport type { Clause, FieldClause } from '../../kernel/query/predicate.js'\nimport type { DeclaredPredicate } from '../../kernel/query/builder.js'\nimport { analyzeDependencies, summarizeQueryPlan, summarizeUnionPlan, summarizeProjectionPlan } from './dependency-analyzer.js'\nimport { computeQueryHash } from './query-hash.js'\nimport { isCollectLeg, resolveForwardLegTarget } from './projection-legs.js'\nimport type { MaterializedViewSpec, MVQueryContext } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2) — MUST match\n * `derivations/registry.ts`'s and `vault.ts`'s overlay-edge marker so\n * cross-registry edges (a derivation feeding an MV, or vice versa) resolve\n * to the SAME graph node. See that file's `WHOLE_RECORD` doc comment.\n */\nconst WHOLE_RECORD = '*'\n\n/**\n * One registered MV strategy alongside its derived metadata. Stored\n * type-erased on `TRow` so the registry can hold heterogeneous MVs.\n */\nexport interface RegisteredMV {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly spec: MaterializedViewSpec<any>\n /** Output collection name (`spec.output?.collection ?? spec.name`). */\n readonly outputCollection: string\n /** Set of source collections; populated at registration via the analyzer. */\n readonly dependencies: ReadonlySet<string>\n /** Canonical `queryHash` — `_materializedFrom.queryHash` for every emitted row. */\n readonly queryHash: string\n /**\n * Top-level FieldClauses on the partition field, captured at\n * registration time. Used by the cycle detector to resolve\n * same-collection-as-source edges via the partition-discriminator\n * check. Empty when `spec.output?.partition` is undefined.\n */\n readonly partitionClauses: readonly FieldClause[]\n /**\n * #638 Task 2 — `'record'` for a row-per-source-row Query<T> or\n * `unionSources` MV, `'aggregate'` for a `.groupBy().aggregate()` MV\n * (the shape that requires explicit `sources`, since the dependency\n * analyzer can't walk an aggregate plan). Feeds `edges()`'s grain.\n */\n readonly grain: Grain\n}\n\n/**\n * Vault-internal registry of MV strategies. Owned by `Vault`; not\n * exported. Parallel to v1's `DerivationRegistry`; the two graphs share\n * a single cycle-detection pass at vault open (see `validate`).\n *\n * @internal\n */\nexport class MaterializedViewRegistry {\n /** Keyed by `spec.name`. */\n private readonly _byName = new Map<string, RegisteredMV>()\n /** Keyed by dependency source-collection → MVs that depend on it. */\n private readonly _bySource = new Map<string, RegisteredMV[]>()\n /**\n * Projection MVs (#810) whose forward-leg ref() targets could not be\n * resolved at registration time. A forward leg names only the FK\n * FIELD; its dependency is the field's ref() TARGET, declared on the\n * source collection — but registration runs at vault open, BEFORE\n * user code declares collections (and their refs). Constructing the\n * source collection here to look them up would be worse: a later\n * `vault.collection(source, { refs })` call only registers refs on a\n * cache miss, so an early construction would silently drop them.\n * Instead each entry carries a non-constructive ref-registry probe;\n * `mvsForSource` retries on every dispatch until every forward\n * target has resolved (see `_resolvePendingForwardDeps`).\n */\n private readonly _pendingForwardDeps: Array<{\n reg: RegisteredMV\n /** Forward-leg ALIASES still awaiting resolution (#1140 — a leg is identified\n * by its alias, not its field: with `from`, two legs may name the same FK\n * field on different collections). */\n aliases: Set<string>\n resolveTarget: (alias: string) => string | null\n }> = []\n\n /**\n * Query-form strategies whose `query()` callback could not be planned at\n * registration time because a `.join()` names a field whose `ref()` is not\n * declared YET (#1139).\n *\n * Registration runs inside `openVault()`; a collection's refs can only be\n * declared afterwards, so for a query-form MV that joins a declared FK there\n * is NO call ordering the consumer can choose — the plan is simply built too\n * early. Rather than failing the vault open, the strategy is parked here and\n * replanned from {@link resolvePendingPlans}, which every write dispatch and\n * `vault.refreshView()` call runs first. This is the query-form counterpart\n * of `_pendingForwardDeps`, one level up: that defers a leg's TARGET, this\n * defers the whole plan.\n *\n * Only `RefNotDeclaredError` parks a strategy. Every other planning failure\n * still throws out of `openVault()` exactly as before, so a typo'd source or\n * a malformed spec keeps its loud, early error.\n *\n * A parked strategy is invisible until it resolves: it contributes no\n * `_bySource` entries and no cycle-detection edges (same caveat as a\n * late-resolved forward target — `edges()` runs once at vault open). It also\n * cannot be dispatched, which is why {@link hasPendingPlans} forces the\n * conservative branch in `Collection._txAtomicSafe`: the atomic write path\n * skips MV dispatch, and skipping it here would mean the write that was\n * supposed to trigger the first replan is the one that gets missed.\n */\n private readonly _pendingPlans: Array<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewSpec<any>\n db: MVQueryContext\n options?: { knownCollections?: (name: string) => boolean }\n }> = []\n\n /**\n * Register an MV. Invokes `spec.query()` once at registration time to\n * read the plan + join context; the resulting `Query<T>` is discarded\n * after dependency extraction. `vault.collection(...)` must therefore\n * be functional by the time this runs — typically wired from\n * `Vault._initMaterializedViews` after collection bootstrap.\n *\n * Throws `MaterializedViewSourceUnknownError` if the analyzer\n * surfaces a dependency the vault doesn't know about (when a\n * `knownCollections` checker is supplied).\n */\n async register(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewSpec<any>,\n db: MVQueryContext,\n options?: { knownCollections?: (name: string) => boolean },\n ): Promise<void> {\n // Build a predicate-aware db wrapper. If `spec.predicates` is\n // declared, the wrapper intercepts `.collection().query()` and\n // attaches the predicates map to the resulting Query<T>. With no\n // predicates declared, the wrapper is the original db unchanged.\n const ungatedDb = ungatedMvContext(db) // #1414 — the engine keeps its pre-gate source; see ungatedMvContext\n const dbForQuery = spec.predicates ? wrapDbWithPredicates(ungatedDb, spec.predicates) : ungatedDb\n\n // Invoke the query callback once to inspect its plan / dependencies.\n // For Query<T> shapes the analyzer extracts deps + plan summary\n // automatically. Reduction / GroupedReduction shapes don't\n // expose the underlying Query, so the spec must declare `sources`\n // explicitly. `partitionClauses` are only populated for Query<T>\n // since same-collection-partition is a non-aggregate concern.\n // UNION-form strategies: dependencies and plan summary come\n // straight off the strategy — no `query` callback to introspect.\n // The dependency-analyzer + summarizer are bypassed entirely; the\n // executor handles materialization via `materializeUnionResult`.\n let dependencies: Set<string>\n let queryPlanSummary: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let qAny: any = null\n let isQuery = false\n // Projection-form (#810): forward legs whose ref() target wasn't\n // resolvable at registration time — folded in lazily, see\n // `_pendingForwardDeps`.\n const pendingForwardFields: string[] = []\n if (spec.unionSources) {\n dependencies = new Set(spec.unionSources.map(s => s.collection))\n // Per-arm joins resolve right-side collections that aren't among\n // the arm `collection`s. The consumer lists those in `sources`;\n // fold them into the dependency set so a write to a join-target\n // collection triggers MV refresh (and contributes a cycle edge).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n // #1411 — a declared join names its target literally, so it needs no\n // `sources` entry to be a dependency (a ref join's target is only known\n // once the ref is declared, which is why THOSE must be listed).\n for (const arm of spec.unionSources) for (const leg of arm.join ?? []) if ('on' in leg) dependencies.add(leg.target)\n queryPlanSummary = summarizeUnionPlan(spec)\n } else if (spec.projection) {\n // Projection-form (#810): dependencies are all AUTO — the primary\n // source, every collect leg's collection (both literal names), and\n // every forward leg's ref() target. Forward targets usually can't\n // resolve yet (refs are declared after vault open) — those go\n // through the pending-resolution path below. Explicit `sources`\n // remains additive, same as the other two forms.\n const projection = spec.projection\n dependencies = new Set([projection.source])\n for (const leg of projection.joins) {\n // A collect leg's dependency is its literal collection name, whatever it\n // attaches to — which is why `from` needs no new dependency machinery on\n // that side. A forward leg's dependency is a ref() target, still unknown.\n if (isCollectLeg(leg)) dependencies.add(leg.collect)\n else pendingForwardFields.push(leg.as)\n }\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n queryPlanSummary = summarizeProjectionPlan(spec)\n } else {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any\n try {\n q = spec.query!(dbForQuery)\n } catch (e) {\n // #1139 — a join on a ref that is not declared YET. Park and replan;\n // see `_pendingPlans`. Anything else is a real registration failure.\n if (!(e instanceof RefNotDeclaredError)) throw e\n this._pendingPlans.push({ spec, db, ...(options !== undefined ? { options } : {}) })\n return\n }\n qAny = q // already `any` — see the declaration above\n isQuery = typeof qAny._plan === 'function'\n if (isQuery) {\n // `q` is the `Query` arm of the union here (runtime-confirmed via\n // `qAny._plan` above); reuse the already-`any` `qAny` the block uses\n // for `_plan()` rather than re-narrowing.\n dependencies = analyzeDependencies(qAny)\n queryPlanSummary = summarizeQueryPlan(qAny)\n // Fold `.wherePredicate(name, ctx)` references into the plan\n // summary so predicate function or ctx changes (signalled by\n // bumping `hash` or supplying a different ctx) propagate into\n // `queryHash` and force refresh on next visit.\n const predicateRefs = extractPredicateRefs(qAny._plan())\n if (predicateRefs.length > 0) {\n queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs })\n }\n // If `sources` is ALSO declared, take the union (consumer's\n // explicit list extends the auto-analyzed set).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n } else {\n // Aggregate shape: require explicit `sources`.\n if (!spec.sources || spec.sources.length === 0) {\n throw new Error(\n `withMaterializedView \"${spec.name}\": query() returned an aggregate ` +\n `(Reduction or GroupedReduction) but no \\`sources\\` field is declared. ` +\n `The dependency analyzer cannot walk through groupBy().aggregate() ` +\n `back to the source — declare sources: [...] explicitly.`,\n )\n }\n dependencies = new Set(spec.sources)\n // Aggregate plans don't carry a chainable query plan for summary\n // purposes; the dep-set + spec.name serve as the queryHash inputs.\n queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() })\n }\n }\n\n // Sanity-check declared dependencies against the vault's known\n // collections. Optional — when the checker isn't supplied (test\n // wiring, in-process composition) the registration succeeds and\n // any typo surfaces at first onSourceWrite as a no-op.\n if (options?.knownCollections) {\n for (const dep of dependencies) {\n if (!options.knownCollections(dep)) {\n throw new MaterializedViewSourceUnknownError(spec.name, dep)\n }\n }\n }\n\n const outputCollection = spec.output?.collection ?? spec.name\n const queryHash = await computeQueryHash(spec.name, dependencies, queryPlanSummary)\n // For same-collection-as-source MVs, capture the where-clauses on\n // the partition field so cycle detection can prove disjointness.\n // Only applicable to Query<T> shapes — aggregate MVs don't carry\n // a chainable plan to inspect (and same-collection aggregation\n // doesn't make sense for same-collection aggregation).\n const partitionClauses: FieldClause[] = []\n const partitionField = spec.output?.partition?.field\n if (partitionField !== undefined && isQuery) {\n const plan = qAny._plan()\n for (const clause of plan.clauses) {\n if (isFieldClauseOnField(clause, partitionField)) partitionClauses.push(clause)\n }\n }\n // #638 Task 2 — 'aggregate' only for the explicit-sources aggregate shape\n // (groupBy().aggregate() with no chainable plan); a row-per-source-row\n // Query<T>, unionSources, or projection MV is 'record'.\n const grain: Grain = spec.unionSources || spec.projection || isQuery ? 'record' : 'aggregate'\n const reg: RegisteredMV = { spec, outputCollection, dependencies, queryHash, partitionClauses, grain }\n\n this._byName.set(spec.name, reg)\n for (const dep of dependencies) {\n const arr = this._bySource.get(dep)\n if (arr) arr.push(reg)\n else this._bySource.set(dep, [reg])\n }\n if (pendingForwardFields.length > 0) {\n // Non-constructive ref-registry probe: the Vault (the real\n // MVQueryContext) carries its RefRegistry as a private field,\n // reachable structurally at runtime. Test stubs without one simply\n // never resolve — forward deps then come only from explicit `sources`.\n const refRegistry = (db as unknown as {\n refRegistry?: { getOutbound(collection: string): Record<string, { target: string }> }\n }).refRegistry\n const projectionSpec = spec.projection!\n const resolveRef = (c: string, f: string): { target: string } | null =>\n refRegistry?.getOutbound(c)[f] ?? null\n this._pendingForwardDeps.push({\n reg,\n aliases: new Set(pendingForwardFields),\n resolveTarget: (alias) => {\n const leg = projectionSpec.joins.find(l => l.as === alias)\n if (!leg || isCollectLeg(leg)) return null\n // Walks the `from` chain — every hop needs its own ref declared, so a\n // two-hop leg simply resolves one dispatch later than a one-hop one.\n return resolveForwardLegTarget(leg, projectionSpec.joins, projectionSpec.source, resolveRef)\n },\n })\n // Immediate attempt — covers refs already declared by the time\n // this MV registers (re-registration flows, test wiring).\n this._resolvePendingForwardDeps()\n }\n }\n\n /**\n * `true` while any query-form strategy is still unplanned (#1139). Read by\n * `Collection._txAtomicSafe`, which must refuse the atomic write path while\n * this holds — see the `_pendingPlans` doc comment for why.\n */\n hasPendingPlans(): boolean {\n return this._pendingPlans.length > 0\n }\n\n /**\n * Replan every parked query-form strategy (#1139). Called at the head of both\n * write dispatchers and of `vault.refreshView()`, so a strategy resolves on the\n * first write or refresh after its source collection's refs are declared —\n * which is the earliest moment it could produce a correct plan, and strictly\n * before the first moment it could have any output to produce.\n *\n * Each entry is handed straight back to {@link register}, so a strategy that\n * is STILL unplannable simply re-parks itself and the next dispatch tries\n * again. Idempotent and cheap: the array empties as strategies resolve, and\n * both callers skip the await entirely once it is empty.\n *\n * A late-resolved strategy does NOT retro-feed `edges()` — the unified cycle\n * pass runs once at vault open, before refs exist. Same caveat, and the same\n * reason, as a late-resolved forward-leg target.\n */\n async resolvePendingPlans(): Promise<void> {\n if (this._pendingPlans.length === 0) return\n // Splice first: `register` re-parks anything still unplannable, and reading\n // a live array it is concurrently pushing to would loop.\n const parked = this._pendingPlans.splice(0)\n for (const entry of parked) {\n await this.register(entry.spec, entry.db, entry.options)\n }\n }\n\n /** All MVs that depend on `source`, in registration order. */\n mvsForSource(source: string): ReadonlyArray<RegisteredMV> {\n if (this._pendingForwardDeps.length > 0) this._resolvePendingForwardDeps()\n return this._bySource.get(source) ?? []\n }\n\n /**\n * Retry ref() resolution for every pending projection forward leg\n * (#810). A field resolves once its source collection's refs have\n * been declared; the target then folds into the owning MV's\n * dependency set and `_bySource`, so subsequent writes to it\n * dispatch a refresh. Idempotent and cheap — the pending list\n * empties as fields resolve, and `mvsForSource` skips the call\n * entirely once it's empty.\n *\n * Late-resolved targets do NOT retro-feed `edges()` — the cycle\n * pass runs once at vault open, before refs exist. A cycle routed\n * exclusively through a forward leg is therefore not detected at\n * open (collect + source edges, the literal names, are).\n */\n private _resolvePendingForwardDeps(): void {\n for (let i = this._pendingForwardDeps.length - 1; i >= 0; i--) {\n const entry = this._pendingForwardDeps[i]!\n for (const alias of [...entry.aliases]) {\n const target = entry.resolveTarget(alias)\n if (target === null) continue\n entry.aliases.delete(alias)\n if (entry.reg.dependencies.has(target)) continue\n // `dependencies` is declared ReadonlySet for consumers; the\n // registry owns the underlying Set and is the one writer.\n ;(entry.reg.dependencies as Set<string>).add(target)\n const arr = this._bySource.get(target)\n if (arr) arr.push(entry.reg)\n else this._bySource.set(target, [entry.reg])\n }\n if (entry.aliases.size === 0) this._pendingForwardDeps.splice(i, 1)\n }\n }\n\n /** Single MV by name, or `undefined`. */\n byName(name: string): RegisteredMV | undefined {\n return this._byName.get(name)\n }\n\n /** Iterate over every registered MV. */\n all(): ReadonlyArray<RegisteredMV> {\n return [...this._byName.values()]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'mv'` edge per registered MV — target\n * = the output collection (a `WHOLE_RECORD` artifact node), sources =\n * every dependency (also `WHOLE_RECORD` nodes). Same-collection edges\n * (`dep === outputCollection`) are skipped IFF the MV declares an\n * `output.partition` discriminator AND the query has a where-clause that\n * provably excludes the partition value (`partitionDisjoint`) — the SAME\n * condition the old local DFS used. Grain comes from the registration-time\n * `RegisteredMV.grain`.\n */\n edges(): ReadonlyArray<{ readonly target: FieldRef; readonly sources: readonly FieldRef[]; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; sources: FieldRef[]; kind: EdgeKind; grain: Grain }> = []\n for (const reg of this._byName.values()) {\n const sources: FieldRef[] = []\n for (const dep of reg.dependencies) {\n if (dep === reg.outputCollection && partitionDisjoint(reg)) continue\n sources.push({ collection: dep, field: WHOLE_RECORD })\n }\n if (sources.length === 0) continue\n out.push({ target: { collection: reg.outputCollection, field: WHOLE_RECORD }, sources, kind: 'mv', grain: reg.grain })\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). `graph` is the caller's shared per-vault graph,\n * ALREADY carrying this registry's `edges()` AND `DerivationRegistry`'s\n * (registered by the caller — see `Vault._initMaterializedViews`, which\n * runs after `_initDerivations`, so a pure-derivation cycle already threw\n * `DerivationCycleError` there; any cycle surfacing here necessarily\n * touches an MV edge). Throws `MaterializedViewCycleError` — SAME class as\n * before.\n */\n validate(graph: ViaGraph): void {\n graph.assertAcyclic()\n }\n}\n\n/**\n * Type guard: is the clause a top-level `FieldClause` on the given\n * field? Used by the partition-disjoint check.\n *\n * @internal\n */\nfunction isFieldClauseOnField(clause: Clause, field: string): clause is FieldClause {\n return clause.type === 'field' && clause.field === field\n}\n\n/**\n * #1414 — wrap an `MVQueryContext` so its `.collection().query()` returns a\n * query with the cold-collection gate removed.\n *\n * The MV engine reads its sources synchronously: registration inspects the\n * plan the spec callback returns, and the executor's scan runs from inside a\n * write commit. Both predate #1414 and both have always read whatever the\n * in-memory cache held. Gating them would convert a pre-existing (and\n * separately tracked) staleness into a thrown error on a write path nobody\n * pointed at a query — so the engine keeps its old source and #1414's change\n * stays on the surface a consumer or a guard actually calls.\n *\n * @internal\n */\nexport function ungatedMvContext(db: MVQueryContext): MVQueryContext {\n return {\n // ⚠️ Every argument is forwarded. A spec's `db.collection(name, { tiers })`\n // supplies the options at FIRST construction (first-construction wins), so\n // a wrapper that drops the second argument silently un-declares them.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collection(name: string, ...rest: any[]): any {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const c = (db.collection as any)(name, ...rest) as Record<string | symbol, unknown>\n return new Proxy(c, {\n get(target, prop, receiver) {\n if (prop === 'query') {\n return (...args: unknown[]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = (target.query as any)(...args)\n // The legacy predicate overload returns `T[]`, not a Query.\n return q && typeof q._ungated === 'function' ? q._ungated() : q\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n },\n }\n}\n\n/**\n * Wrap an `MVQueryContext` so its `.collection().query()` returns a\n * Query<T> with the MV's declared predicates attached. Bare Queries\n * (outside of any MV) don't gain `.wherePredicate()` — only Queries\n * obtained through this wrapped db do.\n *\n * @internal\n */\nexport function wrapDbWithPredicates(\n db: MVQueryContext,\n predicates: NonNullable<MaterializedViewSpec<Record<string, unknown>>['predicates']>,\n): MVQueryContext {\n // Build the predicate map once — the fn signature in the MV spec\n // is row-typed but the QueryBuilder casts to unknown, so we widen\n // here for the Map.\n const map = new Map<string, DeclaredPredicate>()\n for (const [name, decl] of Object.entries(predicates)) {\n map.set(name, {\n hash: decl.hash,\n fn: decl.fn as (record: unknown, ctx?: unknown) => boolean,\n })\n }\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collection<T extends Record<string, unknown>>(name: string): any {\n const c = db.collection<T>(name)\n // Return an object that delegates everything to `c` but\n // overrides `.query()` to attach predicates via the new\n // `Query._withPredicates()` accessor.\n return new Proxy(c, {\n get(target, prop, receiver) {\n if (prop === 'query') {\n return (...args: unknown[]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = (target.query as any)(...args)\n // For non-aggregate Query<T>, attach predicates. For\n // legacy predicate-arg overload that returns T[] (sync\n // filter), pass through unchanged.\n \n if (q && typeof q._withPredicates === 'function') {\n return q._withPredicates(map)\n }\n return q\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n },\n }\n}\n\n/**\n * Walk a QueryPlan's clauses and collect predicate-reference markers\n * for `queryHash` derivation. Returns a sorted array (deterministic\n * order) of `{ name, predicateHash, ctxHash }` tuples — these are the\n * hashable identity of each `.wherePredicate()` call site.\n *\n * @internal\n */\nfunction extractPredicateRefs(\n plan: { clauses: readonly Clause[] },\n): Array<{ name: string; predicateHash: string; ctxHash: string }> {\n const refs: Array<{ name: string; predicateHash: string; ctxHash: string }> = []\n const walk = (clauses: readonly Clause[]): void => {\n for (const c of clauses) {\n if (c.type === 'wherePredicate') {\n refs.push({ name: c.name, predicateHash: c.predicateHash, ctxHash: c.ctxHash })\n } else if (c.type === 'group') {\n walk(c.clauses)\n }\n }\n }\n walk(plan.clauses)\n // Stable-sort by (name, predicateHash, ctxHash) — same predicate\n // appearing twice with different ctx hashes both flow through.\n refs.sort((a, b) => {\n if (a.name !== b.name) return a.name < b.name ? -1 : 1\n if (a.predicateHash !== b.predicateHash) return a.predicateHash < b.predicateHash ? -1 : 1\n return a.ctxHash < b.ctxHash ? -1 : a.ctxHash > b.ctxHash ? 1 : 0\n })\n return refs\n}\n\n/**\n * Provability check for the same-collection partition-discriminator.\n * Returns `true` when\n * the captured partition clauses on the MV's query provably exclude\n * the partition's value — meaning the input filter and the output\n * partition are disjoint and the same-collection edge isn't really a\n * cycle.\n *\n * Supported provability shapes (narrow on purpose — DERIV-PP30-001\n * is the load-bearing case):\n *\n * - `.where(field, '==', X)` where X !== partition.value → disjoint\n * - `.where(field, '!=', partition.value)` → disjoint\n * - `.where(field, 'in', [...])` where partition.value NOT in list → disjoint\n *\n * Anything else (no clause on the partition field, an 'in' list that\n * contains partition.value, unsupported operators) → not disjoint,\n * the cycle detector surfaces `MaterializedViewCycleError`.\n *\n * @internal\n */\nfunction partitionDisjoint(reg: RegisteredMV): boolean {\n const partition = reg.spec.output?.partition\n if (partition === undefined) return false\n const value = partition.value\n // The OR-semantics of multiple where-clauses on the same field\n // would muddy this check. v2 only treats AND-chained clauses;\n // any clause that proves disjoint is sufficient.\n for (const c of reg.partitionClauses) {\n if (c.op === '==' && c.value !== value) return true\n if (c.op === '!=' && c.value === value) return true\n if (c.op === 'in' && Array.isArray(c.value)) {\n const list = c.value as readonly unknown[]\n if (!list.includes(value)) return true\n }\n }\n return false\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +1,16 @@
1
1
  import {
2
2
  buildRecordAad,
3
- openEnvelopeJson
4
- } from "./chunk-KJFW62P4.js";
5
- import {
6
- buildRecordEnvelope
7
- } from "./chunk-SEE332EV.js";
8
- import {
9
- encrypt
10
- } from "./chunk-UYB234ZR.js";
3
+ buildRecordEnvelope,
4
+ encrypt,
5
+ generateSigningKeyPair,
6
+ openEnvelopeJson,
7
+ sha256Hex
8
+ } from "./chunk-4J3MNVVX.js";
11
9
  import {
12
10
  ConflictError
13
- } from "./chunk-244VXYPG.js";
11
+ } from "./chunk-VSUQI5Q6.js";
14
12
 
15
13
  // src/with-audit/attestation/signer.ts
16
- import { generateDocSigningKeyPair } from "@noy-db/attestation";
17
14
  var ATTESTATIONS_COLLECTION = "_attestations";
18
15
  var SIGNER_RECORD_ID = "_signer";
19
16
  var REVOKED_RECORD_ID = "_revoked";
@@ -28,7 +25,9 @@ async function loadOrCreateSigner(store, vault, getDEK) {
28
25
  const existing = await loadSigner(store, vault, getDEK);
29
26
  if (existing) return existing;
30
27
  const dek = await getDEK(ATTESTATIONS_COLLECTION);
31
- const signer = await generateDocSigningKeyPair();
28
+ const { publicKeyB64, privateKeyPkcs8B64 } = await generateSigningKeyPair();
29
+ const keyId = (await sha256Hex(new TextEncoder().encode(publicKeyB64))).slice(0, 16);
30
+ const signer = { keyId, publicKeyB64, privateKeyPkcs8B64 };
32
31
  const identity = { collection: ATTESTATIONS_COLLECTION, id: SIGNER_RECORD_ID, version: 1 };
33
32
  const { iv, data } = await encrypt(JSON.stringify(signer), dek, buildRecordAad(identity));
34
33
  const env = buildRecordEnvelope(
@@ -55,4 +54,4 @@ export {
55
54
  loadSigner,
56
55
  loadOrCreateSigner
57
56
  };
58
- //# sourceMappingURL=chunk-QWFHIOD2.js.map
57
+ //# sourceMappingURL=chunk-D5A3GCZ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-audit/attestation/signer.ts"],"sourcesContent":["import type { NoydbStore } from '../../kernel/types.js'\nimport {\n buildRecordAad, buildRecordEnvelope, encrypt, openEnvelopeJson,\n generateSigningKeyPair, sha256Hex, type EnclaveKey,\n} from '../../capsule/index.js'\nimport { ConflictError } from '../../kernel/errors.js'\n\nexport const ATTESTATIONS_COLLECTION = '_attestations'\nexport const SIGNER_RECORD_ID = '_signer'\nexport const REVOKED_RECORD_ID = '_revoked'\n\nexport interface DocSigner {\n readonly keyId: string\n readonly publicKeyB64: string\n readonly privateKeyPkcs8B64: string\n}\n\n/**\n * Pure read: return the firm's persisted document-signing keypair, or `null`\n * if none has been minted yet. Never writes — callers that must NOT mint\n * (e.g. an ungated public-key getter) use this instead of `loadOrCreateSigner`.\n *\n * Stored as an encrypted record `_attestations/_signer` under the\n * `_attestations` collection DEK (resolved via `getDEK`, which is\n * AES-KW-wrapped under the owner KEK + persisted by the keyring). The KEK\n * itself is AES-KW-only and cannot AES-GCM-encrypt these bytes — hence\n * storage under a normal collection DEK.\n */\nexport async function loadSigner(\n store: NoydbStore,\n vault: string,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n): Promise<DocSigner | null> {\n const existing = await store.get(vault, ATTESTATIONS_COLLECTION, SIGNER_RECORD_ID)\n if (!existing) return null\n const dek = await getDEK(ATTESTATIONS_COLLECTION)\n const json = await openEnvelopeJson({ collection: ATTESTATIONS_COLLECTION, id: SIGNER_RECORD_ID }, existing, dek)\n return JSON.parse(json) as DocSigner\n}\n\n/**\n * Lazily mint (or load) the firm's Ed25519 document-signing keypair.\n *\n * On a concurrent first-mint, two callers can both read `null` and both mint\n * distinct keypairs. The `put(…, expectedVersion: 0)` (\"must not already\n * exist\") lets exactly one win; the loser catches `ConflictError`, re-reads,\n * and returns the winner's signer — converging on a single keypair rather than\n * clobbering it or surfacing a raw conflict. (All real stores treat a missing\n * record + `ev: 0` as a no-conflict write, so the catch fires on lost-race only.)\n */\nexport async function loadOrCreateSigner(\n store: NoydbStore,\n vault: string,\n getDEK: (collection: string) => Promise<EnclaveKey>,\n): Promise<DocSigner> {\n const existing = await loadSigner(store, vault, getDEK)\n if (existing) return existing\n\n const dek = await getDEK(ATTESTATIONS_COLLECTION)\n const { publicKeyB64, privateKeyPkcs8B64 } = await generateSigningKeyPair()\n // keyId: first 16 hex chars of sha256(publicKeyB64) — attestation's\n // `keyIdFor`, computed here so hub does not import it at runtime.\n const keyId = (await sha256Hex(new TextEncoder().encode(publicKeyB64))).slice(0, 16)\n const signer: DocSigner = { keyId, publicKeyB64, privateKeyPkcs8B64 }\n const identity = { collection: ATTESTATIONS_COLLECTION, id: SIGNER_RECORD_ID, version: 1 }\n const { iv, data } = await encrypt(JSON.stringify(signer), dek, buildRecordAad(identity))\n const env = buildRecordEnvelope(\n identity,\n { iv, data },\n )\n try {\n await store.put(vault, ATTESTATIONS_COLLECTION, SIGNER_RECORD_ID, env, 0)\n return signer\n } catch (e) {\n if (!(e instanceof ConflictError)) throw e\n // Lost the race — another writer minted first. Adopt the winner.\n const winner = await loadSigner(store, vault, getDEK)\n if (!winner) {\n throw new ConflictError(0, 'loadOrCreateSigner: signer mint lost a concurrent race but the winning record could not be re-read.')\n }\n return winner\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAOO,IAAM,0BAA0B;AAChC,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAmBjC,eAAsB,WACpB,OACA,OACA,QAC2B;AAC3B,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,yBAAyB,gBAAgB;AACjF,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,MAAM,MAAM,OAAO,uBAAuB;AAChD,QAAM,OAAO,MAAM,iBAAiB,EAAE,YAAY,yBAAyB,IAAI,iBAAiB,GAAG,UAAU,GAAG;AAChH,SAAO,KAAK,MAAM,IAAI;AACxB;AAYA,eAAsB,mBACpB,OACA,OACA,QACoB;AACpB,QAAM,WAAW,MAAM,WAAW,OAAO,OAAO,MAAM;AACtD,MAAI,SAAU,QAAO;AAErB,QAAM,MAAM,MAAM,OAAO,uBAAuB;AAChD,QAAM,EAAE,cAAc,mBAAmB,IAAI,MAAM,uBAAuB;AAG1E,QAAM,SAAS,MAAM,UAAU,IAAI,YAAY,EAAE,OAAO,YAAY,CAAC,GAAG,MAAM,GAAG,EAAE;AACnF,QAAM,SAAoB,EAAE,OAAO,cAAc,mBAAmB;AACpE,QAAM,WAAW,EAAE,YAAY,yBAAyB,IAAI,kBAAkB,SAAS,EAAE;AACzF,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,QAAQ,KAAK,UAAU,MAAM,GAAG,KAAK,eAAe,QAAQ,CAAC;AACxF,QAAM,MAAM;AAAA,IACV;AAAA,IACA,EAAE,IAAI,KAAK;AAAA,EACb;AACA,MAAI;AACF,UAAM,MAAM,IAAI,OAAO,yBAAyB,kBAAkB,KAAK,CAAC;AACxE,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,EAAE,aAAa,eAAgB,OAAM;AAEzC,UAAM,SAAS,MAAM,WAAW,OAAO,OAAO,MAAM;AACpD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,cAAc,GAAG,qGAAqG;AAAA,IAClI;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CollectionNotHydratedError
3
- } from "./chunk-244VXYPG.js";
3
+ } from "./chunk-VSUQI5Q6.js";
4
4
 
5
5
  // src/kernel/query/hydration.ts
6
6
  var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
@@ -50,4 +50,4 @@ function gateTerminal(gate, terminal, compute) {
50
50
  export {
51
51
  gateTerminal
52
52
  };
53
- //# sourceMappingURL=chunk-5O7D6ZTW.js.map
53
+ //# sourceMappingURL=chunk-DHU2AQ6T.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildRecordEnvelope
3
- } from "./chunk-SEE332EV.js";
3
+ } from "./chunk-4J3MNVVX.js";
4
4
 
5
5
  // src/with-party/policy/storage.ts
6
6
  var META_COLLECTION = "_meta";
@@ -9,7 +9,7 @@ async function loadVaultPolicy(store, vault) {
9
9
  const envelope = await store.get(vault, META_COLLECTION, POLICY_RECORD_ID);
10
10
  if (!envelope) return void 0;
11
11
  try {
12
- const parsed = JSON.parse(envelope._data);
12
+ const parsed = JSON.parse(envelope._data ?? "");
13
13
  if (!isVaultPolicy(parsed)) return void 0;
14
14
  return parsed;
15
15
  } catch {
@@ -36,4 +36,4 @@ export {
36
36
  loadVaultPolicy,
37
37
  saveVaultPolicy
38
38
  };
39
- //# sourceMappingURL=chunk-DC5QVPV7.js.map
39
+ //# sourceMappingURL=chunk-DL6BZKP3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-party/policy/storage.ts"],"sourcesContent":["/**\n * Persistence helpers for the vault-level policy document\n * (`_meta/policy`). Mirrors the bypass-AES pattern used by\n * `_meta/handle` — the policy document is plain JSON, the envelope's\n * `_iv` field is left empty.\n *\n * @see https://github.com/noy-db/docs/blob/main/content/docs/services/session-tiers.md → Storage location\n *\n * @module\n */\nimport { buildRecordEnvelope } from '../../capsule/index.js'\nimport type { NoydbStore, VaultPolicy } from '../../kernel/types.js'\n\n/** Reserved collection name for vault-level metadata documents. */\nexport const META_COLLECTION = '_meta'\n/** Reserved id for the vault-level policy document. */\nexport const POLICY_RECORD_ID = 'policy'\n\n/**\n * Read the vault-level policy from `_meta/policy`. Returns `undefined`\n * when no policy has been persisted (fresh vault, or a vault written\n * before the policy module landed). The caller falls back to the\n * default preset.\n *\n * Tolerates corrupted documents the same way `_meta/handle` does: a\n * JSON parse failure surfaces as `undefined`, not a thrown error, so\n * a bad write never permanently locks a vault.\n */\nexport async function loadVaultPolicy(\n store: NoydbStore,\n vault: string,\n): Promise<VaultPolicy | undefined> {\n const envelope = await store.get(vault, META_COLLECTION, POLICY_RECORD_ID)\n if (!envelope) return undefined\n try {\n const parsed = JSON.parse(envelope._data ?? '') as unknown\n if (!isVaultPolicy(parsed)) return undefined\n return parsed\n } catch {\n return undefined\n }\n}\n\n/**\n * Persist the vault-level policy at `_meta/policy`. Idempotent — call\n * once at vault creation and again on `db.updatePolicy()` invocations.\n */\nexport async function saveVaultPolicy(\n store: NoydbStore,\n vault: string,\n policy: VaultPolicy,\n): Promise<void> {\n const envelope = buildRecordEnvelope(\n { collection: META_COLLECTION, id: POLICY_RECORD_ID, version: 1 },\n { iv: '', data: JSON.stringify(policy) },\n )\n await store.put(vault, META_COLLECTION, POLICY_RECORD_ID, envelope)\n}\n\nfunction isVaultPolicy(x: unknown): x is VaultPolicy {\n if (x === null || typeof x !== 'object') return false\n if (!('gates' in x)) return false\n const gates = (x as { gates: unknown }).gates\n return gates !== null && typeof gates === 'object'\n}\n"],"mappings":";;;;;AAcO,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AAYhC,eAAsB,gBACpB,OACA,OACkC;AAClC,QAAM,WAAW,MAAM,MAAM,IAAI,OAAO,iBAAiB,gBAAgB;AACzE,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,SAAS,SAAS,EAAE;AAC9C,QAAI,CAAC,cAAc,MAAM,EAAG,QAAO;AACnC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,gBACpB,OACA,OACA,QACe;AACf,QAAM,WAAW;AAAA,IACf,EAAE,YAAY,iBAAiB,IAAI,kBAAkB,SAAS,EAAE;AAAA,IAChE,EAAE,IAAI,IAAI,MAAM,KAAK,UAAU,MAAM,EAAE;AAAA,EACzC;AACA,QAAM,MAAM,IAAI,OAAO,iBAAiB,kBAAkB,QAAQ;AACpE;AAEA,SAAS,cAAc,GAA8B;AACnD,MAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,MAAI,EAAE,WAAW,GAAI,QAAO;AAC5B,QAAM,QAAS,EAAyB;AACxC,SAAO,UAAU,QAAQ,OAAO,UAAU;AAC5C;","names":[]}
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  MaintenanceMethods
3
- } from "./chunk-BJ2IXQ5Q.js";
3
+ } from "./chunk-BNFNQNEP.js";
4
4
  import {
5
5
  installMethods
6
6
  } from "./chunk-F6NCB7KT.js";
7
7
  import {
8
8
  Query
9
- } from "./chunk-7JE7BDYJ.js";
9
+ } from "./chunk-ZVSKSUGI.js";
10
10
  import {
11
11
  gateTerminal
12
- } from "./chunk-5O7D6ZTW.js";
12
+ } from "./chunk-DHU2AQ6T.js";
13
13
 
14
14
  // src/kernel/query/live/live.ts
15
15
  function buildLiveQuery(recompute, upstreams, options) {
@@ -267,4 +267,4 @@ export {
267
267
  buildLiveQuery,
268
268
  installLive
269
269
  };
270
- //# sourceMappingURL=chunk-FQ54DWUQ.js.map
270
+ //# sourceMappingURL=chunk-EA4VDI5D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/kernel/query/live/live.ts","../src/kernel/query/live/methods.ts","../src/kernel/query/live/install.ts"],"sourcesContent":["/**\n * Reactive query primitive — `query.live()`.\n *\n * produces a `LiveQuery<T>` that re-runs the query and\n * updates its `value` whenever any source feeding it (the left\n * collection AND every right-side collection a join leg points at)\n * mutates.\n *\n * Framework-agnostic by design — core never depends on a UI\n * framework. The framework bindings live in the `noy-db/in` repo\n * (extracted 2026-09-21). The one that wraps a `LiveQuery` is\n * `@noy-db/in-vue`'s `useLiveQuery(live)`: it subscribes once,\n * mirrors `value` into a `ShallowRef`, re-reads `error` on every\n * notification, and disposes via `onScopeDispose`.\n * `@noy-db/in-pinia`'s `store.liveQuery(build)` delegates to it, so\n * there is one implementation rather than two that drift (#1131).\n *\n * There is NO wrapper for `in-react`, `in-solid` or `in-svelte` — a\n * consumer on those subscribes directly (see **Error semantics**\n * below, which is the part a hand-rolled wrapper usually gets\n * wrong). Do not describe those adapters as existing until one does.\n *\n * **Error semantics.** A `.live()` query may throw at re-run time —\n * a strict-mode `DanglingReferenceError` is the most common case\n * (a right-side record was deleted out-of-band, leaving a left\n * row's FK pointing at nothing). When the re-run throws, the\n * `LiveQuery` catches the error and stores it in the `error`\n * field; it does NOT propagate the throw out of the source's\n * change handler, because doing so would tear down whatever\n * upstream emitter is dispatching. Listeners check `error` after\n * each notification and render an error state in the UI.\n *\n * **Dedup of right-side subscriptions.** A multi-FK chain that\n * joins the same target twice (e.g.\n * `.join('billingClientId').join('shippingClientId')`, both\n * pointing at `clients`) only subscribes to that target once. We\n * dedup by target collection name, on the assumption that\n * `resolveSource(name)` returns a single subscribable source per\n * vault + name. Vault's `resolveSource` reads from\n * `collectionCache` so this assumption holds.\n *\n * **Delta maintenance (#1341).** A live query over a plan\n * `canMaintainIncrementally()` admits no longer re-runs: the change event\n * carries which record moved, and `LiveMaintainer` patches the result set —\n * one predicate evaluation, a binary search, a splice. The emitted value is\n * identical to a re-run's, because the maintainer is handed the query's OWN\n * membership test and sort comparator. Every other plan, and every\n * notification that arrives without a delta, re-runs exactly as before.\n *\n * **What .live() still does NOT do:**\n * - No batching unless asked — `live({ batch: true })` coalesces a burst\n * into one recompute + one notification on a microtask; the default stays\n * one event in, one notification out, because batching makes `value`\n * stale until the microtask runs.\n * - No async notifications — every unbatched notification is synchronous\n * within the source's change handler.\n * - No re-planning under live mutations — the planner picks once\n * at subscription time and reuses the same plan for every\n * re-run.\n */\n\nimport type { LiveMaintainer, SourceChange } from './incremental.js'\n\n/**\n * The reactive primitive returned by `Query.live()`.\n *\n * Listeners can read the current `value` snapshot at any time and\n * subscribe to changes via `.subscribe(cb)`. The `error` field\n * carries the most recent re-run error, if any — read it after\n * each notification to render error state.\n *\n * Always call `stop()` when the live query is no longer needed.\n * Without it, the upstream change-stream subscriptions stay live\n * forever and the query keeps re-running on every mutation.\n */\nexport interface LiveQuery<T> {\n /**\n * Current snapshot of the query result.\n *\n * **A FRESH ARRAY on every successful re-run** — `refresh()` assigns\n * `this._value = this.recompute()`, and `recompute` is the query's own\n * `toArray()`. So the reference CHANGES on each change, and reference\n * identity is a valid change signal: a Vue `shallowRef`, a React\n * `useState`, or any `Object.is` comparison detects an update without\n * copying.\n *\n * The corollary is the part that bites: a consumer who reads `value`\n * ONCE and holds the array gets a snapshot that never updates. Read it\n * again after each notification — do not cache it.\n *\n * (An earlier version of this comment said the opposite — \"updated in\n * place… the reference returned is the same array\" — and told callers to\n * copy for change detection. Both halves were wrong, and the advice was\n * backwards. Verified against `buildLiveQuery`: two reads across a\n * notification are not `===`, and the first array still holds the old\n * contents.)\n */\n readonly value: readonly T[]\n /**\n * Most recent re-run error, or `null` on success. Set when the\n * executor throws (e.g. `DanglingReferenceError` in strict mode\n * after a right-side delete). Cleared on the next successful\n * re-run.\n */\n readonly error: Error | null\n /**\n * Register a notification callback. Fires AFTER `value` and\n * `error` have been updated for a given upstream change.\n * Returns an unsubscribe function.\n *\n * The first call to `subscribe` does NOT fire the callback\n * immediately — call sites that want the initial value should\n * read `live.value` directly before subscribing.\n */\n subscribe(cb: () => void): () => void\n /**\n * Tear down every upstream subscription and clear the listener\n * set. Idempotent — calling twice is safe. After `stop()`, the\n * query no longer re-runs and `subscribe()` becomes a no-op\n * (the returned unsubscribe is still callable and is also a\n * no-op).\n */\n stop(): void\n}\n\n/**\n * Internal subscription handle for an upstream source — left or\n * right side. The contract is just `subscribe(cb): unsubscribe`,\n * matching the existing `QuerySource.subscribe` and the new\n * `JoinableSource.subscribe` (added in ).\n */\nexport interface LiveUpstream {\n /**\n * `cb` may be handed the delta that caused the notification (#1341) so the\n * live query can patch its result set instead of re-running. Calling it with\n * NO argument is always valid and always correct — it means \"something\n * changed, I can't say what\", and the live query answers with a full re-run.\n */\n subscribe(cb: (change?: SourceChange) => void): () => void\n}\n\n/** Options for {@link buildLiveQuery}. */\nexport interface LiveBuildOptions {\n /**\n * Coalesce a burst of upstream changes into ONE recompute and ONE\n * notification, flushed on a microtask (#1341). Off by default: with it on,\n * `live.value` is stale until the microtask runs, so a consumer that reads\n * synchronously after an `await put()` would see the old value.\n */\n readonly batch?: boolean\n /**\n * Incremental maintainer for this query's result set. When present it\n * REPLACES `recompute` as the value source — it patches per delta and\n * rebuilds itself whenever a delta is not provably patchable.\n */\n readonly maintainer?: LiveMaintainer\n}\n\n/**\n * Build a LiveQuery from a `recompute` callback (typically the\n * Query's bound `toArray`) and a list of upstream sources to\n * subscribe to.\n *\n * The recompute fires once synchronously to populate the initial\n * value, then re-fires every time any upstream notifies. Errors\n * thrown by recompute are caught and stored in `error` instead of\n * propagating — see the file docstring for the rationale.\n */\nexport function buildLiveQuery<T>(\n recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n options?: LiveBuildOptions,\n): LiveQuery<T> {\n return new LiveQueryImpl<T>(recompute, upstreams, options)\n}\n\nclass LiveQueryImpl<T> implements LiveQuery<T> {\n private _value: readonly T[] = []\n private _error: Error | null = null\n private readonly listeners = new Set<() => void>()\n private readonly unsubs: Array<() => void> = []\n private stopped = false\n\n private readonly maintainer: LiveMaintainer | undefined\n private readonly batch: boolean\n private queued: Array<SourceChange | undefined> = []\n private flushScheduled = false\n\n constructor(\n private readonly recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n options?: LiveBuildOptions,\n ) {\n this.maintainer = options?.maintainer\n this.batch = options?.batch === true\n // From here on the maintainer is delta-driven: it patches on every event\n // and only rebuilds when it has to.\n this.maintainer?.attach()\n // Initial compute. If this throws, the constructor still\n // succeeds — we want consumers to be able to render an error\n // state from `live.error` rather than wrapping every\n // `query.live()` call in a try/catch.\n this.refresh()\n for (const upstream of upstreams) {\n try {\n this.unsubs.push(upstream.subscribe(this.onUpstreamChange))\n } catch (err) {\n // Upstream subscription failed — record it as the live\n // error and continue with the upstreams that did work.\n // The LiveQuery is now degraded (won't re-fire on this\n // upstream's changes) but isn't broken; consumers can\n // detect this via `live.error`.\n this._error = err instanceof Error ? err : new Error(String(err))\n }\n }\n }\n\n get value(): readonly T[] {\n return this._value\n }\n\n get error(): Error | null {\n return this._error\n }\n\n /**\n * Bound change handler — used as the callback passed to every\n * upstream's subscribe. Bound via class field so the `this`\n * context survives the indirect call from arbitrary upstreams.\n */\n private readonly onUpstreamChange = (change?: SourceChange): void => {\n if (this.batch) {\n this.queued.push(change)\n if (!this.flushScheduled) {\n this.flushScheduled = true\n queueMicrotask(this.flush)\n }\n return\n }\n this.absorb([change])\n this.notify()\n }\n\n /**\n * Drain a coalesced burst: every queued delta is folded in, then the value\n * is read ONCE and listeners are notified ONCE.\n */\n private readonly flush = (): void => {\n this.flushScheduled = false\n const batch = this.queued\n this.queued = []\n if (this.stopped) return\n this.absorb(batch)\n this.notify()\n }\n\n private absorb(changes: ReadonlyArray<SourceChange | undefined>): void {\n if (this.stopped) return\n if (this.maintainer) {\n try {\n for (const change of changes) this.maintainer.apply(change)\n } catch {\n // A predicate threw mid-patch. Drop the maintained state and let the\n // read below rebuild: it runs the same predicate, so the consumer sees\n // exactly the error (and the preserved last-good value) a full re-run\n // would have produced.\n this.maintainer.invalidate()\n }\n }\n this.refresh()\n }\n\n private notify(): void {\n for (const cb of this.listeners) {\n try {\n cb()\n } catch {\n // Listener errors are isolated — one buggy consumer\n // doesn't break the others or tear down the live query.\n }\n }\n }\n\n private refresh(): void {\n if (this.stopped) return\n try {\n this._value = (this.maintainer ? this.maintainer.rows() : this.recompute()) as T[]\n this._error = null\n } catch (err) {\n this._error = err instanceof Error ? err : new Error(String(err))\n // Whatever the maintainer holds is no longer trustworthy — the next\n // read pays for a rebuild rather than serving a half-patched set.\n this.maintainer?.invalidate()\n // Don't clobber the previous value on error — consumers\n // typically want to keep showing the last known good state\n // alongside the error message rather than flashing to an\n // empty list.\n }\n }\n\n subscribe(cb: () => void): () => void {\n if (this.stopped) return () => {}\n this.listeners.add(cb)\n return () => this.listeners.delete(cb)\n }\n\n stop(): void {\n if (this.stopped) return\n this.stopped = true\n for (const unsub of this.unsubs) {\n try {\n unsub()\n } catch {\n // Unsub errors are swallowed — at this point we're tearing\n // down anyway and the failure is noise.\n }\n }\n this.unsubs.length = 0\n this.listeners.clear()\n }\n}\n","/**\n * #1458 — **Live**: the same plan, re-run on change.\n *\n * `subscribe` · `live`\n *\n * Bodies moved out of `kernel/query/builder.ts` unchanged; see\n * `../relate/methods.ts` for why `this.plan` still resolves.\n */\nimport type { DeclaredPredicate, InternalSource, QueryPlan } from '../builder.js'\nimport type { JoinContext } from '../relate/join.js'\nimport type { ReduceStrategy } from '../../../with-lookup/reduce/strategy.js'\nimport type { LiveQuery, LiveUpstream } from './live.js'\nimport { buildLiveQuery } from './live.js'\nimport type { LiveMaintainer } from './incremental.js'\nimport type { DateTruncKey } from '../reduce/date-trunc.js'\nimport type { GroupMaintenanceSource } from './incremental.js'\nimport { gateTerminal } from '../hydration.js'\n\n/** @internal — the mixin whose prototype `./index.ts` copies onto `Query`. */\n /* eslint-disable @typescript-eslint/no-unused-vars -- #1458: the parameter\n list must match `Query`'s exactly (T, S, Q, M) because `declare module`\n merges this mixin's `Pick` into it, and TypeScript requires every\n declaration of an interface to carry identical type parameters. A group\n whose methods happen to use only `T` still declares all four. */\nexport class LiveMethods<\n T,\n S extends keyof T = never,\n Q extends keyof T & string = never,\n M extends keyof T & string = never,\n /* eslint-enable @typescript-eslint/no-unused-vars */\n> {\n declare protected readonly source: InternalSource\n declare protected readonly plan: QueryPlan\n declare protected readonly joinContext: JoinContext | undefined\n declare protected readonly reduceStrategy: ReduceStrategy\n declare protected readonly predicates: ReadonlyMap<string, DeclaredPredicate> | undefined\n declare protected decodeVia: (records: readonly unknown[], locale?: string) => unknown[]\n declare protected incrementalMaintainer: (mode: 'rows' | 'records') => LiveMaintainer | undefined\n declare protected groupMaintenance: (derived: readonly DateTruncKey[]) => GroupMaintenanceSource | undefined\n declare toArray: (opts?: { locale?: string }) => T[]\n\n /**\n * Re-run the query whenever the source notifies of changes.\n * Returns an unsubscribe function. The callback receives the latest result.\n * Throws if the source does not support subscriptions.\n *\n * **For joined queries, prefer `.live()`** — `subscribe()`\n * only re-fires on LEFT-side changes, so joined data can be\n * stale if the right side mutates between emissions. `.live()`\n * merges change streams from every join target.\n */\n subscribe(cb: (result: T[]) => void): () => void {\n const _h = this.source.hydration\n if (_h !== undefined && !_h.isHydrated()) return gateTerminal(_h, 'subscribe', () => this.subscribe(cb))\n if (!this.source.subscribe) {\n throw new Error('Query source does not support subscriptions. Pass a source with a subscribe() method.')\n }\n cb(this.toArray())\n return this.source.subscribe(() => cb(this.toArray()))\n }\n\n /**\n * Reactive terminal — returns a `LiveQuery<T>` that re-runs the\n * query and updates its `value` whenever any source feeding it\n * mutates..\n *\n * For non-joined queries, `.live()` is a convenience over the\n * existing `.subscribe()` callback shape: a hand-rolled reactive\n * primitive with `value` / `error` fields and a `subscribe(cb)`\n * notification channel. Frame-agnostic — Vue / React / Solid\n * adapters wrap it in their own primitive.\n *\n * For joined queries, `.live()` additionally subscribes to every\n * join target's change stream. Mutations on a right-side\n * collection (insert / update / delete of a client referenced by\n * an invoice) re-fire the live query and re-evaluate every\n * dependent left row. Right-side targets are deduped by\n * collection name, so a chain that joins the same target twice\n * (e.g. billing client + shipping client → both 'clients') only\n * subscribes once.\n *\n * **Ref-mode behavior on right-side disappearance** — matches the\n * eager `.toArray()` contract from :\n * - `strict` → re-run throws `DanglingReferenceError`. The\n * LiveQuery catches the throw, stores it in `live.error`, and\n * notifies listeners (the throw does NOT propagate out of\n * the source's change handler — that would tear down the\n * emitter). Consumers check `live.error` after each\n * notification and render an error state in the UI.\n * - `warn` → joined value flips to `null`; the existing\n * warn-channel deduplication keeps repeated re-runs from\n * spamming the console.\n * - `cascade` → no special handling needed; the cascade-\n * delete mechanism propagates the right-side delete into the\n * left collection on the next tick, and the live query\n * naturally re-fires with the orphaned left rows gone.\n *\n * Always call `live.stop()` when finished — it tears down every\n * upstream subscription. The Vue layer's `onUnmounted` hook\n * should call `stop()` automatically; raw consumers must do it\n * themselves.\n *\n * **Incremental maintenance (#1341).** For a plan\n * `incrementalMaintainer()` accepts — no joins, no `.filter(fn)`, no\n * label-sort, and nothing an index would serve in index order (an `==`/`in`\n * probe, #1344's sorted-index range, or #1344's `orderBy(f).limit(n)`\n * index page) — the result set is PATCHED per change\n * event instead of re-run: one predicate evaluation for the changed record,\n * a binary search, a splice. Everything else, and any change event that\n * arrives without a delta, still re-runs the whole plan. Either way the\n * emitted value is identical to `toArray()` — the maintainer reuses this\n * query's own membership test and sort comparator.\n *\n * `options.batch` coalesces a burst of changes into one recompute and one\n * notification on a microtask. It is OFF by default because it makes\n * `live.value` stale until the microtask runs: a consumer that reads\n * synchronously after `await put()` would see the previous value.\n *\n * **Limitations:**\n * - No re-planning under live mutations — the planner picks\n * once at subscription time and reuses the same plan.\n * - Streaming live joins are deferred.\n */\n live(options?: { batch?: boolean }): LiveQuery<T> {\n const _h = this.source.hydration\n if (_h !== undefined && !_h.isHydrated()) return gateTerminal(_h, 'live', () => this.live(options))\n const upstreams: LiveUpstream[] = []\n\n // Left-side change stream — every live query subscribes to\n // its source if the source supports subscriptions.\n if (this.source.subscribe) {\n const leftSubscribe = this.source.subscribe.bind(this.source)\n upstreams.push({\n subscribe: (cb: () => void) => leftSubscribe(cb),\n })\n }\n\n // Right-side change streams — only for joined queries. Dedup\n // by target name so a chain joining the same target twice\n // doesn't double-subscribe and double-fire on every right-side\n // mutation.\n if (this.plan.joins.length > 0 && this.joinContext) {\n const subscribed = new Set<string>()\n for (const leg of this.plan.joins) {\n if (subscribed.has(leg.target)) continue\n subscribed.add(leg.target)\n const rightSource = this.joinContext.resolveSource(leg.target)\n if (rightSource?.subscribe) {\n const rightSubscribe = rightSource.subscribe.bind(rightSource)\n upstreams.push({\n subscribe: (cb: () => void) => rightSubscribe(cb),\n })\n }\n }\n }\n\n // Cross-join right-side change streams — symmetric with FK joins above.\n if (this.joinContext) {\n const subscribedCross = new Set<string>()\n for (const clause of this.plan.clauses) {\n if (clause.type !== 'crossJoin') continue\n if (subscribedCross.has(clause.target)) continue\n subscribedCross.add(clause.target)\n const rightSource = this.joinContext.resolveSource(clause.target)\n if (rightSource?.subscribe) {\n const rightSubscribe = rightSource.subscribe.bind(rightSource)\n upstreams.push({\n subscribe: (cb: () => void) => rightSubscribe(cb),\n })\n }\n }\n }\n\n // The recompute is just toArray bound to this query — same\n // pipeline as eager execution, including join application. It stays the\n // fallback even when a maintainer is attached.\n const maintainer = this.incrementalMaintainer('rows')\n return buildLiveQuery<T>(() => this.toArray(), upstreams, {\n ...(options?.batch === true ? { batch: true } : {}),\n ...(maintainer ? { maintainer } : {}),\n })\n }\n}\n\n/** The public Live surface — merged into `Query` by `./index.ts`. */\nexport type LiveSurface<\n T,\n S extends keyof T = never,\n Q extends keyof T & string = never,\n M extends keyof T & string = never,\n> = Pick<LiveMethods<T, S, Q, M>, 'subscribe' | 'live'>\n","/**\n * #1458 — the Live install. See `../relate/install.ts` for why this is a\n * function called from the entry rather than a module-level side effect.\n */\nimport { Query } from '../builder.js'\nimport { installMethods } from '../internal/core.js'\nimport { MaintenanceMethods } from '../internal/maintenance.js'\nimport { LiveMethods } from './methods.js'\n\nlet done = false\n\n/** Idempotent: the root barrel and the subpath entry both call it. */\nexport function installLive(): void {\n if (done) return\n done = true\n installMethods(Query.prototype, MaintenanceMethods)\n installMethods(Query.prototype, LiveMethods)\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  NoydbError
3
- } from "./chunk-244VXYPG.js";
3
+ } from "./chunk-VSUQI5Q6.js";
4
4
 
5
5
  // src/with-formula/computed/index.ts
6
6
  var ComputedFieldError = class extends NoydbError {
@@ -35,4 +35,4 @@ export {
35
35
  ComputedFieldError,
36
36
  evalComputedFields
37
37
  };
38
- //# sourceMappingURL=chunk-IT22PWKV.js.map
38
+ //# sourceMappingURL=chunk-ECGXA4LT.js.map
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-VYYM3MEN.js";
7
7
  import {
8
8
  derivePersistedSchema
9
- } from "./chunk-PSIV7CJV.js";
9
+ } from "./chunk-J4KAPKBJ.js";
10
10
  import {
11
11
  loadPersistedSchema
12
- } from "./chunk-3H5KLDZN.js";
12
+ } from "./chunk-H3YM66EI.js";
13
13
 
14
14
  // src/with-shape/introspection/walk.ts
15
15
  var INTERNAL_PREFIX = "_";
@@ -163,7 +163,7 @@ async function statsForCollection(adapter, vault, collection) {
163
163
  for (const id of ids) {
164
164
  const env = await adapter.get(vault, collection, id);
165
165
  if (!env) continue;
166
- const size = env._data.length;
166
+ const size = (env._data ?? "").length;
167
167
  total += size;
168
168
  if (size < min) min = size;
169
169
  if (size > max) max = size;
@@ -268,4 +268,4 @@ function summariseAggregateOp(value) {
268
268
  export {
269
269
  dumpVaultSchema
270
270
  };
271
- //# sourceMappingURL=chunk-BP6DARYA.js.map
271
+ //# sourceMappingURL=chunk-EMAWOGVH.js.map