@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
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/enclave/crypto.ts"],"sourcesContent":["/**\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 '../errors.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 `kernel/enclave/**`, 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\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst RECOVERY_SECRET_BYTES = 32\nconst KEY_BITS = 256\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). */\nexport interface EchoSecretParts {\n readonly prompt: string\n readonly echo: string\n readonly key: string\n}\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 */\nconst ECHO_KDF_CONTEXT = new Uint8Array([0xff, ...new TextEncoder().encode('noydb-echo-secret-v1')])\n\n/**\n * AG-1 encoding: domain context + 4-byte big-endian length prefix per\n * part. Two independent guarantees make a single typed string unable to\n * derive an echo vault's KEK (spec AG-1):\n *\n * 1. The `0xFF` context prefix — UTF-8 never produces that byte, so the\n * encoding is provably not the encoding of ANY string (not merely of\n * no separator-joined form).\n * 2. Structural length prefixes — part boundaries are key material, so\n * re-splitting the same characters changes the derived KEK.\n *\n * @throws ValidationError when any part is not a string — the single\n * chokepoint every echo KEK derivation and block mint passes through, so a\n * malformed parts object cannot be `TextEncoder`-coerced into key material.\n */\nexport function encodeEchoParts(parts: EchoSecretParts): Uint8Array {\n for (const name of ['prompt', 'echo', 'key'] as const) {\n if (typeof parts?.[name] !== 'string') {\n throw new ValidationError('echo secret parts must be three strings: prompt, echo, key')\n }\n }\n const enc = new TextEncoder()\n const segments = [parts.prompt, parts.echo, parts.key].map((p) => enc.encode(p))\n const total = ECHO_KDF_CONTEXT.length + segments.reduce((n, s) => n + 4 + s.length, 0)\n const out = new Uint8Array(total)\n out.set(ECHO_KDF_CONTEXT, 0)\n let offset = ECHO_KDF_CONTEXT.length\n for (const s of segments) {\n new DataView(out.buffer).setUint32(offset, s.length, false)\n out.set(s, offset + 4)\n offset += 4 + s.length\n }\n return out\n}\n\n/** Derive the tier-1 KEK from a 3-part echo secret (spec: KEK from ALL parts). */\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// ─── 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 kernel/enclave 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// ─── 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// ─── 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/**\n * SHA-256 hex digest of raw bytes. Used to derive content-addressed\n * eTags for blob deduplication. Computed on plaintext bytes\n * before compression and encryption so the eTag identifies content, not\n * ciphertext, and survives re-encryption (key rotation, re-upload).\n */\nexport async function sha256Hex(data: Uint8Array): Promise<string> {\n const hash = await subtle.digest('SHA-256', data as unknown as BufferSource)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── HMAC-SHA-256 ─────────────────────────────\n\n/**\n * Compute HMAC-SHA-256(key, data) and return hex string.\n *\n * Used to derive content-addressed eTags that are opaque to the store:\n * ```\n * eTag = hmacSha256Hex(blobDEK, plaintext)\n * ```\n *\n * Unlike a plain SHA-256, the HMAC is keyed by the vault-shared `_blob` DEK,\n * so an attacker with store access cannot pre-compute eTags for known files.\n * Deduplication still works within a vault (same key + same content = same eTag).\n */\n/**\n * Derive the blob CONTENT-ADDRESSING key for one tier (#1126).\n *\n * The blob eTag is an HMAC over the plaintext. It used to be keyed by the\n * `_blob` DEK — the very key `rotateKeys` replaces — so after any rotation\n * `HMAC(live DEK, plaintext) !== storedETag` for every blob written before it,\n * permanently. `decryptResponse()` checks that unconditionally, so the\n * presigned-URL read path raised `TamperedError` on legitimate data forever;\n * dedup also split, minting a second address for identical bytes.\n *\n * The fix is a `root` that rotation never touches (`_blob_addr`, refused by\n * `rotateKeys` exactly as `_roster` is). Rotation then re-keys chunk BODIES\n * while every address stays stable, which is what makes the AAD\n * `{eTag}:{i}:{count}` and every `_blob_index` / `_blob_slots_*` /\n * `_blob_versions_*` row survive it untouched.\n *\n * **Derived PER TIER, deliberately.** A single vault-wide address would make an\n * elevated blob and a tier-0 blob with identical content share an eTag,\n * leaking equality across a boundary the tier system exists to enforce — and\n * `rehomeForTier` re-addresses on a tier move precisely because the address is\n * tier-scoped today. Domain separation keeps that property while removing the\n * rotation coupling: the two are independent axes, and only one of them was\n * ever meant to change the address.\n *\n * Same HKDF idiom as `asKwKey`, with its own salt so the addressing key can\n * never coincide with a wrapping or encryption key.\n */\nexport async function deriveBlobAddressKey(root: CryptoKey, tier: number): Promise<CryptoKey> {\n const rawRoot = await subtle.exportKey('raw', root)\n const hkdfKey = await subtle.importKey('raw', rawRoot, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-blob-address')\n const info = new TextEncoder().encode(`tier:${tier}`)\n const bits = await subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info }, hkdfKey, KEY_BITS)\n // Imported as AES-GCM only because `hmacSha256Hex` re-exports raw and\n // re-imports as HMAC; the algorithm tag here is a carrier, never used to\n // encrypt anything.\n return subtle.importKey('raw', bits, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])\n}\n\nexport async function hmacSha256Hex(key: CryptoKey, data: Uint8Array): Promise<string> {\n // Export AES-GCM DEK raw bytes → import as HMAC key\n const rawKey = await subtle.exportKey('raw', key)\n const hmacKey = await subtle.importKey(\n 'raw',\n rawKey,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const sig = await subtle.sign('HMAC', hmacKey, data as unknown as BufferSource)\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── AAD-aware Binary Encrypt / Decrypt ──\n\n/**\n * Encrypt raw bytes with AES-256-GCM using Additional Authenticated Data.\n *\n * The AAD binds each chunk to its parent blob and position, preventing\n * chunk reorder, substitution, and truncation attacks:\n * ```\n * AAD = UTF-8(\"{eTag}:{chunkIndex}:{chunkCount}\")\n * ```\n *\n * The AAD is NOT stored — the reader reconstructs it from `BlobObject`\n * metadata and passes it to `decryptBytesWithAAD`.\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/**\n * Derive an AES-256-GCM presence key from a collection DEK using HKDF-SHA256.\n *\n * The presence key is domain-separated from the data DEK by the fixed salt\n * `'noydb-presence'` and the `info` = collection name. This means:\n * - The adapter never sees the presence key.\n * - Presence payloads rotate automatically when the collection DEK is rotated.\n * - Revoked users cannot derive the new presence key after a DEK rotation.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Used as the HKDF `info` parameter for domain separation.\n * @returns A non-extractable AES-256-GCM key suitable for presence payload encryption.\n */\nexport async function derivePresenceKey(dek: CryptoKey, collectionName: string): Promise<CryptoKey> {\n // Step 1: export DEK raw bytes\n const rawDek = await subtle.exportKey('raw', dek)\n\n // Step 2: import as HKDF key material\n const hkdfKey = await subtle.importKey(\n 'raw',\n rawDek,\n 'HKDF',\n false,\n ['deriveBits'],\n )\n\n // Step 3: derive 256 bits with salt='noydb-presence' and info=collectionName\n const salt = new TextEncoder().encode('noydb-presence')\n const info = new TextEncoder().encode(collectionName)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n\n // Step 4: import derived bits as AES-GCM key\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Sealed Field Key Derivation ──────────────────────────\n\n/**\n * Derive a **per-field** AES-256-GCM key from a collection DEK using\n * HKDF-SHA256, used to encrypt a single sensitive field into its own\n * `_sealed[field]` envelope slot (structural group-encryption).\n *\n * Domain-separated from the data DEK (and from the presence/deterministic\n * channels) by the fixed salt `'noydb-sealed'` and an `info` of\n * `JSON.stringify(['noydb-sealed', collectionName, field])`. The JSON-array\n * encoding is injective — each element is separately quoted/escaped — so\n * collection `a/sealed/b` + field `c` cannot collide with collection `a` +\n * field `b/sealed/c`. Each sensitive field gets a **distinct** key, so a\n * `_sealed[a]` ciphertext does not authenticate under field `b`'s key —\n * sealed fields are cryptographically isolated from one another. The\n * collection name keeps the same field name in two collections from sharing a\n * key, mirroring how `derivePresenceKey` / `encryptDeterministic` domain-separate.\n *\n * @param dek The collection's AES-256-GCM DEK (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 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/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n/**\n * Mint a fresh recovery secret — the high-entropy value a recovery profile\n * wraps the DEK set under and then hands out (Shamir splits it into shares;\n * paper prints it). Not a salt: a salt is public, this is not, and a fork\n * may size them differently. The reference enclave returns 32 bytes.\n *\n * Callers must zero the buffer once it has been wrapped and split.\n */\nexport function generateRecoverySecret(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(RECOVERY_SECRET_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/derivations/strategy-hash.ts","../src/with-formula/derivations/registry.ts"],"sourcesContent":["/**\n * Deterministic hash of a derivation strategy's \"shape\": source\n * collection, declared sibling sources, output keys, derive function\n * source. Used to detect\n * strategy drift: a record whose `_derivedFrom.strategyHash` doesn't\n * match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps.\n */\nimport { sha256Hex } from '../../kernel/enclave/index.js'\n\nexport async function computeStrategyHash(\n source: string,\n outputKeys: readonly string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n derive: (...args: any[]) => any,\n sources?: ReadonlyArray<string>,\n): Promise<string> {\n const canonical = JSON.stringify({\n source,\n outputs: [...outputKeys].sort(),\n derive: derive.toString(),\n // Declared sibling sources — adding/removing a trigger\n // collection invalidates cached derived records. Omitted when empty\n // so strategies without siblings keep their existing hash.\n ...(sources?.length ? { sources: [...sources].sort() } : {}),\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n","import { matchTargetFieldNames } from '../../kernel/collection-config.js'\nimport { DerivationCycleError, DuplicateBehaviorNameError, ValidationError } from '../../kernel/errors.js'\nimport { ViaGraph, type FieldRef, type EdgeKind, type Grain } from '../../kernel/via/graph.js'\nimport { schemaFieldKeys } from '../../with-shape/introspection/describe.js'\nimport { computeStrategyHash } from './strategy-hash.js'\nimport { hopCollections, normalizeTriggerBy, type NormalizedTrigger } from './trigger-match.js'\nimport type { DerivationSpec } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2). A derivation's\n * `derive()`/a rollup's `compute()` reads the WHOLE source record — there is\n * no single declared field to point at — so a trigger/output collection is\n * modelled as one artifact node per collection (`kernel/via/graph.ts`'s\n * \"Artifact-grain targets... modelled as a field node whose field is the\n * artifact key\" convention). MUST match `materialized-views/registry.ts`'s\n * and `vault.ts`'s overlay-edge marker so cross-registry edges (a derivation\n * feeding into / out of an MV or overlay collection) resolve to the SAME\n * graph node.\n */\nconst WHOLE_RECORD = '*'\n\n/** Strip this registry's `.${WHOLE_RECORD}` artifact suffix off a graph cycle\n * path entry, recovering the bare collection name — preserves the exact\n * pre-#638 `DerivationCycleError` message shape (behavior lock) for a pure\n * record-shape derivation cycle. A rollup's real-field target (not suffixed\n * `.${WHOLE_RECORD}`) is left untouched. */\nfunction stripArtifactSuffix(displayId: string): string {\n const suffix = `.${WHOLE_RECORD}`\n return displayId.endsWith(suffix) ? displayId.slice(0, -suffix.length) : displayId\n}\n\ninterface RegisteredStrategy {\n // Type-erased to allow the registry to hold heterogeneous strategies.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: DerivationSpec<any, any>\n strategyHash: string\n readonly triggers: ReadonlyArray<NormalizedTrigger>\n}\n\n/**\n * Vault-internal registry of derivation strategies. Owned by `Vault`;\n * not exported.\n *\n * @internal\n */\nexport class DerivationRegistry {\n private readonly _bySource = new Map<string, RegisteredStrategy[]>()\n private readonly _byOutput = new Map<string, RegisteredStrategy[]>()\n private readonly _byName = new Map<string, RegisteredStrategy>()\n\n /**\n * Register a derivation strategy. If `spec.name` is given and already\n * registered by another derivation in this vault, throws\n * {@link DuplicateBehaviorNameError} before indexing.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async register(spec: DerivationSpec<any, any>): Promise<void> {\n if (spec.name !== undefined && this._byName.has(spec.name)) {\n throw new DuplicateBehaviorNameError(spec.name, 'derivation')\n }\n\n const outputKeys = Object.keys(spec.outputs)\n const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources)\n const triggers = normalizeTriggerBy(spec.triggerBy)\n const reg: RegisteredStrategy = { spec, strategyHash, triggers }\n\n if (spec.name !== undefined) this._byName.set(spec.name, reg)\n\n const fromSource = this._bySource.get(spec.source)\n if (fromSource) fromSource.push(reg)\n else this._bySource.set(spec.source, [reg])\n\n // Declared sibling sources index the SAME `reg` under each\n // extra collection so `strategiesForSource(extra)` returns it and a\n // sibling write re-fires the derivation. Sibling keys also enter\n // `_bySource`, so `validate()`'s cycle DFS walks them automatically.\n for (const extra of spec.sources ?? []) {\n const fromExtra = this._bySource.get(extra)\n if (fromExtra) fromExtra.push(reg)\n else this._bySource.set(extra, [reg])\n }\n\n // FK triggers index the SAME `reg` under each parent collection so\n // a parent write re-fires the derivation (fanned out to matching source\n // records in `dispatchDerivations`). Like sources[], these keys enter\n // `_bySource` so the cycle DFS walks the trigger→output edge.\n // #1277 — the INTERMEDIATE of a declared hop is indexed too, so a write\n // that re-points it re-fires the derivation. Without this the hop's whole\n // correctness argument fails: nothing is written to the trigger or source\n // collection when an intermediate moves, so no other path can notice, and\n // every source it used to address is stranded silently. Entering `_bySource`\n // also puts the hop edge into `validate()`'s cycle DFS, which would\n // otherwise be blind to it.\n for (const t of triggers) {\n for (const hop of hopCollections(t.match)) {\n const fromHop = this._bySource.get(hop)\n if (fromHop) { if (!fromHop.includes(reg)) fromHop.push(reg) }\n else this._bySource.set(hop, [reg])\n }\n }\n for (const t of triggers) {\n const fromTrigger = this._bySource.get(t.collection)\n if (fromTrigger) fromTrigger.push(reg)\n else this._bySource.set(t.collection, [reg])\n }\n\n // Rollup: a write to the child `from` collection recomputes\n // the parent (= spec.source = into) at id child[key]. Index under `from`\n // so a child write fires it; spec.source (into) is already indexed above,\n // so a parent write also recomputes its own aggregate (covers the\n // parent-created-after-children case). The from→into edge enters the cycle\n // DFS automatically.\n if (spec.rollup) {\n const fromRollup = this._bySource.get(spec.rollup.from)\n if (fromRollup) fromRollup.push(reg)\n else this._bySource.set(spec.rollup.from, [reg])\n }\n\n for (const key of outputKeys) {\n const output = spec.outputs[key]\n if (!output) continue\n const outputCollection = output.collection\n const arr = this._byOutput.get(outputCollection)\n if (arr) arr.push(reg)\n else this._byOutput.set(outputCollection, [reg])\n }\n }\n\n strategiesForSource(source: string): ReadonlyArray<RegisteredStrategy> {\n return this._bySource.get(source) ?? []\n }\n\n strategiesProducingOutput(collection: string): ReadonlyArray<RegisteredStrategy> {\n return this._byOutput.get(collection) ?? []\n }\n\n /**\n * The #1253-pattern typo guard for match fields (#1249): a misspelt\n * `to`/`from` silently matches nothing forever, so validate against the\n * collection's enumerable field set at the earliest point it exists.\n * `schemaFieldKeys(schema) === undefined` (TS-generic collection,\n * unreadable validator) is DELIBERATELY silent — those fields are real\n * and unenumerable. `configKeys` folds in the collection's other\n * non-schema field declarations (fieldMeta/moneyFields/dictKeyFields/\n * refs/computed); `denormExempt` is derived here from this registry's\n * own strategies rather than taken as a parameter — a field this\n * collection's own derivations write via `denorm` is never a typo.\n */\n validateFieldsFor(\n collectionName: string,\n schema: unknown,\n configKeys: ReadonlyArray<string>,\n viaSources: {\n readonly computed?: Readonly<Record<string, unknown>> | undefined\n readonly viaFields?: Readonly<Record<string, unknown>> | undefined\n } | undefined = undefined,\n ): void {\n const targets = matchTargetFieldNames(viaSources ?? {})\n const virtual = new Set(targets.virtual)\n const shapeKeys = schemaFieldKeys(schema)\n // The two checks have DIFFERENT preconditions, which is why one loop runs\n // both rather than an early return covering both. The typo check needs a\n // readable field list and must stay silent without one — a false \"unknown\n // field\" on a TS-generic collection would be worse than no check. The\n // virtual check needs nothing but the declaration in hand: \"declared, and\n // never stored\" is provable from the declaration alone (#1266).\n if (shapeKeys === undefined && virtual.size === 0) return\n const keys = shapeKeys === undefined ? undefined\n : new Set([...shapeKeys, ...configKeys, ...targets.declared])\n const denormExempt = new Set<string>()\n const regs = new Set([...this._bySource.values()].flat())\n for (const reg of regs) {\n if (reg.spec.source !== collectionName) continue\n for (const out of Object.values(reg.spec.outputs) as Array<{ collection?: string; denorm?: readonly string[] }>) {\n if (out.collection === collectionName) for (const d of out.denorm ?? []) denormExempt.add(d)\n }\n }\n const unknown = (f: string): boolean =>\n keys !== undefined && !keys.has(f) && !denormExempt.has(f)\n for (const reg of regs) {\n const name = reg.spec.name ?? reg.spec.source\n for (const t of reg.triggers) {\n for (const p of t.match) {\n // `to` reads the SOURCE record; `from` reads the WRITTEN record.\n // Both are the stored shape, so both refuse a virtual target.\n if (reg.spec.source === collectionName) {\n if (virtual.has(p.to)) throw new ValidationError(this._virtualMatchMessage(name, collectionName, p.to, 'to'))\n if (unknown(p.to)) {\n throw new ValidationError(\n `derivation \"${name}\": triggerBy match names source field \"${p.to}\", which \"${collectionName}\" does not declare — a typo here silently matches nothing forever`)\n }\n }\n if (t.collection === collectionName && p.from !== 'id') {\n if (virtual.has(p.from)) throw new ValidationError(this._virtualMatchMessage(name, collectionName, p.from, 'from'))\n if (unknown(p.from)) {\n throw new ValidationError(\n `derivation \"${name}\": triggerBy match reads \"${p.from}\" from written \"${collectionName}\" records, which that collection does not declare — a typo here silently matches nothing forever`)\n }\n }\n }\n }\n }\n }\n\n /** #1266 — one message shape for both match sides; see {@link matchTargetFieldNames}. */\n private _virtualMatchMessage(name: string, collectionName: string, field: string, side: 'to' | 'from'): string {\n const where = side === 'to'\n ? `names source field \"${field}\", which \"${collectionName}\"`\n : `reads \"${field}\" from written \"${collectionName}\" records, which that collection`\n return `derivation \"${name}\": triggerBy match ${where} declares as a VIRTUAL computed field — virtual fields are evaluated on read and never stored, so the matcher would read every candidate record and match nothing, forever. Declare it as \\`mode: 'materialized'\\` (stored, indexable) or match on a stored field.`\n }\n\n /**\n * All registered strategies as a flat, deduplicated array.\n * Each strategy is indexed once per source (not once per output key),\n * so iterating `_bySource.values()` naturally yields each strategy\n * exactly once per source — deduplication is handled by flattening\n * the per-source arrays and collecting into a Set by identity.\n *\n * Used by `dumpSchema()` / `describeDerivations()` in the introspection\n * walker to populate the derivations map.\n */\n all(): ReadonlyArray<RegisteredStrategy> {\n const seen = new Set<RegisteredStrategy>()\n for (const strategies of this._bySource.values()) {\n for (const s of strategies) seen.add(s)\n }\n return [...seen]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'derivation'`/`'record'` edge per\n * (non-self-write) output key — target = the output collection (a\n * `WHOLE_RECORD` artifact node), sources = every collection that can\n * trigger this strategy (`source`, `sources[]`, `triggerBy[].collection`,\n * and — for a rollup — `rollup.from`), mirroring EXACTLY the trigger keys\n * `register()` indexes under `_bySource`. The self-write reverse-denorm\n * output (`output.collection === source` with `denorm` declared) is\n * skipped — the SAME condition the old local DFS used; it is not a cycle\n * (see the comment `validate()` used to carry, now on this skip).\n * Rollups ADDITIONALLY get a dedicated `'rollup'`/`'aggregate'` edge\n * targeting the real `rollup.field` on the parent (`source`) — the\n * self-write output above only covers the cycle-DFS skip, not\n * dispatch/taint (Tasks 3/4/6), which need the real field.\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.all()) {\n const spec = reg.spec\n const triggerCollections = [\n spec.source,\n ...(spec.sources ?? []),\n ...(spec.triggerBy ?? []).map((t) => t.collection),\n ...(spec.rollup ? [spec.rollup.from] : []),\n ]\n const sources: FieldRef[] = triggerCollections.map((c) => ({ collection: c, field: WHOLE_RECORD }))\n for (const key of Object.keys(spec.outputs)) {\n const output = spec.outputs[key]\n if (!output) continue\n if (output.shape === 'record' && output.collection === spec.source && output.denorm !== undefined) continue\n out.push({ target: { collection: output.collection, field: WHOLE_RECORD }, sources, kind: 'derivation', grain: 'record' })\n }\n if (spec.rollup) {\n out.push({\n target: { collection: spec.source, field: spec.rollup.field },\n sources: [{ collection: spec.rollup.from, field: WHOLE_RECORD }],\n kind: 'rollup',\n grain: 'aggregate',\n })\n }\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). Call after all `register()` calls complete (i.e.\n * at vault open). Throws `DerivationCycleError` on the first cycle found —\n * SAME class/timing as before; the message/path is byte-identical to the\n * pre-#638 shape for a pure record-shape derivation cycle (the\n * `WHOLE_RECORD` artifact suffix is stripped back to a bare collection\n * name before re-throwing).\n *\n * `graph` is the caller's shared per-vault graph, ALREADY carrying this\n * registry's `edges()` (the caller registers them — see `Vault._initDerivations`)\n * — omit it (e.g. this registry's own unit tests) to validate this\n * registry's edges in isolation against a throwaway graph.\n */\n validate(graph?: ViaGraph): void {\n const g = graph ?? new ViaGraph()\n if (!graph) {\n for (const edge of this.edges()) g.registerDerived(edge.target, edge.sources, edge.kind, edge.grain)\n }\n try {\n g.assertAcyclic()\n } catch (e) {\n if (e instanceof DerivationCycleError) throw new DerivationCycleError(e.path.map(stripArtifactSuffix))\n throw e\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/materialized-views/stale.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { MaterializedViewExecutor as MVExecutorType } from './executor.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { PutDerivedOutputCtx } from '../../kernel/via/dispatch.js'\nimport type { MaterializedViewRegistry, RegisteredMV } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleMVOnRead` only when a stale flag actually fires.\n// Keeps the executor chunk out of the floor bundle (mirrors v1 floor-bundle isolation).\nimport type { MVQueryContext } from './types.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { RecordCodec } from '../../kernel/enclave/index.js'\n\nimport { lazy } from '../../kernel/lazy.js'\n\n/** #846c — one memoized binding instead of a cast at the call site. */\nconst loadMVExecutor = lazy(() => import('./executor.js'))\n\n/**\n * Accessor shape passed in from the owning Vault. Provides the\n * registry (used as a stable WeakMap key + to look up MVs by output\n * collection) and the runtime context the lazy refresh needs.\n * Mirrors v1's `DerivationStaleAccessor`.\n */\nexport interface MVStaleAccessor {\n registry(): MaterializedViewRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n getQueryContext(): MVQueryContext\n}\n\n/**\n * In-memory stale map keyed by `MaterializedViewRegistry` instance\n * (stable per vault). Each registry maps to a set of MV names that\n * have at least one pending source-change requiring a re-materialize.\n *\n * Persistence across vault close is NOT implemented in this iteration\n * (concern flagged in the v2 spec, mirrors v1 derivation behavior).\n * On vault re-open, the unset stale flag is interpreted as \"fresh\" —\n * `vault.refreshView(name)` is the explicit recompute escape hatch.\n *\n * @internal\n */\nconst _staleByRegistry = new WeakMap<MaterializedViewRegistry, Set<string>>()\n\n/**\n * Mark an MV as stale. Called from `Collection.dispatchMaterializedViews`\n * when a source-write fires for a `refresh: 'lazy'` MV.\n *\n * @internal\n */\nexport function markMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n let set = _staleByRegistry.get(registry)\n if (!set) {\n set = new Set()\n _staleByRegistry.set(registry, set)\n }\n set.add(mvName)\n}\n\n/**\n * Test-only: check whether a given MV name is currently flagged stale\n * against a registry. Exported so the regression suite can pin the\n * stale-bit lifecycle without touching the internal `WeakMap`.\n *\n * @internal\n */\nexport function isMVStale(registry: MaterializedViewRegistry, mvName: string): boolean {\n return _staleByRegistry.get(registry)?.has(mvName) ?? false\n}\n\n/**\n * Reserved collection holding CONTENT-FREE lazy-stale markers (record id =\n * MV name, plaintext `_iv: ''` envelope, no payload) — mirrors the\n * `_meta`/schema-fence marker envelope shape. Written only from the\n * delete/forget/elevate dispatcher (#736); ordinary source writes stay\n * in-memory-only (the cheap path). Read via the adapter directly, never\n * through `vault.collection()` — same reserved-collection convention as\n * `_subject_index`/`_meta`.\n */\nexport const MV_STALE_COLLECTION = '_mv_stale'\n\n/**\n * Once-per-registry hydrate guard for `resolveStaleMVOnRead`'s persisted-marker fold-in.\n * Stores the in-flight/settled PROMISE (not a boolean) — a concurrent cold first read that\n * arrives while the hydrate is still awaiting `adapter.list` must await the SAME promise\n * rather than observing \"already hydrating\" and reading an empty pending set (#736\n * whole-branch review).\n */\nconst _hydratedByRegistry = new WeakMap<MaterializedViewRegistry, Promise<void>>()\n\n/** The adapter + vault name backing any collection in this vault (all collections share both). */\nfunction storeOf(accessor: MVStaleAccessor, collectionName: string): { adapter: NoydbStore; vault: string } {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const c = accessor.getCollection(collectionName) as any\n return { adapter: c.adapter as NoydbStore, vault: c.vault as string }\n}\n\nasync function writeMVStaleMarker(adapter: NoydbStore, vault: string, mvName: string): Promise<void> {\n // Content-free — the marker's only payload is its key (the MV name).\n const env = RecordCodec.buildPlaintextEnvelope({ collection: MV_STALE_COLLECTION, id: mvName, version: 1 }, { data: '{}' })\n await adapter.put(vault, MV_STALE_COLLECTION, mvName, env)\n}\n\nasync function deleteMVStaleMarker(adapter: NoydbStore, vault: string, mvName: string): Promise<void> {\n await adapter.delete(vault, MV_STALE_COLLECTION, mvName)\n}\n\n/**\n * On first `resolveStaleMVOnRead` call for a (cold-reopened) registry, fold every\n * persisted `_mv_stale` marker into the in-memory pending set — otherwise a cold\n * session's empty `WeakMap` entry reads as \"fresh\" and serves the emptied output\n * collection forever.\n */\nasync function hydratePersistedStaleMarkers(accessor: MVStaleAccessor, registry: MaterializedViewRegistry): Promise<void> {\n const mvs = registry.all()\n if (mvs.length === 0) return\n const { adapter, vault } = storeOf(accessor, mvs[0]!.outputCollection)\n // No try/catch: a genuine store failure here must surface, not be read as\n // \"nothing pending\" — silently treating it as fresh is the exact leak class\n // this module closes (mirrors `subject-index.ts`'s bare `adapter.list` reads).\n for (const name of await adapter.list(vault, MV_STALE_COLLECTION)) markMVStale(registry, name)\n}\n\n/**\n * Invalidate an MV from the delete/forget/elevate dispatcher (#736) — the ordinary\n * source-write path (`dispatchMaterializedViews`) never calls this. Deletes EVERY\n * persisted row in `reg.outputCollection` via `_internalDelete` (the at-rest law: a\n * stale mark alone leaves the elevated/forgotten source's plaintext sitting in the\n * output row) using the adapter directly so the read-path's `resolveStaleMVOnRead`\n * isn't triggered mid-invalidation. `mode: 'lazy'` also persists the stale mark so a\n * cold session recomputes on next read; `'manual'` gets the purge only — the MV\n * serves empty until an explicit `vault.refreshView()` (erasure wins). Deletion is\n * stamp-scoped (`_materializedFrom.mvName === reg.spec.name` — see the loop below),\n * so another registered MV sharing this output collection keeps its own rows intact:\n * no cross-MV re-marking is needed here, only `reg` itself is marked stale.\n *\n * Returns the count of rows actually `_internalDelete`d (#761 item 1) — folded by the\n * caller into `dispatchMaterializedViewsOnDelete`'s `deleted` so `ForgetResult.derivedRecordsErased`\n * counts lazy/manual purges too, not just the eager executor's tombstone leg — plus\n * `residueUndecodable`, the bare ids whose ownership stamp could NOT be decoded (#776): a\n * candidate row that fails to decode under the collection's default DEK (e.g. elevated above\n * tier 0 on a tiered output collection) has UNKNOWN ownership — it might be this MV's own\n * output, or (same-collection shape) a plain user record — so it is never erased, but it must\n * be SURFACED rather than silently skipped (the #724 posture), since it may still hold the\n * forgotten/pre-elevation contribution, decryptable by tier-holders — and `residueDeclined`\n * (#785), the bare ids that DID decode and stamp-match but whose `_internalDelete` declined\n * (ownership CONFIRMED, erasure declined).\n *\n * @internal\n */\nexport async function invalidateMVAtRest(\n accessor: MVStaleAccessor,\n reg: RegisteredMV,\n mode: 'lazy' | 'manual',\n): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[] }> {\n const outputColl = accessor.getCollection(reg.outputCollection)\n const txCtx = accessor.getActiveTxContext()\n const { adapter, vault } = storeOf(accessor, reg.outputCollection)\n let deleted = 0\n const residueUndecodable: string[] = []\n const residueDeclined: string[] = []\n for (const id of await adapter.list(vault, reg.outputCollection)) {\n // A same-collection partition MV (`output: { collection: <source>, partition }`,\n // the DERIV-PP30-001 shape) writes INTO its own source collection — `adapter.list`\n // over `reg.outputCollection` then also returns untouched user source records.\n // Decode each candidate row (the invalidation path is rare; this cost is fine\n // here) and only erase rows THIS MV stamped via `_materializedFrom.mvName` —\n // an unstamped row, or one stamped by a different MV, is never this MV's to\n // delete (#736 whole-branch review, Critical).\n const envelope = await adapter.get(vault, reg.outputCollection, id)\n if (envelope === null) continue\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const decoded = await (outputColl as any)._decodeEnvelope(envelope, id)\n if (decoded === null) { residueUndecodable.push(id); continue } // #776 — ownership unknown, surfaced not skipped\n const stampedBy = (decoded as Record<string, unknown>)._materializedFrom as { mvName?: string } | undefined\n if (stampedBy?.mvName !== reg.spec.name) continue\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (await (outputColl as any)._internalDelete(id, txCtx)) {\n deleted++\n } else {\n // #782 part b — decoded AND stamp-owned, but erasure declined (#718 tier-elevation\n // gate). Ownership IS confirmed — a real silent survival, not a legit stamp-mismatch\n // skip. Surface it too, in its own channel (#785 — distinct from the decode-null case).\n residueDeclined.push(id)\n }\n }\n\n // `mode: 'manual'` gets the purge only (erasure wins, no auto-recompute promise);\n // `'lazy'` also marks + persists ITS OWN stale bit for cold-session recompute. No\n // sibling MV on the same output collection is touched here — the stamp-scoped\n // deletion above never erased sibling rows in the first place (#736 re-review).\n if (mode === 'lazy') {\n const registry = accessor.registry()\n markMVStale(registry, reg.spec.name)\n await writeMVStaleMarker(adapter, vault, reg.spec.name)\n }\n return { deleted, residueUndecodable, residueDeclined }\n}\n\n/**\n * Called from `Collection.get` (and any reader that materializes the\n * MV's output collection). If any MV producing `outputCollection` is\n * flagged stale, runs the executor against the live source state\n * before returning. No-op when there is no pending work — keeps the\n * read fast path negligible.\n *\n * `dispatchCtx` (#641): threaded from the calling `Collection`'s\n * `#dispatchCtx({ collection: outputCollection, id: 'resolve-on-read' })` — a read has no\n * real \"reacting write\" record, so `'resolve-on-read'` is the sentinel id, mirroring\n * `Vault.refreshView()`'s `'refreshView'` sentinel for the same reason. Passed straight\n * through to the executor so a stale row whose output lands in a closed period follows the\n * frozen-output rule (skip + `derivation:skipped-frozen`, #637) instead of throwing\n * `PeriodClosedError` out of a read.\n *\n * Dynamic-imports the executor only when a stale flag actually fires\n * (the floor-bundle isolation pattern v1 derivations established in\n * floor-bundle isolation pattern).\n */\nexport async function resolveStaleMVOnRead(\n accessor: MVStaleAccessor,\n outputCollection: string,\n dispatchCtx?: PutDerivedOutputCtx,\n): Promise<void> {\n const registry = accessor.registry()\n // Cold session: the in-memory WeakMap has no entry for this (freshly\n // constructed) registry yet — fold any persisted `_mv_stale` markers in\n // before reading the pending set, once per registry (#736). Memoize the\n // PROMISE, not a boolean: a concurrent first read arriving while the\n // hydrate is still awaiting `adapter.list` must await the SAME promise —\n // otherwise it observes \"already hydrating\", skips straight to an empty\n // pending set, and serves the purged/stale view as fresh (#736\n // whole-branch review, hydrate-once race). On rejection, evict the entry\n // BEFORE rethrowing — a cached rejected promise would otherwise poison the\n // MV read surface for the rest of the session (every later read re-awaits\n // the same rejection instead of retrying the store call) (#736 re-review).\n let hydrate = _hydratedByRegistry.get(registry)\n if (!hydrate) {\n hydrate = hydratePersistedStaleMarkers(accessor, registry).catch((err: unknown) => {\n _hydratedByRegistry.delete(registry)\n throw err\n })\n _hydratedByRegistry.set(registry, hydrate)\n }\n await hydrate\n const pending = _staleByRegistry.get(registry)\n if (!pending || pending.size === 0) return\n\n const { adapter, vault } = storeOf(accessor, outputCollection)\n\n // Clean up persisted markers for MV names no longer registered (renamed or\n // removed since the marker was written). Without this sweep an orphaned name\n // can never become a `candidate` below — `candidates` is built from\n // `registry.all()`, which no longer lists it — so both the in-memory pending\n // entry and the persisted `_mv_stale` row would linger forever, re-hydrating\n // on every cold session (#736 whole-branch review).\n for (const name of pending) {\n if (registry.byName(name)) continue\n pending.delete(name)\n await deleteMVStaleMarker(adapter, vault, name)\n }\n if (pending.size === 0) return\n\n // Find every MV that writes to this output collection AND is\n // currently flagged stale. Multiple MVs CAN share an output\n // collection in theory; in practice the registration validation +\n // cycle detection make this unusual.\n const candidates: string[] = []\n for (const mv of registry.all()) {\n if (mv.outputCollection !== outputCollection) continue\n if (!pending.has(mv.spec.name)) continue\n candidates.push(mv.spec.name)\n }\n if (candidates.length === 0) return\n\n let executor: typeof MVExecutorType | null = null\n for (const name of candidates) {\n const reg = registry.byName(name)\n if (!reg) {\n // Unreachable in practice — `candidates` is drawn from `registry.all()`,\n // and the orphan sweep above already cleared any name absent from it.\n // Kept as a defensive fallback; also clears the persisted marker so it\n // can't linger if this ever does fire.\n pending.delete(name)\n await deleteMVStaleMarker(adapter, vault, name)\n continue\n }\n executor ??= (await loadMVExecutor()).MaterializedViewExecutor\n // A cold-hydrated stale flag (unlike an in-session one) can fire with\n // NONE of the MV's dependency collections touched yet this session —\n // the sync `Query.toArray()` `spec.query()` runs reads straight off\n // `Collection`'s in-memory cache, populated only by a prior async\n // touch (`ensureHydrated()`, called from `get`/`list`/`put`, never\n // from `query()` itself). Warm every dependency first so the recompute\n // sees the live persisted state instead of an empty cache (#736).\n for (const dep of reg.dependencies) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (accessor.getCollection(dep) as any).ensureHydrated()\n }\n await executor.refresh(reg, {\n getCollection: (n) => accessor.getCollection(n),\n getActiveTxContext: () => accessor.getActiveTxContext(),\n getQueryContext: () => accessor.getQueryContext(),\n ...(dispatchCtx !== undefined ? { dispatchCtx } : {}),\n })\n pending.delete(name)\n // Runs even when no marker was ever persisted for this name (an in-session-only\n // stale bit — e.g. the cheap ordinary-write path never writes one, see the\n // \"cheap-path guarantee\" test). `adapter.delete` on an absent key is a harmless\n // void — keeping this unconditional avoids branching on marker origin here.\n await deleteMVStaleMarker(adapter, vault, name)\n }\n}\n\n/**\n * Drop every stale flag for a registry. Used after a manual\n * `vault.refreshView(name)` runs the executor explicitly — the\n * post-refresh state matches the registered strategies, so\n * lingering stale bits would force a redundant refresh on the next\n * read.\n *\n * @internal\n */\nexport function clearMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n _staleByRegistry.get(registry)?.delete(mvName)\n}\n\n/**\n * `clearMVStale` plus the persisted marker (#736) — `Vault.refreshView()`'s\n * completion path. A lingering persisted marker after a manual refresh would\n * make the NEXT cold session recompute redundantly.\n *\n * @internal\n */\nexport async function clearMVStaleFully(\n adapter: NoydbStore,\n vault: string,\n registry: MaterializedViewRegistry,\n mvName: string,\n): Promise<void> {\n clearMVStale(registry, mvName)\n await deleteMVStaleMarker(adapter, vault, mvName)\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-audit/attestation/strategy.ts"],"sourcesContent":["import type { NoydbStore } from '../../kernel/types.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { RevocationList } from '@noy-db/attestation'\nimport type { IssueContext, IssueArgs, IssueResult } from './issue.js'\nimport type { RevokeContext } from './revoke.js'\nimport { AttestationNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * Deps the signing-public-key lookup needs. Unlike issue/revoke there is no\n * per-call context object for it, so the vault facade assembles this at call\n * time (reading `role` fresh).\n */\nexport interface SignerLookupDeps {\n readonly adapter: NoydbStore\n readonly vault: string\n /** The invoking keyring's role, read fresh by the caller per call. */\n readonly role: string\n /** Per-collection DEK resolver (bound `vault.getDEK`). */\n readonly getDEK: (collection: string) => Promise<EnclaveKey>\n}\n\n/**\n * Attestation capability strategy — the six on-demand methods the vault's\n * attestation delegators route through. The active engine ({@link withAttestation})\n * dynamically imports the issue/revoke/signer cores (keeping them out of the floor\n * bundle); {@link NO_ATTESTATION} throws. The vault-side {@link VaultAttestation}\n * facade holds the per-collection field-schema registry and builds the per-call\n * contexts, then delegates here.\n * @internal\n */\nexport interface AttestationStrategy {\n issueAttestation(ctx: IssueContext, args: IssueArgs): Promise<IssueResult>\n getDocumentSigningPublicKey(deps: SignerLookupDeps): Promise<{ keyId: string; publicKeyB64: string }>\n revokeAttestation(ctx: RevokeContext, docId: string): Promise<void>\n unrevokeAttestation(ctx: RevokeContext, docId: string): Promise<void>\n getRevokedDocIds(ctx: RevokeContext): Promise<string[]>\n publishRevocationList(ctx: RevokeContext): Promise<RevocationList>\n}\n\n/**\n * No-op stub — the floor default. Every capability method throws\n * {@link AttestationNotEnabledError}; opt in with\n * `attestationStrategy: withAttestation()` in createNoydb. @internal\n */\nexport const NO_ATTESTATION: AttestationStrategy = {\n async issueAttestation() { throw new AttestationNotEnabledError() },\n async getDocumentSigningPublicKey() { throw new AttestationNotEnabledError() },\n async revokeAttestation() { throw new AttestationNotEnabledError() },\n async unrevokeAttestation() { throw new AttestationNotEnabledError() },\n async getRevokedDocIds() { throw new AttestationNotEnabledError() },\n async publishRevocationList() { throw new AttestationNotEnabledError() },\n}\n"],"mappings":";;;;;AA4CO,IAAM,iBAAsC;AAAA,EACjD,MAAM,mBAAmB;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AAAA,EAClE,MAAM,8BAA8B;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AAAA,EAC7E,MAAM,oBAAoB;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AAAA,EACnE,MAAM,sBAAsB;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AAAA,EACrE,MAAM,mBAAmB;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AAAA,EAClE,MAAM,wBAAwB;AAAE,UAAM,IAAI,2BAA2B;AAAA,EAAE;AACzE;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-party/session/session.ts","../src/with-party/session/session-policy.ts","../src/with-party/session/dev-unlock.ts"],"sourcesContent":["/**\n * Session tokens —\n *\n * After a vault is unlocked (via secret, WebAuthn, OIDC, or magic-\n * link), the caller can call `createSession()` to get a session token that\n * allows re-establishing the KEK for the session lifetime without re-running\n * PBKDF2 or any interactive auth challenge.\n *\n * Security model\n * ──────────────\n * A session consists of two pieces that must both be present to recover the\n * KEK:\n *\n * 1. The **session key** — a non-extractable AES-256-GCM CryptoKey that\n * exists only in memory. \"Non-extractable\" is enforced by the WebCrypto\n * API: the key object cannot be serialized, exported, or sent over\n * postMessage. When the JS context is GC'd (tab close, navigation away,\n * worker termination) the key becomes unrecoverable.\n *\n * 2. The **session token** — a JSON object that carries the KEK wrapped\n * with the session key (AES-256-GCM, fresh IV per session), plus\n * unencrypted session metadata (sessionId, userId, vault, role,\n * expiresAt). The token can be serialized to JSON and stored in\n * sessionStorage or passed across callsites within the same tab, but\n * it is useless without the session key.\n *\n * The session key is kept in a module-level Map indexed by sessionId. Callers\n * that need to re-use a session must hold on to the sessionId returned from\n * `createSession()`; the key is looked up automatically by `resolveSession()`.\n *\n * Revocation: `revokeSession()` removes the entry from the Map. Because the\n * key is non-extractable, removal is sufficient — no one holds a serializable\n * copy of the key.\n *\n * Tab-scoped lifetime: the module-level Map lives only as long as the JS\n * module. Tab close → module unloaded → Map GC'd → all session keys gone.\n * This is the zero-effort logout: closing the tab is always a secure logout.\n *\n * Expiry: `createSession()` accepts a `ttlMs` option. `resolveSession()`\n * checks `expiresAt` and throws `SessionExpiredError` if the token is stale,\n * even if the session key is still in the Map.\n */\n\nimport { bufferToBase64, base64ToBuffer, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport type { Role } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport { SessionExpiredError, SessionNotFoundError } from '../../kernel/errors.js'\n\nconst subtle = globalThis.crypto.subtle\n\n// Default session TTL: 60 minutes\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\n\n// Module-level session key store. Tab-scoped by construction.\nconst sessionKeyStore = new Map<string, CryptoKey>()\n\n// ─── Public types ──────────────────────────────────────────────────────\n\n/** The serializable part of a session token. Safe to store in sessionStorage. */\nexport interface SessionToken {\n readonly _noydb_session: 1\n /** Unique session identifier (ULID). Use this as the handle for resolve/revoke. */\n readonly sessionId: string\n readonly userId: string\n readonly vault: string\n readonly role: Role\n /** ISO timestamp — resolveSession() rejects this token after this time. */\n readonly expiresAt: string\n /** KEK wrapped with the session key (AES-256-GCM). Base64. */\n readonly wrappedKek: string\n /** IV used for the wrapping operation. Base64. */\n readonly kekIv: string\n}\n\n/** Result returned from `createSession()`. */\nexport interface CreateSessionResult {\n /** Serializable token — store in sessionStorage or pass to `resolveSession()`. */\n token: SessionToken\n /** The sessionId — use this handle for `resolveSession()` and `revokeSession()`. */\n sessionId: string\n}\n\n/** Options for `createSession()`. */\nexport interface CreateSessionOptions {\n /**\n * Session lifetime in milliseconds. Defaults to 60 minutes.\n * After this duration, `resolveSession()` throws `SessionExpiredError`.\n */\n ttlMs?: number\n}\n\n// ─── Core session operations ───────────────────────────────────────────\n\n/**\n * Create a session for an already-unlocked keyring.\n *\n * Call this after any successful unlock (secret, WebAuthn, OIDC,\n * magic-link). The returned `sessionId` is the handle for later\n * `resolveSession()` and `revokeSession()` calls.\n *\n * The session key is generated fresh (non-extractable) and stored in the\n * module-level Map. The KEK from `keyring.kek` is exported (it must be\n * extractable — it was derived by `deriveKey()` which sets extractable: false,\n * but it's unwrapped from the keyring which sets extractable: true) and then\n * re-wrapped with the session key.\n *\n * @param keyring - An already-unlocked keyring whose `kek` is available.\n * @param vault - The vault name this session is scoped to.\n * @param options - Optional session configuration.\n */\nexport async function createSession(\n keyring: UnlockedKeyring,\n vault: string,\n options: CreateSessionOptions = {},\n): Promise<CreateSessionResult> {\n const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n const sessionId = generateULID()\n const expiresAt = new Date(Date.now() + ttlMs).toISOString()\n\n // Generate a fresh non-extractable session key.\n // AES-256-GCM is used here (rather than AES-KW) because the session key\n // wraps raw key bytes (the exported KEK) rather than a CryptoKey object.\n const sessionKey = await subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n false, // non-extractable — this is the tab-scope security invariant\n ['encrypt', 'decrypt'],\n )\n\n // Export the KEK as raw bytes so we can wrap it.\n // The KEK is AES-256-KW, which must have been importable (extractable: true)\n // to allow wrapKey — it is, because unwrapKey sets extractable: true for\n // DEKs, but the KEK itself is derived with extractable: false (see\n // crypto.ts deriveKey). We use a separate raw export + encrypt path.\n //\n // Wait — the KEK is AES-KW with extractable:false. We cannot export it.\n // Instead, we wrap the DEKs (which ARE extractable) and the salt+role+userId\n // metadata together. This means resolveSession() reconstructs an\n // UnlockedKeyring by re-wrapping the DEKs list from the token.\n //\n // Simpler approach: export each DEK (they're extractable) and encrypt\n // the serialized DEK map with the session key. The keyring is reconstructed\n // from the session token without the original KEK — only DEKs matter for\n // record operations.\n //\n // This is the right design: sessions don't need the KEK (no re-grant,\n // no re-derive during session lifetime). They need the DEK set.\n\n const dekMap: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n const raw = await subtle.exportKey('raw', dek)\n dekMap[collName] = bufferToBase64(raw)\n }\n\n const payload = JSON.stringify({\n userId: keyring.userId,\n displayName: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: dekMap,\n salt: bufferToBase64(keyring.salt),\n })\n\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))\n const encrypted = await subtle.encrypt(\n { name: 'AES-GCM', iv },\n sessionKey,\n new TextEncoder().encode(payload),\n )\n\n const token: SessionToken = {\n _noydb_session: 1,\n sessionId,\n userId: keyring.userId,\n vault,\n role: keyring.role,\n expiresAt,\n wrappedKek: bufferToBase64(encrypted),\n kekIv: bufferToBase64(iv),\n }\n\n sessionKeyStore.set(sessionId, sessionKey)\n return { token, sessionId }\n}\n\n/**\n * Resolve a session token back into an UnlockedKeyring.\n *\n * Looks up the session key by `sessionId`, checks the token is not expired,\n * then decrypts the payload to reconstruct the keyring's DEK set.\n *\n * Throws `SessionExpiredError` if the token's `expiresAt` is in the past.\n * Throws `SessionNotFoundError` if the session key is not in the store\n * (tab was reloaded, session was revoked, or the sessionId is wrong).\n *\n * @param token - The SessionToken from `createSession()`.\n */\nexport async function resolveSession(token: SessionToken): Promise<UnlockedKeyring> {\n // Expiry check first — fast path without touching crypto\n if (Date.now() > new Date(token.expiresAt).getTime()) {\n sessionKeyStore.delete(token.sessionId)\n throw new SessionExpiredError(token.sessionId)\n }\n\n const sessionKey = sessionKeyStore.get(token.sessionId)\n if (!sessionKey) {\n throw new SessionNotFoundError(token.sessionId)\n }\n\n const iv = base64ToBuffer(token.kekIv)\n const ciphertext = base64ToBuffer(token.wrappedKek)\n\n let plaintext: ArrayBuffer\n try {\n plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv },\n sessionKey,\n ciphertext,\n )\n } catch {\n throw new SessionNotFoundError(token.sessionId)\n }\n\n const payload = JSON.parse(new TextDecoder().decode(plaintext)) as {\n userId: string\n displayName: string\n role: Role\n permissions: Record<string, 'rw' | 'ro'>\n deks: Record<string, string>\n salt: string\n }\n\n const deks = new Map<string, EnclaveKey>()\n for (const [collName, rawBase64] of Object.entries(payload.deks)) {\n const dek = await subtle.importKey(\n 'raw',\n base64ToBuffer(rawBase64),\n { name: 'AES-GCM', length: 256 },\n true,\n ['encrypt', 'decrypt'],\n )\n deks.set(collName, dek)\n }\n\n return {\n userId: payload.userId,\n displayName: payload.displayName,\n role: payload.role,\n permissions: payload.permissions,\n deks,\n kek: null, // KEK not available in session context\n salt: base64ToBuffer(payload.salt),\n authenticators: [],\n }\n}\n\n/**\n * Revoke a session by removing its key from the store.\n *\n * After revocation, `resolveSession()` will throw `SessionNotFoundError`\n * for this sessionId. The session token (if held by the caller) becomes\n * permanently useless. This is the explicit logout path.\n *\n * No-op if the session was already expired or does not exist.\n */\nexport function revokeSession(sessionId: string): void {\n sessionKeyStore.delete(sessionId)\n}\n\n/**\n * Check if a session is still alive (key in store + not expired).\n * Does not decrypt anything — purely a metadata check.\n */\nexport function isSessionAlive(token: SessionToken): boolean {\n if (Date.now() > new Date(token.expiresAt).getTime()) return false\n return sessionKeyStore.has(token.sessionId)\n}\n\n/**\n * Revoke all active sessions. Used by `Noydb.close()` to ensure that\n * closing the instance destroys all session state, not just the keyring\n * cache.\n */\nexport function revokeAllSessions(): void {\n sessionKeyStore.clear()\n}\n\n/**\n * Return the number of active sessions currently in the store.\n * Useful for diagnostics and tests.\n */\nexport function activeSessionCount(): number {\n return sessionKeyStore.size\n}\n","/**\n * Session policies —\n *\n * A `SessionPolicy` is a small declarative object that controls how long a\n * session lives and which operations require re-authentication. It is\n * evaluated by the `PolicyEnforcer` class, which the Noydb instance\n * integrates to replace the bare `sessionTimeout` timer from.\n *\n * Design decisions\n * ────────────────\n * Policies are stateless value objects — no timers, no event listeners.\n * The Noydb instance is the stateful coordinator: it holds the enforcer,\n * calls `enforcer.touch()` on every operation, and calls\n * `enforcer.checkOperation()` before high-risk operations.\n *\n * This keeps the policy module easy to unit-test (no global timers to mock)\n * and avoids the \"who owns cleanup\" problem that comes with timer-based\n * callbacks embedded in a value object.\n *\n * `lockOnBackground` registers a `visibilitychange` listener on the document\n * at enforcer creation time and removes it on `destroy()`. It is a no-op in\n * non-browser environments (no `document`).\n */\n\nimport type { SessionPolicy, ReAuthOperation } from '../../kernel/types.js'\nimport { SessionExpiredError, SessionPolicyError } from '../../kernel/errors.js'\nimport { revokeSession } from './session.js'\n\n// ─── PolicyEnforcer ────────────────────────────────────────────────────\n\nexport interface PolicyEnforcerOptions {\n /** The policy to enforce. */\n policy: SessionPolicy\n /** The session ID to revoke when idle/absolute timeouts fire. */\n sessionId: string\n /**\n * Called when the policy decides the session should end (idle timeout,\n * absolute timeout, or lockOnBackground). Use this to trigger the\n * same cleanup that `Noydb.close()` would perform.\n */\n onRevoke: (reason: 'idle' | 'absolute' | 'background') => void\n}\n\n/**\n * Stateful enforcer for a single session policy.\n *\n * Create one per open session, call `touch()` on every operation,\n * call `checkOperation(op)` before export/grant/revoke/rotate/changeSecret,\n * and call `destroy()` when the session ends.\n */\nexport class PolicyEnforcer {\n private readonly policy: SessionPolicy\n private readonly sessionId: string\n private readonly onRevoke: PolicyEnforcerOptions['onRevoke']\n private readonly createdAt: number\n private lastActivityAt: number\n private idleTimer: ReturnType<typeof setTimeout> | null = null\n private absoluteTimer: ReturnType<typeof setTimeout> | null = null\n private visibilityHandler: (() => void) | null = null\n\n constructor(opts: PolicyEnforcerOptions) {\n this.policy = opts.policy\n this.sessionId = opts.sessionId\n this.onRevoke = opts.onRevoke\n this.createdAt = Date.now()\n this.lastActivityAt = Date.now()\n\n this.scheduleIdleTimer()\n this.scheduleAbsoluteTimer()\n this.registerBackgroundLock()\n }\n\n /**\n * Record an activity timestamp and reset the idle timer.\n * Call this at the top of every Noydb public method.\n */\n touch(): void {\n this.lastActivityAt = Date.now()\n this.scheduleIdleTimer()\n }\n\n /**\n * Check whether the given operation is allowed under the active policy.\n * Throws `SessionPolicyError` if the operation requires re-authentication.\n * Throws `SessionExpiredError` if the absolute timeout has been exceeded\n * (defensive check in case the timer fired before the call arrived).\n *\n * This is a synchronous check — callers don't await it.\n */\n checkOperation(op: ReAuthOperation): void {\n // Defensive absolute-timeout check (timer may have fired late)\n const { absoluteTimeoutMs } = this.policy\n if (absoluteTimeoutMs !== undefined && Date.now() - this.createdAt >= absoluteTimeoutMs) {\n this.expire('absolute')\n throw new SessionExpiredError(this.sessionId)\n }\n\n const required = this.policy.requireReAuthFor ?? []\n if (required.includes(op)) {\n throw new SessionPolicyError(op)\n }\n }\n\n /**\n * Tear down timers and background-lock listener. Call from `Noydb.close()`\n * and whenever the session is revoked externally.\n */\n destroy(): void {\n if (this.idleTimer) {\n clearTimeout(this.idleTimer)\n this.idleTimer = null\n }\n if (this.absoluteTimer) {\n clearTimeout(this.absoluteTimer)\n this.absoluteTimer = null\n }\n if (this.visibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.visibilityHandler)\n this.visibilityHandler = null\n }\n }\n\n /** How long since the last activity, in ms. */\n get idleMs(): number {\n return Date.now() - this.lastActivityAt\n }\n\n /** How long since session creation, in ms. */\n get ageMs(): number {\n return Date.now() - this.createdAt\n }\n\n // ── Private ──────────────────────────────────────────────────────────\n\n private scheduleIdleTimer(): void {\n const { idleTimeoutMs } = this.policy\n if (!idleTimeoutMs) return\n\n if (this.idleTimer) clearTimeout(this.idleTimer)\n this.idleTimer = setTimeout(() => {\n this.expire('idle')\n }, idleTimeoutMs)\n }\n\n private scheduleAbsoluteTimer(): void {\n const { absoluteTimeoutMs } = this.policy\n if (!absoluteTimeoutMs) return\n\n if (this.absoluteTimer) clearTimeout(this.absoluteTimer)\n this.absoluteTimer = setTimeout(() => {\n this.expire('absolute')\n }, absoluteTimeoutMs)\n }\n\n private registerBackgroundLock(): void {\n if (!this.policy.lockOnBackground) return\n if (typeof document === 'undefined') return\n\n this.visibilityHandler = () => {\n if (document.hidden) {\n this.expire('background')\n }\n }\n document.addEventListener('visibilitychange', this.visibilityHandler)\n }\n\n private expire(reason: 'idle' | 'absolute' | 'background'): void {\n this.destroy()\n revokeSession(this.sessionId)\n this.onRevoke(reason)\n }\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────\n\n/**\n * Build a `PolicyEnforcer` from a policy + session token, and return it\n * alongside a cleanup function. Convenience wrapper for Noydb.\n */\nexport function createEnforcer(opts: PolicyEnforcerOptions): PolicyEnforcer {\n return new PolicyEnforcer(opts)\n}\n\n/**\n * Validate that a `SessionPolicy` is well-formed.\n * Throws a plain `Error` (not `NoydbError`) because this is a developer\n * error — invalid policies passed at construction time, not at runtime.\n */\nexport function validateSessionPolicy(policy: SessionPolicy): void {\n const { idleTimeoutMs, absoluteTimeoutMs } = policy\n if (idleTimeoutMs !== undefined && (typeof idleTimeoutMs !== 'number' || idleTimeoutMs <= 0)) {\n throw new Error(`SessionPolicy.idleTimeoutMs must be a positive number, got ${idleTimeoutMs}`)\n }\n if (absoluteTimeoutMs !== undefined && (typeof absoluteTimeoutMs !== 'number' || absoluteTimeoutMs <= 0)) {\n throw new Error(`SessionPolicy.absoluteTimeoutMs must be a positive number, got ${absoluteTimeoutMs}`)\n }\n if (idleTimeoutMs !== undefined && absoluteTimeoutMs !== undefined && idleTimeoutMs >= absoluteTimeoutMs) {\n throw new Error(\n `SessionPolicy.idleTimeoutMs (${idleTimeoutMs}ms) must be less than absoluteTimeoutMs (${absoluteTimeoutMs}ms)`,\n )\n }\n}\n","/**\n * Dev-mode persistent unlock —\n *\n * Solves the developer inner-loop friction: hot-reload destroys the session\n * (page navigation semantics), forcing a secret re-entry every refresh.\n *\n * This module provides an opt-in, deliberately-named escape hatch that lets\n * developers store the keyring payload in sessionStorage or localStorage so\n * the vault auto-unlocks on every page load — without a secret,\n * without a biometric prompt, without any OIDC flow.\n *\n * ⚠️ WARNING — this is a loaded footgun ⚠️\n * ─────────────────────────────────────────\n * The keyring payload stored by this module contains the DEKs. Whoever has\n * access to sessionStorage/localStorage has access to the DEKs. On a shared\n * development machine, a compromised browser extension, or a mis-configured\n * origin, this is a complete key exposure.\n *\n * This module is ONLY safe for local development. It must NEVER be active\n * in production builds.\n *\n * Guardrails (all enforced by the module, not by the caller)\n * ──────────────────────────────────────────────────────────\n * 1. **Production guard:** `enableDevUnlock()` throws immediately if\n * `process.env.NODE_ENV === 'production'` or if `import.meta.env?.PROD === true`\n * (Vite convention). Also throws if the hostname is NOT localhost or 127.0.0.1.\n *\n * 2. **Explicit acknowledgement string:** the caller must pass\n * `acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'` or the call\n * throws. This string appears in every grep for `devUnlock` in the codebase,\n * making it impossible to enable this feature accidentally.\n *\n * 3. **Scope is vault + userId:** the storage key includes both the\n * vault name and the userId, so dev-unlock for vault-A does\n * NOT auto-unlock vault-B.\n *\n * 4. **Storage scope:** default is `sessionStorage` (cleared on tab close).\n * `localStorage` is opt-in and requires an additional\n * `persistAcrossTabs: true` flag in the options.\n *\n * 5. **Clear method:** `clearDevUnlock()` removes the stored payload. Wire\n * this to a dev toolbar button or `Ctrl+Shift+L` so clearing is one action.\n *\n * 6. **Console banner:** on first enable, a highly visible console warning\n * fires. Cannot be suppressed.\n *\n * Usage\n * ─────\n * ```ts\n * // In your dev entry point only (guarded by import.meta.env.DEV):\n * if (import.meta.env.DEV) {\n * const { enableDevUnlock, loadDevUnlock } = await import('@noy-db/hub')\n * enableDevUnlock('my-compartment', 'alice', keyring, {\n * acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY',\n * })\n * }\n *\n * // On page load:\n * if (import.meta.env.DEV) {\n * const keyring = await loadDevUnlock('my-compartment', 'alice')\n * if (keyring) {\n * // Skip unlock prompt, use keyring directly\n * }\n * }\n * ```\n */\n\nimport { bufferToBase64, base64ToBuffer, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { ValidationError } from '../../kernel/errors.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport type { Role } from '../../kernel/types.js'\n\n// The exact acknowledgement string callers must pass\nconst REQUIRED_ACKNOWLEDGE = 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'\n\nconst STORAGE_PREFIX = 'noydb:dev-unlock:'\n\n// ─── Options ──────────────────────────────────────────────────────────\n\nexport interface DevUnlockOptions {\n /**\n * Required: the exact string 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'.\n * Any other value causes `enableDevUnlock()` to throw.\n */\n acknowledge: string\n /**\n * If `true`, stores in localStorage (persists across tabs and browser restarts).\n * If `false` (default), stores in sessionStorage (cleared on tab close).\n */\n persistAcrossTabs?: boolean\n}\n\n// ─── Production guard ─────────────────────────────────────────────────\n\nfunction assertDevEnvironment(): void {\n // Node.js: check NODE_ENV\n if (\n typeof process !== 'undefined' &&\n process.env.NODE_ENV === 'production'\n ) {\n throw new ValidationError(\n 'devUnlock is not available in production builds. ' +\n 'process.env.NODE_ENV is \"production\".',\n )\n }\n\n // Vite / build tool convention\n if (\n typeof globalThis !== 'undefined' &&\n (globalThis as Record<string, unknown>).__vite_is_production__ === true\n ) {\n throw new ValidationError('devUnlock is not available in production builds.')\n }\n\n // Browser: only allow on localhost\n if (\n typeof window !== 'undefined' &&\n typeof window.location !== 'undefined'\n ) {\n const host = window.location.hostname\n if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && !host.endsWith('.local')) {\n throw new ValidationError(\n `devUnlock is only available on localhost. Current hostname: \"${host}\". ` +\n 'Set NODE_ENV=development and run on localhost to use dev unlock.',\n )\n }\n }\n}\n\n// ─── Storage key ──────────────────────────────────────────────────────\n\nfunction storageKey(vault: string, userId: string): string {\n return `${STORAGE_PREFIX}${vault}:${userId}`\n}\n\nfunction resolveStorage(persistAcrossTabs?: boolean): Storage {\n if (typeof window === 'undefined') {\n throw new ValidationError('devUnlock requires a browser environment (window.sessionStorage / window.localStorage).')\n }\n return persistAcrossTabs ? window.localStorage : window.sessionStorage\n}\n\n// ─── Public API ────────────────────────────────────────────────────────\n\n/**\n * Serialize and store a keyring to browser storage for dev-mode auto-unlock.\n *\n * Throws immediately if:\n * - The acknowledge string is wrong.\n * - Running in a production environment (NODE_ENV=production).\n * - Running on a non-localhost hostname.\n *\n * Emits a highly visible console warning that cannot be suppressed.\n *\n * @param vault - The vault name.\n * @param userId - The user ID.\n * @param keyring - The unlocked keyring to persist.\n * @param options - Options including the required acknowledge string.\n */\nexport async function enableDevUnlock(\n vault: string,\n userId: string,\n keyring: UnlockedKeyring,\n options: DevUnlockOptions,\n): Promise<void> {\n if (options.acknowledge !== REQUIRED_ACKNOWLEDGE) {\n throw new ValidationError(\n `devUnlock requires acknowledge: '${REQUIRED_ACKNOWLEDGE}'. ` +\n `Got: '${options.acknowledge}'. This is intentional — the full string must appear in your source.`,\n )\n }\n\n assertDevEnvironment()\n\n const storage = resolveStorage(options.persistAcrossTabs)\n\n const dekMap: Record<string, string> = {}\n for (const [collName, dek] of keyring.deks) {\n const raw = await globalThis.crypto.subtle.exportKey('raw', dek)\n dekMap[collName] = bufferToBase64(raw)\n }\n\n const payload = JSON.stringify({\n _noydb_dev_unlock: 1,\n userId: keyring.userId,\n displayName: keyring.displayName,\n role: keyring.role,\n permissions: keyring.permissions,\n deks: dekMap,\n salt: bufferToBase64(keyring.salt),\n })\n\n storage.setItem(storageKey(vault, userId), payload)\n\n // Visible, unsuppressable warning\n console.warn(\n '%c⚠️ NOYDB DEV UNLOCK ACTIVE ⚠️',\n 'color: red; font-size: 16px; font-weight: bold',\n `\\n\\nVault \"${vault}\" user \"${userId}\" is stored in ` +\n `${options.persistAcrossTabs ? 'localStorage' : 'sessionStorage'} in PLAINTEXT DEKs.\\n` +\n 'This is ONLY safe for local development. Never use in production.\\n' +\n 'Call clearDevUnlock() to remove.',\n )\n}\n\n/**\n * Load a dev-mode keyring from browser storage.\n *\n * Returns `null` if no dev-unlock state is stored for this vault + user,\n * or if the stored payload is malformed.\n *\n * Does NOT perform the production environment check — it's safe to CALL\n * `loadDevUnlock` in production (it will simply return `null` because no\n * dev-unlock state was ever written). The guard only fires on `enableDevUnlock`.\n *\n * @param vault - The vault name.\n * @param userId - The user ID.\n * @param options - Optional storage override.\n */\nexport async function loadDevUnlock(\n vault: string,\n userId: string,\n options: { persistAcrossTabs?: boolean } = {},\n): Promise<UnlockedKeyring | null> {\n if (typeof window === 'undefined') return null\n\n const storage = resolveStorage(options.persistAcrossTabs)\n const raw = storage.getItem(storageKey(vault, userId))\n if (!raw) return null\n\n let parsed: {\n _noydb_dev_unlock?: number\n userId: string\n displayName: string\n role: Role\n permissions: Record<string, 'rw' | 'ro'>\n deks: Record<string, string>\n salt: string\n }\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n\n if (parsed._noydb_dev_unlock !== 1) return null\n\n const deks = new Map<string, EnclaveKey>()\n for (const [collName, rawBase64] of Object.entries(parsed.deks)) {\n const dek = await globalThis.crypto.subtle.importKey(\n 'raw',\n base64ToBuffer(rawBase64),\n { name: 'AES-GCM', length: 256 },\n true,\n ['encrypt', 'decrypt'],\n )\n deks.set(collName, dek)\n }\n\n return {\n userId: parsed.userId,\n displayName: parsed.displayName,\n role: parsed.role,\n permissions: parsed.permissions,\n deks,\n kek: null,\n salt: base64ToBuffer(parsed.salt),\n authenticators: [],\n }\n}\n\n/**\n * Remove dev-unlock state from browser storage.\n *\n * Safe to call in production (no-op if no dev state exists).\n */\nexport function clearDevUnlock(\n vault: string,\n userId: string,\n options: { persistAcrossTabs?: boolean } = {},\n): void {\n if (typeof window === 'undefined') return\n const storage = resolveStorage(options.persistAcrossTabs)\n storage.removeItem(storageKey(vault, userId))\n}\n\n/**\n * Check if dev-unlock state exists for this vault + user.\n *\n * Safe to call in production (returns false if nothing is stored).\n */\nexport function isDevUnlockActive(\n vault: string,\n userId: string,\n options: { persistAcrossTabs?: boolean } = {},\n): boolean {\n if (typeof window === 'undefined') return false\n const storage = resolveStorage(options.persistAcrossTabs)\n return storage.getItem(storageKey(vault, userId)) !== null\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-lookup/indexing/index-snapshot.ts","../src/with-lookup/indexing/sorted-indexes.ts","../src/with-lookup/indexing/compound-indexes.ts","../src/with-lookup/indexing/eager-indexes.ts"],"sourcesContent":["/**\n * The on-disk shape of a persisted field index (#1359) — the format both\n * `SortedIndex` (#1344) and `CompoundIndex` (#1345) round-trip through, plus\n * the structural validator every load must pass.\n *\n * ⭐ **Nothing is flattened into a delimited string.** A compound entry keeps\n * its tuple as an ARRAY of per-component keys, each carrying its runtime KIND\n * alongside its value — which is the whole point of #1345's in-memory shape\n * and would be destroyed by a `join()`. Two properties depend on it:\n *\n * - **Kind survives.** Entries rank by runtime kind FIRST (mirroring\n * `predicate.ts`'s `isComparable`), so a `string` probe must not reach\n * `number` entries. An encoding that wrote only the value would collapse\n * `1` and `'1'` into one key space on the way back in.\n * - **Per-component ordering survives.** A string component may contain any\n * byte, including whatever a separator would have been.\n *\n * The validator is deliberately paranoid and its failure mode is a REBUILD,\n * never a partial load: a blob that is truncated, from a future format, or\n * internally out of order is rejected whole. See\n * {@link parseFieldIndexSnapshot}.\n */\n\n/** Bump on ANY incompatible change to the shapes below. An older stamp is rejected, not migrated. */\nexport const FIELD_INDEX_SNAPSHOT_VERSION = 1\n\n/** One `SortKey`, wire form: `[kind, key]`. `kind` IS the sort rank. */\nexport type WireKey = readonly [kind: number, key: number | string]\n\n/** One `SortedIndex` entry: `[kind, key, seq, id]`. */\nexport type WireSortedEntry = readonly [kind: number, key: number | string, seq: number, id: string]\n\n/** One `CompoundIndex` entry: `[tupleKeys, seq, id]` — the tuple stays an ARRAY. */\nexport type WireCompoundEntry = readonly [keys: readonly WireKey[], seq: number, id: string]\n\nexport interface SortedIndexSnapshot {\n readonly v: number\n readonly t: 'sorted'\n readonly field: string\n readonly nextSeq: number\n readonly entries: readonly WireSortedEntry[]\n}\n\nexport interface CompoundIndexSnapshot {\n readonly v: number\n readonly t: 'compound'\n readonly fields: readonly string[]\n readonly nextSeq: number\n readonly entries: readonly WireCompoundEntry[]\n}\n\nexport type FieldIndexSnapshot = SortedIndexSnapshot | CompoundIndexSnapshot\n\n/**\n * Sidecar key for a single-field sorted index. Field paths are\n * percent-encoded so no field name can collide with the `,` the compound\n * form joins on, nor with the `s:` / `c:` discriminator.\n */\nexport function sortedIndexKey(field: string): string {\n return `s:${encodeURIComponent(field)}`\n}\n\n/** Sidecar key for a compound index over an ordered field tuple. */\nexport function compoundIndexKey(fields: readonly string[]): string {\n return `c:${fields.map(encodeURIComponent).join(',')}`\n}\n\n/** A key is well-formed only if its declared kind matches its value's runtime type. */\nfunction isWireKey(x: unknown): x is WireKey {\n if (!Array.isArray(x) || x.length !== 2) return false\n const [kind, key] = x as [unknown, unknown]\n // 0 = number, 1 = string, 2 = Date-as-epoch-millis. See `sorted-indexes.ts`.\n if (kind === 0 || kind === 2) return typeof key === 'number' && Number.isFinite(key)\n if (kind === 1) return typeof key === 'string'\n return false\n}\n\nfunction isSeq(x: unknown): x is number {\n return typeof x === 'number' && Number.isInteger(x) && x >= 0\n}\n\n/**\n * Decode + validate a persisted snapshot. Returns `null` — meaning \"rebuild\"\n * — for anything that is not exactly the current format: bad JSON (a torn\n * write), a version stamp from another release, a shape mismatch, a\n * malformed key, or entries out of the order the index's binary searches\n * require. A caller must never repair a snapshot; the source of truth is the\n * cache, and rebuilding from it is always available.\n */\nexport function parseFieldIndexSnapshot(json: string): FieldIndexSnapshot | null {\n let raw: unknown\n try {\n raw = JSON.parse(json)\n } catch {\n return null\n }\n if (typeof raw !== 'object' || raw === null) return null\n const o = raw as Record<string, unknown>\n if (o['v'] !== FIELD_INDEX_SNAPSHOT_VERSION) return null\n if (!isSeq(o['nextSeq']) || !Array.isArray(o['entries'])) return null\n const nextSeq = o['nextSeq']\n const entries = o['entries'] as unknown[]\n\n if (o['t'] === 'sorted') {\n if (typeof o['field'] !== 'string') return null\n const out: WireSortedEntry[] = []\n for (const e of entries) {\n if (!Array.isArray(e) || e.length !== 4) return null\n const [kind, key, seq, id] = e as [unknown, unknown, unknown, unknown]\n if (!isWireKey([kind, key])) return null\n if (!isSeq(seq) || seq >= nextSeq || typeof id !== 'string') return null\n out.push([kind as number, key as number | string, seq, id])\n }\n if (!isAscending(out.map(e => [[e[0], e[1]] as WireKey]), out.map(e => e[2]))) return null\n return { v: FIELD_INDEX_SNAPSHOT_VERSION, t: 'sorted', field: o['field'], nextSeq, entries: out }\n }\n\n if (o['t'] === 'compound') {\n const fields = o['fields']\n if (!Array.isArray(fields) || fields.length === 0 || !fields.every(f => typeof f === 'string')) return null\n const out: WireCompoundEntry[] = []\n for (const e of entries) {\n if (!Array.isArray(e) || e.length !== 3) return null\n const [keys, seq, id] = e as [unknown, unknown, unknown]\n if (!Array.isArray(keys) || keys.length !== fields.length || !keys.every(isWireKey)) return null\n if (!isSeq(seq) || seq >= nextSeq || typeof id !== 'string') return null\n out.push([keys as readonly WireKey[], seq, id])\n }\n if (!isAscending(out.map(e => e[0]), out.map(e => e[1]))) return null\n return { v: FIELD_INDEX_SNAPSHOT_VERSION, t: 'compound', fields, nextSeq, entries: out }\n }\n\n return null\n}\n\n/**\n * Every binary search in both index classes assumes `(keys…, seq)` ascending.\n * A blob that is not sorted would not merely be slow — it would answer\n * WRONGLY, so it is rejected rather than re-sorted (re-sorting would launder\n * a corrupt blob into a plausible-looking answer).\n */\nfunction isAscending(keys: ReadonlyArray<readonly WireKey[]>, seqs: readonly number[]): boolean {\n for (let i = 1; i < keys.length; i++) {\n const c = compareWireTuples(keys[i - 1]!, keys[i]!)\n if (c > 0) return false\n if (c === 0 && seqs[i - 1]! >= seqs[i]!) return false\n }\n return true\n}\n\nfunction compareWireTuples(a: readonly WireKey[], b: readonly WireKey[]): number {\n for (let i = 0; i < a.length; i++) {\n const x = a[i]!\n const y = b[i]!\n if (x[0] !== y[0]) return x[0] - y[0]\n if (typeof x[1] === 'number' && typeof y[1] === 'number') {\n if (x[1] !== y[1]) return x[1] - y[1]\n } else if (x[1] !== y[1]) {\n return x[1] < y[1] ? -1 : 1\n }\n }\n return 0\n}\n","/**\n * Sorted (range) secondary indexes — the ordered sibling of the hash\n * index in `eager-indexes.ts` (#1344).\n *\n * A sorted index is an array of `{ kind, key, seq, id }` entries kept in\n * ascending `(kindRank, key, seq)` order. Binary search then answers\n * `<`, `<=`, `>`, `>=`, `between` and `startsWith` as a contiguous slice,\n * and `orderedIds()` walks the array for `orderBy(field).limit(n)`.\n *\n * Three properties are load-bearing, each mirroring the linear scan this\n * replaces (`kernel/query/predicate.ts`):\n *\n * - **Kind-partitioned.** `evaluateFieldClause`'s `isComparable` refuses\n * to order two values of different runtime types, so entries are ranked\n * by kind FIRST. A `string` probe therefore cannot reach `number`\n * entries, exactly as the scan cannot. Values with no order-defined\n * type (booleans, objects, arrays) are not indexed at all.\n * - **Nullish is not indexed**, same as the hash index.\n * - **Ties keep insertion order** (`seq`), which after a `build()` is\n * snapshot order — so an index-served `orderBy(...).limit(n)` returns\n * the same rows the stable scan-and-sort would. An in-place UPDATE\n * reinstates the rank it held rather than appending (#1369): the record\n * cache does not move a re-`put` record, so neither may the index.\n *\n * Keys canonicalise through the SAME per-field closure the hash index\n * uses (`ViaPipeline.canonicalizeIndexKey`), so a Via-covered field's\n * entries live in one key space here too. Range DISPATCH for a\n * Via-covered clause is nevertheless declined by the query builder (a\n * money operand has no ordered stored-form probe) — see\n * `kernel/query/builder.ts`'s `candidateRecords`.\n *\n * PERSISTENCE (#1359) is OPT-IN, via `{ persist: true }` on the declaration.\n * {@link SortedIndex.toSnapshot} / {@link SortedIndex.loadSnapshot} are the\n * whole of this class's part in it — the encrypted sidecar, the freshness\n * stamp and the debounce live in `persisted-field-indexes.ts`. Without the\n * opt-in the index is in memory only and rebuilt on hydrate, as before.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport { KIND_STRING, compareKeys, toSortKey, type KeyKind, type SortKey } from '../../kernel/query/sort-key.js'\nimport {\n FIELD_INDEX_SNAPSHOT_VERSION,\n type FieldIndexSnapshot,\n type SortedIndexSnapshot,\n type WireSortedEntry,\n} from './index-snapshot.js'\n\n/** Range operators a sorted index can answer. */\nexport type RangeOperator = '<' | '<=' | '>' | '>=' | 'between' | 'startsWith'\n\n// The ordered-key encoding itself moved to `kernel/query/sort-key.ts` (#1339):\n// #1339's declared joins key on it too, and the kernel spine may not\n// statically import a `with-*` service (`check-architecture.mjs`'s\n// port-layering ratchet). Re-exported here so every existing importer of this\n// module is unchanged, and so there is still exactly ONE definition of what\n// makes two stored values comparable.\nexport { KIND_STRING, compareKeys, toSortKey, type KeyKind, type SortKey } from '../../kernel/query/sort-key.js'\n\ninterface Entry extends SortKey {\n readonly seq: number\n readonly id: string\n}\n\n/** A single field's sorted index. */\nexport class SortedIndex {\n private readonly entries: Entry[] = []\n private nextSeq = 0\n\n constructor(readonly field: string) {}\n\n get size(): number {\n return this.entries.length\n }\n\n clear(): void {\n this.entries.length = 0\n this.nextSeq = 0\n }\n\n /**\n * Insert one record. No-op when the value has no order-defined kind.\n *\n * `seq` REINSTATES a rank a previous entry for this id held — see\n * {@link remove}'s return value and `CollectionIndexes.upsert`. Omit it\n * for a fresh insert, which appends after every existing tie.\n */\n add(id: string, value: unknown, canonicalKey: string | undefined, seq?: number): void {\n const sk = toSortKey(value, canonicalKey)\n if (!sk) return\n const entry: Entry = { ...sk, seq: seq ?? this.nextSeq++, id }\n this.entries.splice(this.insertionPoint(entry), 0, entry)\n }\n\n /**\n * Remove one record, addressed by the value it was indexed under.\n * Returns the rank it held, so an UPDATE can reinstate it (#1369):\n * `sortRecords()` sorts a snapshot whose order does not change when a\n * record is written in place, so a tie-breaking rank that drifted to the\n * back on every `put` would make an index-served page disagree with the\n * scan it must match. Same contract as `CompoundIndex.remove`.\n */\n remove(id: string, value: unknown, canonicalKey: string | undefined): number | undefined {\n const sk = toSortKey(value, canonicalKey)\n if (!sk) return undefined\n // Every entry sharing this key is contiguous; walk that run for the id.\n for (let i = this.lowerBound(sk); i < this.entries.length; i++) {\n const e = this.entries[i]!\n if (compareKeys(e, sk) !== 0) break\n if (e.id === id) {\n this.entries.splice(i, 1)\n return e.seq\n }\n }\n return undefined\n }\n\n /**\n * Ids in ascending (or descending) key order. Ties keep INSERTION order\n * in both directions — `sortRecords()` negates its comparator over a\n * stable sort, which reverses the key order but not the order of equal\n * keys. A plain `.reverse()` here would disagree on ties.\n */\n orderedIds(direction: 'asc' | 'desc'): string[] {\n if (direction === 'asc') return this.entries.map(e => e.id)\n const out: string[] = []\n let end = this.entries.length\n while (end > 0) {\n let start = end - 1\n while (start > 0 && compareKeys(this.entries[start - 1]!, this.entries[end - 1]!) === 0) start--\n for (let i = start; i < end; i++) out.push(this.entries[i]!.id)\n end = start\n }\n return out\n }\n\n /**\n * Ids matching a range clause. Returns an empty set when the probe has\n * no comparable kind — which is what the linear scan would produce too.\n */\n lookup(op: RangeOperator, value: unknown): Set<string> {\n if (op === 'startsWith') return this.prefix(value)\n if (op === 'between') {\n if (!Array.isArray(value) || value.length !== 2) return new Set()\n const lo = toSortKey(value[0], undefined)\n const hi = toSortKey(value[1], undefined)\n if (!lo || !hi || lo.kind !== hi.kind) return new Set()\n return this.slice(this.lowerBound(lo), this.upperBound(hi))\n }\n const probe = toSortKey(value, undefined)\n if (!probe) return new Set()\n const lo = this.lowerBound(probe)\n const hi = this.upperBound(probe)\n // Kind-partitioned: `<`/`>` must stay inside the probe's own kind run.\n const [kindStart, kindEnd] = this.kindBounds(probe.kind)\n switch (op) {\n case '<':\n return this.slice(kindStart, lo)\n case '<=':\n return this.slice(kindStart, hi)\n case '>':\n return this.slice(hi, kindEnd)\n case '>=':\n return this.slice(lo, kindEnd)\n default: {\n const _exhaustive: never = op\n void _exhaustive\n return new Set()\n }\n }\n }\n\n private prefix(value: unknown): Set<string> {\n if (typeof value !== 'string') return new Set()\n const out = new Set<string>()\n const from = this.lowerBound({ kind: KIND_STRING, key: value })\n for (let i = from; i < this.entries.length; i++) {\n const e = this.entries[i]!\n if (e.kind !== KIND_STRING) break\n if (!(e.key as string).startsWith(value)) break\n out.add(e.id)\n }\n return out\n }\n\n private slice(from: number, to: number): Set<string> {\n const out = new Set<string>()\n for (let i = from; i < to; i++) out.add(this.entries[i]!.id)\n return out\n }\n\n /** [start, end) of the run of entries with the given kind — binary, not linear. */\n private kindBounds(kind: KeyKind): [number, number] {\n return [this.kindBoundary(k => k < kind), this.kindBoundary(k => k <= kind)]\n }\n\n private kindBoundary(before: (k: KeyKind) => boolean): number {\n let lo = 0\n let hi = this.entries.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (before(this.entries[mid]!.kind)) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /** First index whose key is >= `probe`. */\n private lowerBound(probe: SortKey): number {\n let lo = 0\n let hi = this.entries.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareKeys(this.entries[mid]!, probe) < 0) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /** First index whose key is > `probe`. */\n private upperBound(probe: SortKey): number {\n let lo = 0\n let hi = this.entries.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareKeys(this.entries[mid]!, probe) <= 0) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /**\n * The persistable form of this index (#1359). `kind` travels with every\n * key: entries rank by runtime kind first, so an encoding that dropped it\n * would let a `string` probe reach `number` entries on the way back in.\n */\n toSnapshot(): SortedIndexSnapshot {\n return {\n v: FIELD_INDEX_SNAPSHOT_VERSION,\n t: 'sorted',\n field: this.field,\n nextSeq: this.nextSeq,\n entries: this.entries.map(e => [e.kind, e.key, e.seq, e.id] as WireSortedEntry),\n }\n }\n\n /**\n * Adopt a validated snapshot, or refuse it. Refusal (`false`) means the\n * caller rebuilds — it NEVER means a partial index: the entries are staged\n * and only swapped in once every one of them is accepted.\n *\n * `isLive` is the caller's liveness predicate over the record cache. An\n * entry naming a record the cache does not hold means the blob and the\n * collection have diverged (a crash between the two writes, say), so the\n * whole blob is discarded. Crash residue must be a rebuild, never a wrong\n * answer.\n */\n loadSnapshot(snap: FieldIndexSnapshot, isLive: (id: string) => boolean): boolean {\n if (snap.t !== 'sorted' || snap.field !== this.field) return false\n const staged: Entry[] = []\n for (const [kind, key, seq, id] of snap.entries) {\n if (!isLive(id)) return false\n staged.push({ kind: kind as KeyKind, key, seq, id })\n }\n this.entries.length = 0\n for (const e of staged) this.entries.push(e)\n this.nextSeq = snap.nextSeq\n return true\n }\n\n /**\n * Insertion point that keeps ties in `seq` order. A fresh entry lands at\n * the end of its tie run; a REINSTATED one (see {@link add}) lands back at\n * its own rank inside it.\n */\n private insertionPoint(entry: Entry): number {\n let i = this.upperBound(entry)\n while (i > 0 && compareKeys(this.entries[i - 1]!, entry) === 0 && this.entries[i - 1]!.seq > entry.seq) {\n i--\n }\n return i\n }\n}\n\n/** Build the whole index from a snapshot. Entry seqs follow snapshot order. */\nexport function buildSortedIndex<T>(\n idx: SortedIndex,\n records: ReadonlyArray<{ id: string; record: T }>,\n canonicalize?: (field: string, value: unknown) => string | undefined,\n): void {\n idx.clear()\n for (const { id, record } of records) {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) continue\n idx.add(id, value, canonicalize?.(idx.field, value))\n }\n}\n\n","/**\n * Compound (multi-field) sorted indexes — the tuple-keyed sibling of\n * `sorted-indexes.ts` (#1345).\n *\n * A compound index is an array of `{ keys, seq, id }` entries kept in\n * ascending `(keys[0], keys[1], …, seq)` order, where each `keys[i]` is\n * the SAME `SortKey` the single-field sorted index builds — so the tuple\n * encoding is order-preserving per component BY CONSTRUCTION rather than\n * by a second, hand-rolled serialization. Nothing is flattened into a\n * delimited string: a string component can therefore contain any byte\n * without colliding with a separator, and a money field canonicalized to\n * a scaled-integer string (`ViaPipeline.canonicalizeIndexKey`) or a date\n * stored as ISO text sorts inside its own component exactly as it does in\n * the single-field index.\n *\n * The two load-bearing properties of #1344 hold PER COMPONENT:\n *\n * - **Kind-partitioned.** `compareKeys` ranks by runtime kind first, so\n * within any equality-prefix run a `string` probe on the next\n * component cannot reach `number` entries — mirroring `predicate.ts`'s\n * `isComparable`, exactly as the linear scan behaves.\n * - **Ties keep insertion order** in both directions. `orderedIds()`\n * walks equal-key runs forward even when emitting descending, because\n * `sortRecords()` negates its comparator over a STABLE sort.\n *\n * **A record is indexed only when EVERY component has an order-defined\n * value.** One nullish or non-orderable component and the record is\n * absent — which is why the query builder compares {@link\n * CompoundIndex.size} against the snapshot size before dispatching, and\n * falls back to the scan when the index does not cover the collection.\n *\n * PERSISTENCE (#1359) is OPT-IN, same as its single-field sibling. The wire\n * form keeps the tuple as an ARRAY of per-component keys — see\n * `index-snapshot.ts`: flattening it to a delimited string would destroy\n * exactly the per-component ordering and kind partition this class is built\n * to preserve.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport { KIND_STRING, compareKeys, toSortKey, type RangeOperator, type SortKey } from './sorted-indexes.js'\nimport {\n FIELD_INDEX_SNAPSHOT_VERSION,\n type CompoundIndexSnapshot,\n type FieldIndexSnapshot,\n type WireCompoundEntry,\n type WireKey,\n} from './index-snapshot.js'\n\n/** A range constraint on the component just past the equality prefix. */\nexport interface CompoundRangeProbe {\n readonly op: RangeOperator\n readonly value: unknown\n}\n\ninterface CompoundEntry {\n readonly keys: readonly SortKey[]\n readonly seq: number\n readonly id: string\n}\n\n/** One collection's index over an ordered field tuple. */\nexport class CompoundIndex {\n private readonly entries: CompoundEntry[] = []\n private nextSeq = 0\n\n constructor(readonly fields: readonly string[]) {}\n\n get size(): number {\n return this.entries.length\n }\n\n clear(): void {\n this.entries.length = 0\n this.nextSeq = 0\n }\n\n /**\n * Insert one record. No-op unless every component has an ordered key.\n *\n * `seq` REINSTATES a rank a previous entry for this id held — see\n * {@link remove}'s return value and `CollectionIndexes.upsert`. Omit it\n * for a fresh insert, which appends after every existing tie.\n */\n add(id: string, keys: readonly SortKey[] | undefined, seq?: number): void {\n if (!keys) return\n const entry: CompoundEntry = { keys, seq: seq ?? this.nextSeq++, id }\n this.entries.splice(this.insertionPoint(entry), 0, entry)\n }\n\n /**\n * Remove one record, addressed by the tuple it was indexed under.\n * Returns the rank it held, so an UPDATE can reinstate it: `sortRecords()`\n * sorts a snapshot whose order does not change when a record is written in\n * place, so a tie-breaking rank that drifted to the back on every `put`\n * would make an index-served page disagree with the scan it must match.\n */\n remove(id: string, keys: readonly SortKey[] | undefined): number | undefined {\n if (!keys) return undefined\n const [from, to] = this.run(keys, keys.length)\n for (let i = from; i < to; i++) {\n if (this.entries[i]!.id === id) {\n const [gone] = this.entries.splice(i, 1)\n return gone!.seq\n }\n }\n return undefined\n }\n\n /**\n * Ids matching an equality prefix, optionally narrowed by a range on\n * the next component. An empty set is authoritative: the index covers\n * these fields and nothing matches.\n */\n lookup(prefix: readonly SortKey[], range?: CompoundRangeProbe): Set<string> {\n const [from, to] = this.run(prefix, prefix.length)\n if (!range) return this.slice(from, to)\n const k = prefix.length\n if (k >= this.fields.length) return new Set()\n\n if (range.op === 'startsWith') {\n if (typeof range.value !== 'string') return new Set()\n const out = new Set<string>()\n const start = this.lowerBound([...prefix, { kind: KIND_STRING, key: range.value }], k + 1, from, to)\n for (let i = start; i < to; i++) {\n const key = this.entries[i]!.keys[k]!\n if (key.kind !== KIND_STRING) break\n if (!(key.key as string).startsWith(range.value)) break\n out.add(this.entries[i]!.id)\n }\n return out\n }\n\n if (range.op === 'between') {\n if (!Array.isArray(range.value) || range.value.length !== 2) return new Set()\n const lo = toSortKey(range.value[0], undefined)\n const hi = toSortKey(range.value[1], undefined)\n if (!lo || !hi || lo.kind !== hi.kind) return new Set()\n return this.slice(\n this.lowerBound([...prefix, lo], k + 1, from, to),\n this.upperBound([...prefix, hi], k + 1, from, to),\n )\n }\n\n const probe = toSortKey(range.value, undefined)\n if (!probe) return new Set()\n // Kind-partitioned per component: an open-ended bound stays inside the\n // probe's own kind run, so `< '5'` never sweeps up numeric entries.\n const [kindStart, kindEnd] = this.kindRun(from, to, k, probe.kind)\n const lo = this.lowerBound([...prefix, probe], k + 1, from, to)\n const hi = this.upperBound([...prefix, probe], k + 1, from, to)\n switch (range.op) {\n case '<':\n return this.slice(kindStart, lo)\n case '<=':\n return this.slice(kindStart, hi)\n case '>':\n return this.slice(hi, kindEnd)\n case '>=':\n return this.slice(lo, kindEnd)\n default: {\n const _exhaustive: never = range.op\n void _exhaustive\n return new Set()\n }\n }\n }\n\n /**\n * Ids matching an equality prefix, in the order of the remaining\n * components. Ties keep INSERTION order in both directions — a plain\n * `.reverse()` would disagree with `sortRecords()` on equal keys.\n */\n orderedIds(prefix: readonly SortKey[], direction: 'asc' | 'desc'): string[] {\n const [from, to] = this.run(prefix, prefix.length)\n const out: string[] = []\n if (direction === 'asc') {\n for (let i = from; i < to; i++) out.push(this.entries[i]!.id)\n return out\n }\n let end = to\n while (end > from) {\n let start = end - 1\n while (start > from && this.compare(this.entries[start - 1]!, this.entries[end - 1]!.keys, this.fields.length) === 0) {\n start--\n }\n for (let i = start; i < end; i++) out.push(this.entries[i]!.id)\n end = start\n }\n return out\n }\n\n private slice(from: number, to: number): Set<string> {\n const out = new Set<string>()\n for (let i = from; i < to; i++) out.add(this.entries[i]!.id)\n return out\n }\n\n /** [start, end) of the entries whose first `upto` components equal `probe`. */\n private run(probe: readonly SortKey[], upto: number): [number, number] {\n return [\n this.lowerBound(probe, upto, 0, this.entries.length),\n this.upperBound(probe, upto, 0, this.entries.length),\n ]\n }\n\n /** [start, end) of the entries in [from,to) whose component `k` has this kind. */\n private kindRun(from: number, to: number, k: number, kind: number): [number, number] {\n return [\n this.kindBoundary(from, to, k, x => x < kind),\n this.kindBoundary(from, to, k, x => x <= kind),\n ]\n }\n\n private kindBoundary(from: number, to: number, k: number, before: (kind: number) => boolean): number {\n let lo = from\n let hi = to\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (before(this.entries[mid]!.keys[k]!.kind)) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /** First index in [from,to) whose first `upto` components are >= `probe`. */\n private lowerBound(probe: readonly SortKey[], upto: number, from: number, to: number): number {\n let lo = from\n let hi = to\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (this.compare(this.entries[mid]!, probe, upto) < 0) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /** First index in [from,to) whose first `upto` components are > `probe`. */\n private upperBound(probe: readonly SortKey[], upto: number, from: number, to: number): number {\n let lo = from\n let hi = to\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (this.compare(this.entries[mid]!, probe, upto) <= 0) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n /**\n * The persistable form of this index (#1359). Each entry's tuple stays an\n * ARRAY of `[kind, key]` pairs: nothing is joined into a string, so a\n * string component may hold any byte and the per-component kind partition\n * survives the round trip intact.\n */\n toSnapshot(): CompoundIndexSnapshot {\n return {\n v: FIELD_INDEX_SNAPSHOT_VERSION,\n t: 'compound',\n fields: [...this.fields],\n nextSeq: this.nextSeq,\n entries: this.entries.map(\n e => [e.keys.map(k => [k.kind, k.key] as WireKey), e.seq, e.id] as WireCompoundEntry,\n ),\n }\n }\n\n /**\n * Adopt a validated snapshot, or refuse it. Same contract as\n * `SortedIndex.loadSnapshot`: all-or-nothing, and an entry naming a record\n * the cache does not hold rejects the whole blob so crash residue becomes a\n * rebuild rather than a wrong answer. A snapshot whose field tuple is not\n * exactly this index's is refused — the declaration changed under it.\n */\n loadSnapshot(snap: FieldIndexSnapshot, isLive: (id: string) => boolean): boolean {\n if (snap.t !== 'compound') return false\n if (snap.fields.length !== this.fields.length) return false\n for (let i = 0; i < snap.fields.length; i++) if (snap.fields[i] !== this.fields[i]) return false\n const staged: CompoundEntry[] = []\n for (const [keys, seq, id] of snap.entries) {\n if (!isLive(id)) return false\n staged.push({ keys: keys.map(k => ({ kind: k[0] as SortKey['kind'], key: k[1] })), seq, id })\n }\n this.entries.length = 0\n for (const e of staged) this.entries.push(e)\n this.nextSeq = snap.nextSeq\n return true\n }\n\n /**\n * Insertion point that keeps ties in `seq` order. A fresh entry lands at\n * the end of its tie run; a REINSTATED one (see {@link add}) lands back at\n * its own rank inside it.\n */\n private insertionPoint(entry: CompoundEntry): number {\n const to = this.upperBound(entry.keys, this.fields.length, 0, this.entries.length)\n let i = to\n while (i > 0 && this.compare(this.entries[i - 1]!, entry.keys, this.fields.length) === 0\n && this.entries[i - 1]!.seq > entry.seq) {\n i--\n }\n return i\n }\n\n private compare(entry: CompoundEntry, probe: readonly SortKey[], upto: number): number {\n for (let i = 0; i < upto; i++) {\n const c = compareKeys(entry.keys[i]!, probe[i]!)\n if (c !== 0) return c\n }\n return 0\n }\n}\n\n/**\n * The tuple key for one record, or `undefined` when any component is\n * nullish or has no order-defined runtime type. Canonicalizes through the\n * same per-field closure the hash and sorted indexes use, so a Via-covered\n * component lives in one key space across all three.\n */\nexport function tupleKeyOf<T>(\n fields: readonly string[],\n record: T,\n canonicalize?: (field: string, value: unknown) => string | undefined,\n): SortKey[] | undefined {\n const keys: SortKey[] = []\n for (const field of fields) {\n const value = readPath(record, field)\n if (value === null || value === undefined) return undefined\n const key = toSortKey(value, canonicalize?.(field, value))\n if (!key) return undefined\n keys.push(key)\n }\n return keys\n}\n\n/** Probe keys for an equality prefix, or `undefined` if any operand is unorderable. */\nexport function probeKeysOf(values: readonly unknown[]): SortKey[] | undefined {\n const keys: SortKey[] = []\n for (const value of values) {\n const key = toSortKey(value, undefined)\n if (!key) return undefined\n keys.push(key)\n }\n return keys\n}\n\n/** Build the whole index from a snapshot. Entry seqs follow snapshot order. */\nexport function buildCompoundIndex<T>(\n idx: CompoundIndex,\n records: ReadonlyArray<{ id: string; record: T }>,\n canonicalize?: (field: string, value: unknown) => string | undefined,\n): void {\n idx.clear()\n for (const { id, record } of records) {\n idx.add(id, tupleKeyOf(idx.fields, record, canonicalize))\n }\n}\n\n/** Stable map key for a field tuple. `\\0` cannot appear in a field path. */\nexport function compoundKey(fields: readonly string[]): string {\n return fields.join('\\u0000')\n}\n","/**\n * Secondary indexes for the query DSL.\n *\n * ships **in-memory hash indexes**:\n * - Built during `Collection.ensureHydrated()` from the decrypted cache\n * - Maintained incrementally on `put` and `delete`\n * - Consulted by the query executor for `==` and `in` operators on\n * indexed fields, falling back to a linear scan otherwise\n * - Live entirely in memory — no adapter writes for the index itself\n *\n * Persistent encrypted index blobs (the spec's \"store as a separate\n * AES-256-GCM blob\" note) are deferred to a follow-up issue. The reasons\n * are documented in the PR body — short version: at the target\n * scale of 1K–50K records, building the index during hydrate is free,\n * so persistence buys nothing measurable.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport { stringifyBucketKey, isProbeableBucketKey } from '../../kernel/query/distinct-key.js'\nimport { SortedIndex, buildSortedIndex, type RangeOperator } from './sorted-indexes.js'\nimport {\n CompoundIndex,\n buildCompoundIndex,\n compoundKey,\n probeKeysOf,\n tupleKeyOf,\n type CompoundRangeProbe,\n} from './compound-indexes.js'\nimport {\n compoundIndexKey,\n sortedIndexKey,\n type FieldIndexSnapshot,\n} from './index-snapshot.js'\n\n/**\n * Index declaration accepted by `Collection`'s constructor.\n *\n * Accepts:\n * - `string` — a single-field hash index (`'clientId'`)\n * - `{ fields: [...] }` or `readonly string[]` — a composite index\n * over an ordered field tuple. Only lazy-mode\n * collections consume composite declarations today; eager mode\n * silently treats a composite as equivalent to declaring each\n * component field as its own single-field index.\n *\n * Additive variants (unique constraints, partial indexes) will land as\n * further union members without breaking existing declarations.\n */\nexport type IndexDef =\n | string\n | {\n readonly fields: readonly string[]\n /**\n * Enforce that no two records share this field tuple. A MULTI-field\n * tuple is a COMPOUND unique — uniqueness over the tuple, never over\n * each component on its own (`['workerId','employerId']` allows one\n * worker at many employers). Null-distinct: a record with `null` or\n * `undefined` in any constrained field is exempt.\n *\n * ⚠️ **What \"unique\" means depends on the collection's mode (#1358),\n * and one of the four cannot promise prevention at all:**\n *\n * - eager (default) — prevented, from the in-memory map.\n * - lazy (`prefetch: false`) — prevented, by a point lookup through the\n * persisted `_idx/` mirror. Requires `withIndexing()`.\n * - tiered (`tiers: [...]`) — prevented across every tier whose DEK the\n * writer holds. **A tier the writer cannot read is outside the\n * guarantee** — those records are unreadable, so no local check can\n * see them, and a duplicate against one is accepted.\n * - CRDT (`crdt: '...'`) — **NOT prevented.** Uniqueness cannot be\n * enforced across concurrent replicas: two replicas that are offline\n * from each other can both accept the same value, and no local check\n * can prevent that. The write always succeeds and both records are\n * kept; the collision is reported on the `unique:violation` event as\n * a `UniqueConstraintError`, both when it happens and when a later\n * session first sees the two records together. What you get is\n * uniqueness within what this writer could see at write time, plus\n * detection when the replicas meet.\n */\n readonly unique?: boolean\n readonly kind?: IndexKind\n /**\n * Persist this index as an encrypted sidecar so a restart reuses it\n * instead of rebuilding (#1359). Only meaningful with `kind: 'sorted'`\n * — the hash index is not persisted. OPT-IN until measured: at the\n * 1K–50K target scale rebuild-on-open is free, and a sidecar is a\n * write on every debounce window plus a blob at rest.\n */\n readonly persist?: boolean\n }\n | readonly string[]\n\n/**\n * The access structure an index declaration asks for (#1344).\n *\n * `'hash'` (the default, and what every pre-#1344 declaration means) is\n * the equality/`in` bucket map. `'sorted'` additionally maintains an\n * ordered array over the same canonicalized keys, which lights up\n * index-driven `<`, `<=`, `>`, `>=`, `between`, `startsWith` and\n * `orderBy(field).limit(n)`. A `'sorted'` declaration keeps the hash\n * index too — `==`/`in` stay O(1).\n */\nexport type IndexKind = 'hash' | 'sorted'\n\n/**\n * Normalize a declared `IndexDef[]` into a uniform `{ fields, unique? }`\n * shape: `string` → `{fields:[s]}`, `string[]` → `{fields}`, object →\n * passthrough. Pure — used by `Collection`'s sensitive-field leak check\n * and by `getDeclaredIndexes()` (introspection).\n */\nexport function normalizeIndexDefs(\n defs: readonly IndexDef[],\n): ReadonlyArray<{ readonly fields: readonly string[]; readonly unique?: boolean }> {\n return defs.map((def) => {\n if (typeof def === 'string') return { fields: [def] }\n if (Array.isArray(def)) return { fields: def }\n return def as { readonly fields: readonly string[]; readonly unique?: boolean }\n })\n}\n\n/**\n * Internal representation of a built hash index.\n *\n * Maps stringified field values to the set of record ids whose value\n * for that field matches. Stringification keeps the index simple and\n * works uniformly for primitives (`'open'`, `'42'`, `'true'`).\n *\n * Records whose indexed field is `undefined` or `null` are NOT inserted\n * — `query().where('field', '==', undefined)` falls back to a linear\n * scan, which is the conservative behavior.\n */\nexport interface HashIndex {\n readonly field: string\n readonly buckets: Map<string, Set<string>>\n}\n\n/**\n * Container for all indexes on a single collection.\n *\n * Methods are pure with respect to the in-memory `buckets` Map — they\n * never touch the adapter or the keyring. The Collection class owns\n * lifecycle (build on hydrate, maintain on put/delete).\n */\nexport class CollectionIndexes {\n private readonly indexes = new Map<string, HashIndex>()\n\n /**\n * Sorted (range) indexes, keyed by field — the #1344 half. Parallel to\n * `indexes` above and maintained at the SAME mutation sites, through the\n * SAME `canonicalize` closure, so the two can never disagree about which\n * key space a field lives in.\n */\n private readonly sorted = new Map<string, SortedIndex>()\n\n /**\n * Compound (tuple-keyed) sorted indexes, keyed by the joined field\n * tuple — the #1345 half. Third parallel map, maintained at the SAME\n * mutation sites through the SAME `canonicalize` closure as the other\n * two, so no field can end up in disagreeing key spaces.\n */\n private readonly compound = new Map<string, CompoundIndex>()\n\n /**\n * The indexes declared `persist: true` (#1359), keyed by their SIDECAR key\n * (`s:<field>` / `c:<f1>,<f2>`). A separate map rather than a flag on the\n * index classes: persistence is a property of the DECLARATION, and the\n * index classes stay unaware of storage.\n */\n private readonly persistable = new Map<string, SortedIndex | CompoundIndex>()\n\n /**\n * Per-field bucket-key canonicalizer (#672 review C1), registered ONCE\n * via {@link setCanonicalizer} when the collection wires indexing up\n * (money-aware via `ViaPipeline.canonicalizeIndexKey`). Consulted by\n * EVERY bucket-mutation site — `build`/`upsert`/`remove` — so bucket\n * membership is symmetric by construction: a value can never be added\n * under a canonical key and later removed under the raw one (or vice\n * versa), which is what stranded ids on `put`/`delete` before this fix.\n */\n private canonicalize?: (field: string, value: unknown) => string | undefined\n\n /**\n * Register the bucket-key canonicalizer. Called once, where the\n * collection constructs its indexing state — safe to call again (the\n * closure is simply replaced) but there is normally only one caller.\n */\n setCanonicalizer(fn: (field: string, value: unknown) => string | undefined): void {\n this.canonicalize = fn\n }\n\n /**\n * Declare an index. Subsequent record additions are tracked under it.\n * Calling this twice for the same field is a no-op (idempotent).\n */\n declare(field: string): void {\n if (this.indexes.has(field)) return\n this.indexes.set(field, { field, buckets: new Map() })\n }\n\n /**\n * Declare a sorted (range) index on a field. Independent of\n * {@link declare} — callers that want both `==` speed and range speed\n * declare both (which is what `withIndexing()` does for a\n * `kind: 'sorted'` declaration). Idempotent.\n */\n declareSorted(field: string, opts?: { readonly persist?: boolean }): void {\n let idx = this.sorted.get(field)\n if (!idx) {\n idx = new SortedIndex(field)\n this.sorted.set(field, idx)\n }\n if (opts?.persist) this.persistable.set(sortedIndexKey(field), idx)\n }\n\n /** True if the given field has a declared SORTED index. */\n hasSorted(field: string): boolean {\n return this.sorted.has(field)\n }\n\n /** All sorted-index field names, in declaration order. */\n sortedFields(): string[] {\n return [...this.sorted.keys()]\n }\n\n /** Number of entries in a field's sorted index (0 when undeclared). */\n sortedSize(field: string): number {\n return this.sorted.get(field)?.size ?? 0\n }\n\n /**\n * Range lookup: ids whose `field` satisfies the operator. Returns\n * `null` when no SORTED index covers the field — the caller falls back\n * to a linear scan. An empty set means \"the index covers this field and\n * nothing matches\", which is authoritative.\n */\n lookupRange(field: string, op: RangeOperator, value: unknown): ReadonlySet<string> | null {\n return this.sorted.get(field)?.lookup(op, value) ?? null\n }\n\n /**\n * Ids of every indexed record in `field` order. `null` when no sorted\n * index covers the field. Records whose value is nullish or has no\n * order-defined type are absent — compare against {@link sortedSize}\n * before treating the result as the full collection.\n */\n orderedIds(field: string, direction: 'asc' | 'desc'): readonly string[] | null {\n return this.sorted.get(field)?.orderedIds(direction) ?? null\n }\n\n /**\n * Declare a compound (multi-field) sorted index over an ordered field\n * tuple. A tuple shorter than two fields is a single-field sorted index\n * and is ignored here. Idempotent.\n */\n declareCompound(fields: readonly string[], opts?: { readonly persist?: boolean }): void {\n if (fields.length < 2) return\n const key = compoundKey(fields)\n let idx = this.compound.get(key)\n if (!idx) {\n idx = new CompoundIndex([...fields])\n this.compound.set(key, idx)\n }\n if (opts?.persist) this.persistable.set(compoundIndexKey(fields), idx)\n }\n\n /** Every declared field tuple, in declaration order. */\n compoundTuples(): ReadonlyArray<readonly string[]> {\n return [...this.compound.values()].map(idx => idx.fields)\n }\n\n /**\n * Number of records the tuple's index holds (0 when undeclared). A\n * record with a nullish or non-orderable component is absent, so a\n * caller that removes clauses from the plan MUST compare this against\n * the snapshot size before trusting the result.\n */\n compoundSize(fields: readonly string[]): number {\n return this.compound.get(compoundKey(fields))?.size ?? 0\n }\n\n /**\n * Compound lookup: ids whose leading components equal `prefixValues`,\n * optionally narrowed by a range on the component just past them.\n * `null` when no compound index covers the tuple, or when an operand\n * has no order-defined type — the caller falls back to a linear scan.\n */\n lookupCompound(\n fields: readonly string[],\n prefixValues: readonly unknown[],\n range?: CompoundRangeProbe,\n ): ReadonlySet<string> | null {\n const idx = this.compound.get(compoundKey(fields))\n if (!idx) return null\n const prefix = probeKeysOf(prefixValues)\n if (!prefix) return null\n return idx.lookup(prefix, range)\n }\n\n /**\n * Ids matching an equality prefix, ordered by the remaining components.\n * `null` when no compound index covers the tuple or an operand has no\n * order-defined type. See {@link compoundSize} for the coverage caveat.\n */\n compoundOrderedIds(\n fields: readonly string[],\n prefixValues: readonly unknown[],\n direction: 'asc' | 'desc',\n ): readonly string[] | null {\n const idx = this.compound.get(compoundKey(fields))\n if (!idx) return null\n const prefix = probeKeysOf(prefixValues)\n if (!prefix) return null\n return idx.orderedIds(prefix, direction)\n }\n\n /** True if the given field has a declared index. */\n has(field: string): boolean {\n return this.indexes.has(field)\n }\n\n /** All declared field names, in declaration order. */\n fields(): string[] {\n return [...this.indexes.keys()]\n }\n\n /**\n * Build all declared indexes from a snapshot of records.\n * Called once per hydration. O(N × indexes.size). Buckets through the\n * registered {@link setCanonicalizer} closure, same as `upsert`/`remove`.\n */\n build<T>(records: ReadonlyArray<{ id: string; record: T }>, skip: ReadonlySet<string> = EMPTY_KEYS): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n for (const { id, record } of records) {\n addToIndex(idx, id, record, this.canonicalize)\n }\n }\n // `skip` names the sidecar keys a persisted snapshot already restored\n // (#1359) — rebuilding them would throw that work away AND renumber every\n // `seq`, which is what the whole ordered fast path rests on.\n for (const [field, idx] of this.sorted) {\n if (skip.has(sortedIndexKey(field))) continue\n buildSortedIndex(idx, records, this.canonicalize)\n }\n for (const idx of this.compound.values()) {\n if (skip.has(compoundIndexKey(idx.fields))) continue\n buildCompoundIndex(idx, records, this.canonicalize)\n }\n }\n\n /** Sidecar keys of every index declared `persist: true`, in declaration order. */\n persistableKeys(): readonly string[] {\n return [...this.persistable.keys()]\n }\n\n /** The persistable snapshot behind one sidecar key, or `undefined` if it is not declared. */\n snapshotIndex(key: string): FieldIndexSnapshot | undefined {\n return this.persistable.get(key)?.toSnapshot()\n }\n\n /**\n * Adopt a validated snapshot into the index behind `key`. `false` means the\n * caller must rebuild that index from the cache — the index is left\n * untouched, never half-loaded.\n */\n restoreIndex(key: string, snap: FieldIndexSnapshot, isLive: (id: string) => boolean): boolean {\n return this.persistable.get(key)?.loadSnapshot(snap, isLive) ?? false\n }\n\n /**\n * Insert or update a single record across all indexes.\n * Called by `Collection.put()` after the encrypted write succeeds.\n *\n * If `previousRecord` is provided, the record is removed from any old\n * buckets first — this is the update path. Pass `null` for fresh adds.\n */\n upsert<T>(id: string, newRecord: T, previousRecord: T | null): void {\n if (this.indexes.size === 0 && this.sorted.size === 0 && this.compound.size === 0) return\n // Detach the compound and sorted entries FIRST, holding the rank each one had. An\n // in-place `put` does not move a record within `snapshot()`, so it must\n // not move it within a tie run either — otherwise an index-served\n // `orderBy(...).limit(n)` page disagrees with the stable scan-and-sort\n // it is required to reproduce exactly (#1345, #1369). The `this.remove()`\n // below re-runs both removals, which are then no-ops.\n const ranks = new Map<string, number | undefined>()\n const sortedRanks = new Map<string, number | undefined>()\n if (previousRecord !== null) {\n for (const [key, idx] of this.compound) {\n ranks.set(key, idx.remove(id, tupleKeyOf(idx.fields, previousRecord, this.canonicalize)))\n }\n // Same for the single-field sorted indexes (#1369) — the defect and the\n // remedy are identical, and the two implementations must agree.\n for (const [key, idx] of this.sorted) {\n const value = readPath(previousRecord, idx.field)\n if (value === null || value === undefined) continue\n sortedRanks.set(key, idx.remove(id, value, this.canonicalize?.(idx.field, value)))\n }\n this.remove(id, previousRecord)\n }\n for (const idx of this.indexes.values()) {\n addToIndex(idx, id, newRecord, this.canonicalize)\n }\n for (const [key, idx] of this.sorted) {\n const value = readPath(newRecord, idx.field)\n if (value === null || value === undefined) continue\n idx.add(id, value, this.canonicalize?.(idx.field, value), sortedRanks.get(key))\n }\n for (const [key, idx] of this.compound) {\n idx.add(id, tupleKeyOf(idx.fields, newRecord, this.canonicalize), ranks.get(key))\n }\n }\n\n /**\n * Remove a record from all indexes. Called by `Collection.delete()`\n * (and as the first half of `upsert` for the update path).\n */\n remove<T>(id: string, record: T): void {\n if (this.indexes.size === 0 && this.sorted.size === 0 && this.compound.size === 0) return\n for (const idx of this.indexes.values()) {\n removeFromIndex(idx, id, record, this.canonicalize)\n }\n for (const idx of this.sorted.values()) {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) continue\n idx.remove(id, value, this.canonicalize?.(idx.field, value))\n }\n for (const idx of this.compound.values()) {\n idx.remove(id, tupleKeyOf(idx.fields, record, this.canonicalize))\n }\n }\n\n /** Drop all index data. Called when the collection is invalidated. */\n clear(): void {\n for (const idx of this.indexes.values()) {\n idx.buckets.clear()\n }\n for (const idx of this.sorted.values()) {\n idx.clear()\n }\n for (const idx of this.compound.values()) {\n idx.clear()\n }\n }\n\n /**\n * Equality lookup: return the set of record ids whose `field` matches\n * the given value. Returns `null` if no index covers the field — the\n * caller should fall back to a linear scan.\n *\n * ⛔ ALSO `null` when the OPERAND has no bucket ADDRESS (#1402) — a\n * nullish operand, or an object one, which every object-valued record in\n * the collection shares. Answering those from the buckets returns rows\n * the scan does not, silently, and only on the collections that happen to\n * have an index. See `isProbeableBucketKey` for the two measurements.\n *\n * The returned Set is a reference to the index's internal storage —\n * callers must NOT mutate it.\n */\n lookupEqual(field: string, value: unknown): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n if (!isProbeableBucketKey(value)) return null\n const key = stringifyKey(value)\n return idx.buckets.get(key) ?? EMPTY_SET\n }\n\n /**\n * Set lookup: return the union of record ids whose `field` matches any\n * of the given values. Returns `null` if no index covers the field.\n *\n * ⛔ ALSO `null` when ANY member of `values` has no bucket address\n * (#1402) — one unaddressable member poisons the whole union, since the\n * result is a union and there is no partial answer to hand back. Same\n * measurement as {@link lookupEqual}.\n */\n lookupIn(field: string, values: readonly unknown[]): ReadonlySet<string> | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n if (!values.every(isProbeableBucketKey)) return null\n const out = new Set<string>()\n for (const value of values) {\n const key = stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (bucket) {\n for (const id of bucket) out.add(id)\n }\n }\n return out\n }\n\n /**\n * One record id per NON-EMPTY bucket of `field`'s hash index, in bucket\n * order — the index-backed spine of `Query.distinct()` (#1347). `null` when\n * no hash index covers the field, so the caller scans instead.\n *\n * The bucket set IS the distinct key set, so this costs O(buckets) rather\n * than O(records). It returns REPRESENTATIVE IDS rather than the keys\n * themselves on purpose: a bucket key is a canonical string (money's\n * BigInt-normalized scaled int, say), and `distinct()` owes its caller the\n * field's real value, which only the record carries.\n *\n * Bucket insertion order is first-seen record order — `build()` walks the\n * snapshot in order and `upsert()` appends — which is why an index-backed\n * `distinct()` and a scanned one agree on ORDER too, not merely on the set.\n * Empty buckets are skipped defensively; `removeFromIndex` already deletes\n * them, so this is belt-and-braces against a future partial removal path.\n */\n bucketRepresentatives(field: string): readonly string[] | null {\n const idx = this.indexes.get(field)\n if (!idx) return null\n const out: string[] = []\n for (const bucket of idx.buckets.values()) {\n for (const id of bucket) {\n out.push(id)\n break\n }\n }\n return out\n }\n}\n\nconst EMPTY_SET: ReadonlySet<string> = new Set()\nconst EMPTY_KEYS: ReadonlySet<string> = new Set()\n\n/**\n * Stringify a value into a stable bucket key.\n *\n * ⚠️ The DEFINITION moved to `kernel/query/distinct-key.ts` (#1347) and is\n * only aliased here. `distinct()` / `countDistinct()` recompute this key on\n * the scan path, and an index-backed `distinct()` reads the buckets it made —\n * one definition is what stops those two answers from drifting apart. Change\n * the shared function, not a copy.\n */\nconst stringifyKey = stringifyBucketKey\n\nfunction addToIndex<T>(\n idx: HashIndex,\n id: string,\n record: T,\n canonicalize?: (field: string, value: unknown) => string | undefined,\n): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = canonicalize?.(idx.field, value) ?? stringifyKey(value)\n let bucket = idx.buckets.get(key)\n if (!bucket) {\n bucket = new Set()\n idx.buckets.set(key, bucket)\n }\n bucket.add(id)\n}\n\nfunction removeFromIndex<T>(\n idx: HashIndex,\n id: string,\n record: T,\n canonicalize?: (field: string, value: unknown) => string | undefined,\n): void {\n const value = readPath(record, idx.field)\n if (value === null || value === undefined) return\n const key = canonicalize?.(idx.field, value) ?? stringifyKey(value)\n const bucket = idx.buckets.get(key)\n if (!bucket) return\n bucket.delete(id)\n // Clean up empty buckets so the Map doesn't accumulate dead keys.\n if (bucket.size === 0) idx.buckets.delete(key)\n}\n"],"mappings":"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","names":["lo","hi","lo","hi"]}